Merge pull request #985 from SharePoint/dev

Merge from dev to master
This commit is contained in:
Laura Kokkarinen 2019-09-08 15:55:02 +03:00 committed by GitHub
commit ec222c8a28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 42675 additions and 174 deletions

View File

@ -1,6 +1,6 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"isCreatingSolution": false,
"environment": "spo",
"version": "1.7.0",
"libraryName": "react-bot-framework",

View File

@ -1,10 +1,11 @@
# Microsoft Bot Framework Web Chat
## Summary
A web part that acts as a web chat component for bot's built on the Microsoft Bot Framework using the Direct Line API. When sending messages
the web part uses the username of the currently logged in user. The web part has settings for color for branding purposes.
![bot framework client web part](./assets/bot-framework-webpart-preview.png)
| Name | Image |Description |
|:------------:|------------|------------|
|Bot Framework v4 WebPart|![bot framework v3 web part](./assets/bot-frameworkv4-webpart-preview.png)|A web part that uses the [botframework-webchat module](https://www.npmjs.com/package/botframework-webchat) to create implement a React component to render the Bot Framework v4 webchat component. This web part is able to render Text and richt attachments (Images, Cards, Adaptive Cards, ...) and has settings for branding purposes.|
|Bot Framework v3 WebPart|![bot framework v4 web part](./assets/bot-framework-webpart-preview.png)|A web part that acts as a web chat component for bot's built on the Microsoft Bot Framework using the DirectLine API. When sending messages the web part uses the username of the currently logged in user. The web part has settings for color for branding purposes.|
You can see this web part sample, including a sample VS 2015 bot application in practice from [PnP SPFx Special Interest Group recording](https://youtu.be/Tv03CU_PmVs?t=1329)
where sample was demonstrated.
@ -21,17 +22,17 @@ where sample was demonstrated.
## Prerequisites
> You need to have a bot created and registered using the Microsoft Bot Framework and registered to use the Direct Line Channel,
> You need to have a bot created and registered using the Microsoft Bot Framework and registered to use the DirectLine Channel,
which will give you the secret needed when adding this web part to the page. For more information on creating a bot and registering
the channel you can see the official web site at [dev.botframework.com](http://dev.botframework.com), as well as various tutorials
over at [www.garypretty.co.uk/category/microsoft-bot-framework/](http://www.garypretty.co.uk/category/microsoft-bot-framework/)
over at [www.garypretty.co.uk/category/microsoft-bot-framework/](http://www.garypretty.co.uk/category/microsoft-bot-framework/) & [Stephan Bisser's blog](https://bisser.io)
See more details on how to create a bot from following locations.
* [Getting started with the Connector](https://docs.botframework.com/en-us/csharp/builder/sdkreference/gettingstarted.html) - MS Bot Framework documentation
* [Creating your first bot with the Microsoft Bot Framework Part 1 Build and test locally](http://www.garypretty.co.uk/2016/07/14/creating-your-first-bot-with-the-microsoft-bot-framework-part-1/) - [@GaryPretty](https://twitter.com/GaryPretty)
* [Creating your first bot with the Microsoft Bot Framework Part 2 publishing and chatting through Skype](http://www.garypretty.co.uk/2016/07/16/creating-your-first-bot-with-the-microsoft-bot-framework-part-2/) - [@GaryPretty](https://twitter.com/GaryPretty)
* [Creating your first bot with the Microsoft Bot Framework Part 2 publishing and chatting through Skype](http://www.garypretty.co.uk/2016/07/16/creating-your-first-bot-with-the-microsoft-bot-framework-part-2/)- [@GaryPretty](https://twitter.com/GaryPretty)
* [Create a QnA Bot with Azure Bot Service v4](https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/tutorials/create-qna-bot)
> Notice that you can find simplistic bot implemented with Visual Studio 2015 using the bot templates (Oct 2016)
under the [vs2015-bot-application](./vs2015-bot-application) folder. This is simplistic bot based on above blog posts, which responses random string back.
@ -41,6 +42,7 @@ under the [vs2015-bot-application](./vs2015-bot-application) folder. This is sim
Solution|Author(s)
--------|---------
bot-framework | Gary Pretty ([@garypretty](http://www.twitter.com/garypretty), [garypretty.co.uk](www.garypretty.co.uk))
|webpart v4| Stephan Bisser ([@stephanbisser](https://twitter.com/stephanbisser), [bisser.io](https://bisser.io))
## Version history
@ -50,6 +52,7 @@ Version|Date|Comments
1.1|Jan 24th, 2017|Updated to RC0
1.2|Feb 23rd, 2017|Initial load bug fix
1.3|February 8, 2018|Updated to SPFx 1.7
1.4|September 4, 2019|Added BotFramework webchat v4
## Disclaimer
@ -65,36 +68,38 @@ Version|Date|Comments
- `tsd install`
- `gulp serve`
- Register your bot in the Microsoft Bot Framework Portal, configure the Direct Line channel on the bot and obtain your Direct Line secret.
- Register your bot in the Microsoft Bot Framework Portal, configure the DirectLine channel on the bot and obtain your DirectLine secret.
## Features
This Web Part illustrates the following concepts on top of the SharePoint Framework:
- Connecting and communicating with a bot built on the Microsoft Bot Framework using the Direct Line Channel
- Connecting and communicating with a bot built on the Microsoft Bot Framework using the DirectLine Channel
- Validating Property Pane Settings
- Office UI Fabric
- React
When adding the web part to a page you need to obtain your Bot Direct Line Channel secret via the [Bot Framework Portal](http://dev.botframework.com).
You then add this secret via the Property Pane of the web part. If there is an error initializing the Direct Line Client with the bot then they will
be shown in the console within the browser.
When adding the web part to a page you need to obtain your bot's DirectLine channel secret via the [Azure Portal](http://portal.azure.com).
You then add this secret via the Property Pane of the web part. If there is an error initializing the DirectLine Client with the bot then they will be shown in the console within the browser.
You can add Direct Line channel to bot from the bot details page under the "Add another channel" section
You can add DirectLine channel to bot from the bot details page under the "Add a featured channel" section
![bot framework client web part](./assets/add-another-channel.png)
![bot framework client web part](./assets/bf-add-directline-channel.png)
After this, you can click Edit for the just added Channel to get the needed secret for the client side web part.
![bot framework client web part](./assets/bot-framework-configure-direct-line-secret.png)
![bot framework client web part](./assets/bf-configure-directline-secret.png)
Additional settings can be set to style the web part, including;
Additional settings can be set to style the web part, including:
- Display title of the web part
- Web part header background color
- Placeholder text
- Foreground / background colors for messages, both from the user and from the bot
|Version|Description|
|-------|----|
|[v3](./src/webparts/botFrameworkChat) & [v4](./src/webparts/botFrameworkChatv4) | Foreground / background colors for messages & sendbox, both from the user and from the bot|
|[v4](./src/webparts/botFrameworkChatv4)| Add avatar images or initials for both user and bot|
|[v4](./src/webparts/botFrameworkChatv4)| Hide upload button in sendbox|
|[v3](./src/webparts/botFrameworkChat)| Display title of the web part|
|[v3](./src/webparts/botFrameworkChat)| Web part header background color|
|[v3](./src/webparts/botFrameworkChat)| Placeholder text|
Currently this web part only supports plain text conversations with a bot. Other message types,
such as Rich Cards and Attachments are not supported, but are on the roadmap for a future update.
Currently the [Bot Framework Webchat v3 web part](./src/webparts/botFrameworkChat) only supports plain text conversations with a bot. Other message types, such as Rich Cards and Attachments are not supported, so in order to use that, use the [Bot Framework Webchat v4 web part](./src/webparts/botFrameworkChatv4)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-bot-framework" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -9,10 +9,19 @@
"manifest": "./src/webparts/botFrameworkChat/BotFrameworkChatWebPart.manifest.json"
}
]
},
"bot-framework-chatv-4-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/botFrameworkChatv4/BotFrameworkChatv4WebPart.js",
"manifest": "./src/webparts/botFrameworkChatv4/BotFrameworkChatv4WebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"BotFrameworkChatWebPartStrings": "lib/webparts/botFrameworkChat/loc/{locale}.js"
"BotFrameworkChatWebPartStrings": "lib/webparts/botFrameworkChat/loc/{locale}.js",
"BotFrameworkChatv4WebPartStrings": "lib/webparts/botFrameworkChatv4/loc/{locale}.js"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,8 @@
"@types/react": "16.4.2",
"@types/react-dom": "16.0.5",
"@types/webpack-env": "1.13.1",
"botframework-directlinejs": "^0.11.4",
"botframework-webchat": "^4.5.2",
"react": "16.3.2",
"react-dom": "16.3.2",
"swagger-client": "^2.1.23"

View File

@ -0,0 +1,26 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "7aa6d70f-2a9f-4e92-ad01-a45c582e82be",
"alias": "BotFrameworkChatv4WebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "BotFrameworkChatv4" },
"description": { "default": "Use the botframework-webchat v4 React component to render the webchat with the new v4 UI" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "BotFrameworkChatv4"
}
}]
}

View File

@ -0,0 +1,164 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneToggle
} from '@microsoft/sp-webpart-base';
import * as strings from 'BotFrameworkChatv4WebPartStrings';
import BotFrameworkChatv4 from './components/BotFrameworkChatv4';
import { IBotFrameworkChatv4Props } from './components/IBotFrameworkChatv4Props';
export interface IBotFrameworkChatv4WebPartProps {
description: string;
directLineSecret: string;
bubbleBackground: string;
bubbleTextColor: string;
bubbleFromUserBackground: string;
bubbleFromUserTextColor: string;
backgroundColor: string;
botAvatarImage: string;
botAvatarInitials: string;
userAvatarImage: string;
userAvatarInitials: string;
hideUploadButton: boolean;
sendBoxBackground: string;
sendBoxTextColor: string;
}
export default class BotFrameworkChatv4WebPart extends BaseClientSideWebPart<IBotFrameworkChatv4WebPartProps> {
public render(): void {
const element: React.ReactElement<IBotFrameworkChatv4Props > = React.createElement(
BotFrameworkChatv4,
{
description: this.properties.description,
directLineSecret: this.properties.directLineSecret,
bubbleBackground: this.properties.bubbleBackground,
bubbleTextColor: this.properties.bubbleTextColor,
bubbleFromUserBackground: this.properties.bubbleFromUserBackground,
bubbleFromUserTextColor: this.properties.bubbleFromUserTextColor,
backgroundColor: this.properties.backgroundColor,
botAvatarImage: this.properties.botAvatarImage,
botAvatarInitials: this.properties.botAvatarInitials,
userAvatarImage: this.properties.userAvatarImage,
userAvatarInitials: this.properties.userAvatarInitials,
hideUploadButton: this.properties.hideUploadButton,
sendBoxBackground: this.properties.sendBoxBackground,
sendBoxTextColor: this.properties.sendBoxTextColor,
context: this.context
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: 'Here you can set various properties and settings regarding how your bot chat web part will look visually and functionally work'
},
groups: [
{
groupName: 'Bot Connection',
groupFields: [
PropertyPaneTextField('directLineSecret', {
label: 'Direct Line Secret'
})
]
},
{
groupName: 'Appearance - Colors',
groupFields: [
PropertyPaneTextField('backgroundColor', {
label: 'Background color of webchat'
}),
PropertyPaneTextField('bubbleBackground', {
label: 'Bot messages background color',
onGetErrorMessage: this._validateColorPropertyAsync.bind(this), // validation function
deferredValidationTime: 500 // delay after which to run the validation function
}),
PropertyPaneTextField('bubbleTextColor', {
label: 'Bot messages foreground color',
onGetErrorMessage: this._validateColorPropertyAsync.bind(this), // validation function
deferredValidationTime: 500 // delay after which to run the validation function
}),
PropertyPaneTextField('bubbleFromUserBackground', {
label: 'User messages background color',
onGetErrorMessage: this._validateColorPropertyAsync.bind(this), // validation function
deferredValidationTime: 500 // delay after which to run the validation function
}),
PropertyPaneTextField('bubbleFromUserTextColor', {
label: 'User messages foreground color',
onGetErrorMessage: this._validateColorPropertyAsync.bind(this), // validation function
deferredValidationTime: 500 // delay after which to run the validation function
}),
PropertyPaneTextField('sendBoxBackground', {
label: 'Sendbox background color',
onGetErrorMessage: this._validateColorPropertyAsync.bind(this), // validation function
deferredValidationTime: 500, // delay after which to run the validation function
}),
PropertyPaneTextField('sendBoxTextColor', {
label: 'Sendbox text color',
onGetErrorMessage: this._validateColorPropertyAsync.bind(this), // validation function
deferredValidationTime: 500 // delay after which to run the validation function
})
]
}
]
},
{
header: {
description: 'Here you can set various properties and settings regarding how your bot chat web part will look visually and functionally work'
},
groups: [
{
groupName: 'Appearance - Visuals',
groupFields: [
PropertyPaneTextField('botAvatarImage', {
label: 'Avatar image used for bot'
}),
PropertyPaneTextField('botAvatarInitials', {
label: 'Avatar initials used for bot'
}),
PropertyPaneTextField('userAvatarImage', {
label: 'Avatar image used for user'
}),
PropertyPaneTextField('userAvatarInitials', {
label: 'Avatar initials used for user'
}),
PropertyPaneToggle('hideUploadButton', {
label: 'Diable upload button'
})
]
}
]
}
]
};
}
private _validateColorPropertyAsync(value: string): string {
var colorRegex = /^#([a-zA-Z0-9]){6}$/;
if (!value || colorRegex.test(value) == false) {
return "Please enter a valid 6 character hex color value";
}
return "";
}
}

View File

@ -0,0 +1,74 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.botFrameworkChatv4 {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,47 @@
import * as React from 'react';
import styles from './BotFrameworkChatv4.module.scss';
import { IBotFrameworkChatv4Props } from './IBotFrameworkChatv4Props';
import { escape } from '@microsoft/sp-lodash-subset';
import ReactWebChat from 'botframework-webchat';
import styleSetOptions from 'botframework-webchat';
import { DirectLine } from 'botframework-directlinejs';
export interface IBotFrameworkChatv4State {
directLine: any;
styleSetOptions: any;
}
export default class BotFrameworkChatv4 extends React.Component<IBotFrameworkChatv4Props, IBotFrameworkChatv4State> {
constructor(props) {
super(props);
const styleOptions = {
backgroundColor: this.props.backgroundColor,
botAvatarImage: this.props.botAvatarImage,
userAvatarImage: this.props.userAvatarImage,
hideUploadButton: this.props.hideUploadButton,
sendBoxBackground: this.props.sendBoxBackground,
sendBoxTextColor: this.props.sendBoxTextColor,
bubbleBackground: this.props.bubbleBackground,
bubbleTextColor: this.props.bubbleTextColor,
bubbleFromUserTextColor: this.props.bubbleFromUserTextColor,
bubbleFromUserBackground: this.props.bubbleFromUserBackground,
userAvatarInitials: this.props.userAvatarInitials,
botAvatarInitials: this.props.botAvatarInitials
};
this.state = {
directLine: new DirectLine({
secret: this.props.directLineSecret
}),
styleSetOptions: styleOptions
};
}
public render(): React.ReactElement<IBotFrameworkChatv4Props> {
return (
<div className={styles.botFrameworkChatv4} style={{ height: 700 }}>
<ReactWebChat directLine={this.state.directLine} styleOptions={this.state.styleSetOptions} />
</div>
);
}
}

View File

@ -0,0 +1,19 @@
import { IWebPartContext } from '@microsoft/sp-webpart-base';
export interface IBotFrameworkChatv4Props {
description: string;
directLineSecret: string;
bubbleBackground: string;
bubbleTextColor: string;
bubbleFromUserBackground: string;
bubbleFromUserTextColor: string;
backgroundColor: string;
botAvatarImage: string;
botAvatarInitials: string;
userAvatarImage: string;
userAvatarInitials: string;
hideUploadButton: boolean;
sendBoxBackground: string;
sendBoxTextColor: string;
context: IWebPartContext;
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface IBotFrameworkChatv4WebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'BotFrameworkChatv4WebPartStrings' {
const strings: IBotFrameworkChatv4WebPartStrings;
export = strings;
}

View File

@ -0,0 +1,47 @@
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.2/MicrosoftTeams.schema.json",
"manifestVersion": "1.2",
"packageName": "BotFrameworkChatv4",
"id": "7aa6d70f-2a9f-4e92-ad01-a45c582e82be",
"version": "0.1",
"developer": {
"name": "SPFx + Teams Dev",
"websiteUrl": "https://products.office.com/en-us/sharepoint/collaboration",
"privacyUrl": "https://privacy.microsoft.com/en-us/privacystatement",
"termsOfUseUrl": "https://www.microsoft.com/en-us/servicesagreement"
},
"name": {
"short": "BotFrameworkChatv4"
},
"description": {
"short": "Use the botframework-webchat v4 React component to render the webchat with the new v4 UI",
"full": "Use the botframework-webchat v4 React component to render the webchat with the new v4 UI"
},
"icons": {
"outline": "tab20x20.png",
"color": "tab96x96.png"
},
"accentColor": "#004578",
"configurableTabs": [
{
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=7aa6d70f-2a9f-4e92-ad01-a45c582e82be",
"canUpdateConfiguration": false,
"scopes": [
"team"
]
}
],
"validDomains": [
"*.login.microsoftonline.com",
"*.sharepoint.com",
"*.sharepoint-df.com",
"spoppe-a.akamaihd.net",
"spoprod-a.akamaihd.net",
"resourceseng.blob.core.windows.net",
"msft.spoppe.com"
],
"webApplicationInfo": {
"resource": "https://{teamSiteDomain}",
"id": "00000003-0000-0ff1-ce00-000000000000"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -18718,6 +18718,11 @@
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=",
"dev": true
},
"string-format": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz",
"integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA=="
},
"string-hash": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz",

View File

@ -43,6 +43,7 @@
"react-dom": "16.7.0",
"react-draft-wysiwyg": "^1.13.2",
"spfx-uifabric-themes": "^0.6.0",
"string-format": "^2.0.0",
"typescript": "^3.2.4",
"xml2js": "^0.4.19"
},

View File

@ -29,4 +29,5 @@ export interface IEventState {
showRecurrenceSeriesInfo:boolean;
newRecurrenceEvent:boolean;
recurrenceAction:string;
recurrenceDescription?:string;
}

View File

@ -4,6 +4,7 @@ import * as strings from 'CalendarWebPartStrings';
import { IEventProps } from './IEventProps';
import { IEventState } from './IEventState';
import * as moment from 'moment';
import { parseString } from 'xml2js';
import 'react-big-calendar/lib/css/react-big-calendar.css';
import { PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker";
import {
@ -44,6 +45,7 @@ import { Map, ICoordinates, MapType } from "@pnp/spfx-controls-react/lib/Map";
import { EventRecurrenceInfo } from '../../controls/EventRecurrenceInfo/EventRecurrenceInfo';
import { getGUID } from '@pnp/common';
import { toLocaleShortDateString } from '../../utils/dateUtils';
const format = require('string-format');
const DayPickerStrings: IDatePickerStrings = {
months: [strings.January, strings.February, strings.March, strings.April, strings.May, strings.June, strings.July, strings.August, strings.September, strings.October, strings.November, strings.December],
@ -177,6 +179,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
eventData.fRecurrence = true;
eventData.UID = getGUID();
panelMode = IPanelModelEnum.add;
eventData.RecurrenceData = await this.returnExceptionRecurrenceInfo(eventData.RecurrenceData);
}
startDate = `${moment(this.state.startDate).format('YYYY/MM/DD')}`;
endDate = `${moment(this.state.endDate).format('YYYY/MM/DD')}`;
@ -299,6 +302,8 @@ export class Event extends React.Component<IEventProps, IEventState> {
event.geolocation.Latitude = this.latitude;
event.geolocation.Longitude = this.longitude;
const recurrenceInfo = event.EventType === "4" && event.MasterSeriesItemID !== "" ? event.RecurrenceData : await this.returnExceptionRecurrenceInfo(event.RecurrenceData);
// Update Component Data
this.setState({
eventData: event,
@ -315,6 +320,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
siteRegionalSettings: siteRegionalSettigns,
locationLatitude: this.latitude,
locationLongitude: this.longitude,
recurrenceDescription: recurrenceInfo
});
} else {
editorState = EditorState.createEmpty();
@ -337,8 +343,6 @@ export class Event extends React.Component<IEventProps, IEventState> {
* @memberof Event
*/
public async componentDidMount() {
await this.renderEventData();
}
@ -566,6 +570,280 @@ export class Event extends React.Component<IEventProps, IEventState> {
this.setState({ showRecurrenceSeriesInfo: true, recurrenceSeriesEdited: true });
}
/**
*
*
* @private
* @param {string} rule
* @memberof Event
*/
private parseDailyRule(rule): string {
const keys = Object.keys(rule);
if (keys.indexOf("weekday") !== -1 && rule["weekday"] === "TRUE")
return format("{} {}", format(strings.everyFormat, 1), strings.weekDayLabel);
if (keys.indexOf("dayFrequency") !== -1) {
const dayFrequency: number = parseInt(rule["dayFrequency"]);
const frequencyFormat = dayFrequency === 1 ? strings.everyFormat : dayFrequency === 2 ? strings.everySecondFormat : strings.everyNthFormat;
return format("{} {}", format(frequencyFormat, dayFrequency), strings.dayLable);
}
return "Invalid recurrence format";
}
/**
*
*
* @private
* @param { string } rule
* @memberof Event
*/
private parseWeeklyRule(rule): string {
const frequency: number = parseInt(rule["weekFrequency"]);
const keys = Object.keys(rule);
const dayMap: any = {
"mo": strings.Monday,
"tu": strings.Tuesday,
"we": strings.Wednesday,
"th": strings.Thursday,
"fr": strings.Friday,
"sa": strings.Saturday,
"su": strings.Sunday
};
let days: string[] = [];
for (let key of keys) {
days.push(dayMap[key]);
}
return format("{}{} {} {}",
frequency === 1 ? format(strings.everyFormat, frequency) : frequency === 2 ? format(strings.everySecondFormat, frequency): format(strings.everyNthFormat, frequency),
strings.weekLabel,
strings.onLabel,
days.join(", "));
}
/**
*
*
* @private
* @param { string } rule
* @memberof Event
*/
private parseMonthlyRule(rule): string {
const frequency: number = parseInt(rule["monthFrequency"]);
const day: number = parseInt(rule["day"]);
return format("{}{} {}",
frequency === 1 ? format(strings.everyFormat, frequency) : frequency === 2 ? format(strings.everySecondFormat, frequency): format(strings.everyNthFormat, frequency),
strings.monthLabel,
format(strings.onTheDayFormat, day)
);
}
/**
*
* @private
* @param { string } rule
* @memberof Event
*/
private parseMonthlyByDayRule(rule): string {
let keys: string[] = Object.keys(rule);
const dayTypeMap: any = {
"day": strings.weekDayLabel,
"weekend_day": strings.weekEndDay,
"mo": strings.Monday,
"tu": strings.Tuesday,
"we": strings.Wednesday,
"th": strings.Thursday,
"fr": strings.Friday,
"sa": strings.Saturday,
"su": strings.Sunday
};
const orderType: any = {
"first": strings.firstLabel,
"second": strings.secondLabel,
"third": strings.thirdLabel,
"fourth": strings.fourthLabel,
"last": strings.lastLabel
};
let order: string;
let dayType: string;
let frequencyFormat: string;
for (let key of keys) {
switch (key) {
case "monthFrequency":
const frequency = parseInt(rule[key]);
switch(frequency) {
case 1:
frequencyFormat = format(strings.everyFormat, frequency);
break;
case 2:
frequencyFormat = format(strings.everySecondFormat, frequency);
break;
default:
frequencyFormat = format(strings.everyNthFormat, frequency);
break;
}
break;
case "weekDayOfMonth":
order = orderType[rule[key]];
break;
default:
dayType = dayTypeMap[rule[key]];
break;
}
}
return format("{} {} {} {} {}{}",
frequencyFormat,
strings.monthLabel.toLowerCase(),
strings.onTheLabel,
order,
dayType,
strings.theSuffix);
}
/**
*
* @private
* @param rule
* @memberof Event
*/
private parseYearlyRule(rule): string {
const keys: string[] = Object.keys(rule);
const months: string[] = DayPickerStrings.months;
let frequencyString: string;
let month: string;
let day: string;
for (let key of keys) {
switch(key) {
case "yearFrequency":
const frequency = parseInt(rule[key]);
const frequencyFormat = frequency == 1 ? strings.everyFormat : frequency == 2 ? strings.everySecondFormat : strings.everyNthFormat;
frequencyString = format(frequencyFormat, frequency);
break;
case "month":
month = months[parseInt(rule[key]) - 1];
break;
case "day":
day = rule[key];
break;
}
}
return format("{} {} {}", frequencyString, strings.yearLabel, format(strings.theNthOfMonthFormat, month, day));
}
/**
*
*
* @private
* @param rule
* @memberof Event
*/
private parseYearlyByDayRule(rule): string {
const keys: string[] = Object.keys(rule);
const months: string[] = DayPickerStrings.months;
const orderMap: any = {
"first": strings.firstLabel,
"second": strings.secondLabel,
"third": strings.thirdLabel,
"fourth": strings.fourthLabel,
"last": strings.lastLabel
};
const dayTypeMap: any = {
"day": strings.weekDayLabel,
"weekend_day": strings.weekEndDay,
"mo": strings.Monday,
"tu": strings.Tuesday,
"we": strings.Wednesday,
"th": strings.Thursday,
"fr": strings.Friday,
"sa": strings.Saturday,
"su": strings.Sunday
};
let frequencyString: string;
let month: string;
let order: string;
let dayTypeString: string;
for (let key of keys) {
switch(key) {
case "yearFrequency":
const frequency = parseInt(rule[key]);
const frequencyFormat = frequency === 1 ? strings.everyFormat : frequency === 2 ? strings.everySecondFormat : strings.everyNthFormat;
frequencyString = format(frequencyFormat, frequency);
break;
case "weekDayOfMonth":
order = orderMap[rule[key]];
break;
case "month":
month = months[parseInt(rule[key]) - 1];
break;
default:
dayTypeString = dayTypeMap[rule[key]];
break;
}
return format("{} {} {}",
frequencyString,
strings.yearLabel,
format(strings.onTheDayTypeFormat, order, dayTypeString.toLowerCase(), strings.theSuffix)
);
}
}
/**
*
*
* @private
* @param {string} recurrenceData
* @memberof Event
*/
private async returnExceptionRecurrenceInfo(recurrenceData: string) {
const promise = new Promise<object>((resolve, reject) => {
parseString(recurrenceData, (err, result) => {
if (err) {
reject(err);
}
resolve(result);
});
});
const recurrenceInfo: any = await promise;
let keys = Object.keys(recurrenceInfo.recurrence.rule[0].repeat[0]);
const recurrenceTypes = ["daily", "weekly", "monthly", "monthlyByDay", "yearly", "yearlyByDay"];
for (var key of keys) {
const rule = recurrenceInfo.recurrence.rule[0].repeat[0][key][0]['$'];
switch(recurrenceTypes.indexOf(key)) {
case 0:
return this.parseDailyRule(rule);
break;
case 1:
return this.parseWeeklyRule(rule);
break;
case 2:
return this.parseMonthlyRule(rule);
break;
case 3:
return this.parseMonthlyByDayRule(rule);
break;
case 4:
return this.parseYearlyRule(rule);
break;
case 5:
return this.parseYearlyByDayRule(rule);
break;
default:
continue;
}
}
}
/**
*
*
@ -579,6 +857,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
//console.log(this.returnedRecurrenceInfo);
}
/**
*
*
@ -588,6 +867,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
public render(): React.ReactElement<IEventProps> {
const { editorState } = this.state;
return (
<div>
<Panel
@ -617,6 +897,7 @@ export class Event extends React.Component<IEventProps, IEventState> {
(this.state.eventData && (this.state.eventData.EventType !== "0" && this.state.showRecurrenceSeriesInfo !== true)) ?
<div>
<h2 style={{ display: 'inline-block', verticalAlign: 'top' }}>{ strings.recurrenceEventLabel }</h2>
{ this.state.recurrenceDescription ? <span style={{ display: 'block' }} >{ this.state.recurrenceDescription }</span> : null }
<DefaultButton
style={{ display: 'inline-block', marginLeft: '330px', verticalAlign: 'top', width: 'auto' }}
iconProps={{ iconName: 'RecurringEvent' }}

View File

@ -368,6 +368,10 @@ export default class parseRecurrentEvent {
if ((new Date(init) > end) || (rTotal > 0 && rTotal <= total)) loop = false;
}
}
if (e.fRecurrence === "1" && e.MasterSeriesItemID !== "") {
const ni = this.cloneObj(e);
er.push(ni);
}
return er;
} //end recurrence check
}

View File

@ -126,6 +126,15 @@ define([], function () {
patternLabel: "Pattern",
dateRangeLabel: "Date Range",
occurrencesLabel: "occurrences",
ofMonthLabel: "of"
ofMonthLabel: "of",
everyFormat: "Every {0} ",
everySecondFormat: "Every {0} ",
everyNthFormat: "Every {0} ",
onTheDayFormat: "on the {0}th day",
onTheLabel: "on the",
theSuffix: "",
yearLabel: "year",
theNthOfMonthFormat: "on {0} {1}",
onTheDayTypeFormat: "on the {0} {1}"
}
});

View File

@ -127,7 +127,15 @@ declare interface ICalendarWebPartStrings {
dateRangeLabel: string;
occurrencesLabel: string;
ofMonthLabel:string;
everyFormat: string;
everySecondFormat: string;
everyNthFormat: string;
onTheDayFormat: string;
onTheLabel: string;
theSuffix: string;
yearLabel: string;
theNthOfMonthFormat: string;
onTheDayTypeFormat: string;
}
declare module 'CalendarWebPartStrings' {

View File

@ -5,7 +5,7 @@ define([], function () {
OcurrencesLabel: "Tillfällen",
dateRangeLabel: "Datumintervall",
weekEndDay: "Helgdag",
weekDayLabel: "Veckodag",
weekDayLabel: "Arbetsdag",
lastLabel: "sista",
fourthLabel: "fjärde",
thirdLabel: "tredje",
@ -16,7 +16,7 @@ define([], function () {
ofEveryLabel: "för varje ",
AllowedValues1to12Label: "Tillåtna värder 1 till 12",
noEndDate: "inget slutdatum",
everyweekdays: "alla veckodagar",
everyweekdays: "alla arbetsdagar",
days: "dag",
every: "var",
EndByLabel: "slutar den",
@ -113,7 +113,7 @@ define([], function () {
nextLabel: "Nästa",
showMore: "mer",
recurrenceEventLabel: "Återkommande händelse",
editRecurrenceSeries: "Redigera återkommande händelse",
editRecurrenceSeries: "Redigera serie",
ifRecurrenceLabel: "Återkommande ?",
onLabel: "På",
offLabel: "Av",
@ -126,7 +126,15 @@ define([], function () {
patternLabel: "Schema",
dateRangeLabel: "Datumintervall",
occurrencesLabel: "tillfällen",
ofMonthLabel: "i"
ofMonthLabel: "i",
everyFormat: "Varje ",
everySecondFormat: "Varannan ",
everyNthFormat: "Var {0}:e ",
onTheDayFormat: "den {0}:e dagen",
onTheLabel: "på den",
theSuffix: "en",
theNthOfMonthFormat: "den {1} {0}",
onTheDayTypeFormat: "på den {0} {1}{2}"
}
});

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

32
samples/react-page-navigator/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,28 @@
{
"@pnp/generator-spfx": {
"framework": "react",
"pnpFramework": "reactjs.plus",
"pnp-libraries": [
"@pnp/pnpjs",
"@pnp/spfx-property-controls",
"@pnp/spfx-controls-react",
"ouifr@6",
"rush@2.9"
],
"pnp-ci": [],
"pnp-vetting": [],
"spfxenv": "spo",
"pnp-testing": []
},
"@microsoft/generator-sharepoint": {
"environment": "spo",
"framework": "react",
"isCreatingSolution": true,
"version": "1.9.1",
"libraryName": "navigator",
"libraryId": "065ee566-e00d-4058-bbfd-356c8d9a8005",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,40 @@
# React Page Navigator
## Summary
This web part fetches all the automatically added Header anchor tags in a SharePoint page and displays them in a Navigation component.
When added to a Vertical Section it can be used as a Contents table for the page
![Page Navigator](./assets/PageNavigator.gif)
## Used SharePoint Framework Version
![version](https://img.shields.io/badge/version-1.9.1-green.svg)
## Version history
Version|Date|Comments
-------|----|--------
1.0|September 5, 2019|Initial release
## Minimal Path to Awesome
- git clone the repo
- npm i
- gulp bundle --ship
- gulp package-solution --ship
- Add the app package to Site Collection App Catalog and Install the App
- Add the web part to a page in the Site Collection
## Solution
Solution|Author(s)
--------|---------
react-page-navigator|Aakash Bhardwaj
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-page-navigator" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

View File

@ -0,0 +1,20 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"page-navigator-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/pageNavigator/PageNavigatorWebPart.js",
"manifest": "./src/webparts/pageNavigator/PageNavigatorWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"PageNavigatorWebPartStrings": "lib/webparts/pageNavigator/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "navigator",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,4 @@
{
"preset": "@voitanos/jest-preset-spfx-react16",
"rootDir": "../src"
}

View File

@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-page-navigator",
"id": "065ee566-e00d-4058-bbfd-356c8d9a8005",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/react-page-navigator.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,29 @@
'use strict';
// check if gulp dist was called
if (process.argv.indexOf('dist') !== -1) {
// add ship options to command call
process.argv.push('--ship');
}
const path = require('path');
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
const gulpSequence = require('gulp-sequence');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
// Create clean distrubution package
gulp.task('dist', gulpSequence('clean', 'bundle', 'package-solution'));
// Create clean development package
gulp.task('dev', gulpSequence('clean', 'bundle', 'package-solution'));
/**
* Custom Framework Specific gulp tasks
*/
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
{
"name": "navigator",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"preversion": "node ./tools/pre-version.js",
"postversion": "gulp dist",
"test": "./node_modules/.bin/jest --config ./config/jest.config.json",
"test:watch": "./node_modules/.bin/jest --config ./config/jest.config.json --watchAll"
},
"dependencies": {
"@microsoft/sp-core-library": "1.9.1",
"@microsoft/sp-lodash-subset": "1.9.1",
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
"@microsoft/sp-webpart-base": "1.9.1",
"@pnp/pnpjs": "^1.3.5",
"@pnp/spfx-controls-react": "1.14.0",
"@pnp/spfx-property-controls": "1.16.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "^6.182.0",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"resolutions": {
"@types/react": "16.8.8"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-3.3": "0.1.7",
"@microsoft/sp-build-web": "1.9.1",
"@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-tslint-rules": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"@types/react": "^16.7.22",
"@voitanos/jest-preset-spfx-react16": "^1.1.0",
"ajv": "~5.2.2",
"gulp": "~3.9.1",
"gulp-sequence": "1.0.0",
"jest": "^23.6.0"
}
}

View File

@ -0,0 +1,99 @@
import { INavLink } from 'office-ui-fabric-react/lib/Nav';
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { SPHttpClient } from '@microsoft/sp-http';
export class SPService {
public static async GetAnchorLinks(context: WebPartContext) {
let anchorLinks: INavLink[] = [];
try {
/* Page ID on which the web part is added */
let pageId = context.pageContext.listItem.id;
/* Get the canvasContent1 data for the page which consists of all the HTML */
let data = await context.spHttpClient.get(`${context.pageContext.web.absoluteUrl}/_api/sitepages/pages(${pageId})`, SPHttpClient.configurations.v1);
let jsonData = await data.json();
let canvasContent1 = jsonData.CanvasContent1;
let canvasContent1JSON: any[] = JSON.parse(canvasContent1);
/* Initialize variables to be used for sorting and adding the Navigation links */
let depth = 0;
let headingIndex = 0;
let subHeadingIndex = -1;
let headingOrder = 0;
let prevHeadingOrder = 0;
/* Array to store all unique anchor URLs */
let allUrls: string[] = [];
/* Traverse through all the Text web parts in the page */
canvasContent1JSON.map((webPart) => {
if (webPart.innerHTML) {
let HTMLString: string = webPart.innerHTML;
while (HTMLString.search(/<h[1-4]>/g) !== -1) {
/* The Header Text value */
let headingValue = HTMLString.substring(HTMLString.search(/<h[1-4]>/g) + 4, HTMLString.search(/<\/h[1-4]>/g));
console.log(headingValue);
headingOrder = parseInt(HTMLString.charAt(HTMLString.search(/<h[1-4]>/g) + 2));
/* Check if same anchorUrl already exists */
let urlExists = true;
let anchorUrl = `#${headingValue.replace(/ /g, '-')}`.toLowerCase();
let urlSuffix = 1;
while (urlExists === true) {
urlExists = (allUrls.indexOf(anchorUrl) === -1) ? false : true;
if (urlExists) {
anchorUrl = anchorUrl + `-${urlSuffix}`;
urlSuffix++;
}
}
allUrls.push(anchorUrl);
/* Add links to Nav element */
if (anchorLinks.length === 0) {
anchorLinks.push({ name: headingValue, key: anchorUrl, url: anchorUrl, links: [], isExpanded: true });
prevHeadingOrder = headingOrder;
} else {
if (headingOrder <= prevHeadingOrder) {
/* Adding or Promoting links */
if (depth === 0) {
anchorLinks.push({ name: headingValue, key: anchorUrl, url: anchorUrl, links: [], isExpanded: true });
headingIndex++;
subHeadingIndex = -1;
} else {
if ((depth === 2) && (headingOrder === prevHeadingOrder)) {
anchorLinks[headingIndex].links[subHeadingIndex].links.push({ name: headingValue, key: anchorUrl, url: anchorUrl, links: [], isExpanded: true });
} else {
anchorLinks[headingIndex].links.push({ name: headingValue, key: anchorUrl, url: anchorUrl, links: [], isExpanded: true });
subHeadingIndex++;
}
prevHeadingOrder = headingOrder;
}
} else {
/* Making sub links */
if (depth === 0) {
anchorLinks[headingIndex].links.push({ name: headingValue, key: anchorUrl, url: anchorUrl, links: [], isExpanded: true });
subHeadingIndex++;
} else {
anchorLinks[headingIndex].links[subHeadingIndex].links.push({ name: headingValue, key: anchorUrl, url: anchorUrl, links: [], isExpanded: true });
}
depth++;
prevHeadingOrder = headingOrder;
}
}
/* Replace the added header links from the string so they don't get processed again */
HTMLString = HTMLString.replace(`<h${headingOrder}>`, '');
HTMLString = HTMLString.replace(`</h${headingOrder}>`, '');
}
}
});
} catch (error) {
console.log(error);
}
console.log(anchorLinks);
return anchorLinks;
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "bcac4d9d-adf5-4462-97c5-e5f3e97dd518",
"alias": "PageNavigatorWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "PageNavigator" },
"description": { "default": "PageNavigator description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "PageNavigator"
}
}]
}

View File

@ -0,0 +1,70 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import * as strings from 'PageNavigatorWebPartStrings';
import PageNavigator from './components/PageNavigator';
import { IPageNavigatorProps } from './components/IPageNavigatorProps';
import { INavLink } from 'office-ui-fabric-react/lib/Nav';
import { SPService } from '../../Service/SPService';
export interface IPageNavigatorWebPartProps {
description: string;
}
export default class PageNavigatorWebPart extends BaseClientSideWebPart<IPageNavigatorWebPartProps> {
private anchorLinks: INavLink[] = [];
public render(): void {
const element: React.ReactElement<IPageNavigatorProps > = React.createElement(
PageNavigator,
{
description: this.properties.description,
anchorLinks: this.anchorLinks
}
);
ReactDom.render(element, this.domElement);
}
protected onInit(): Promise<void> {
return super.onInit().then(async _ => {
this.anchorLinks = await SPService.GetAnchorLinks(this.context);
});
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,6 @@
import { INavLink } from 'office-ui-fabric-react/lib/Nav';
export interface IPageNavigatorProps {
description: string;
anchorLinks: INavLink[];
}

View File

@ -0,0 +1,6 @@
import { INavLink } from 'office-ui-fabric-react/lib/Nav';
export interface IPageNavigatorState {
anchorLinks: INavLink[];
selectedKey: string;
}

View File

@ -0,0 +1,73 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.pageNavigator {
.container {
margin: 0px auto;
/* box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); */
}
.row {
@include ms-Grid-row;
/* @include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px; */
}
.column {
@include ms-Grid-col;
@include ms-lg12;
/* @include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1; */
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,53 @@
import * as React from 'react';
import styles from './PageNavigator.module.scss';
import { IPageNavigatorProps } from './IPageNavigatorProps';
import { IPageNavigatorState } from './IPageNavigatorState';
import { Nav, INavLink } from 'office-ui-fabric-react/lib/Nav';
export default class PageNavigator extends React.Component<IPageNavigatorProps, IPageNavigatorState> {
constructor(props: IPageNavigatorProps) {
super(props);
this.state = {
anchorLinks: [],
selectedKey: ''
};
this.onLinkClick = this.onLinkClick.bind(this);
}
public componentDidMount() {
this.setState({ anchorLinks: this.props.anchorLinks, selectedKey: this.props.anchorLinks[0] ? this.props.anchorLinks[0].key : '' });
}
public componentDidUpdate(prevProps: IPageNavigatorProps) {
if (JSON.stringify(prevProps.anchorLinks) !== JSON.stringify(this.props.anchorLinks)) {
this.setState({ anchorLinks: this.props.anchorLinks, selectedKey: this.props.anchorLinks[0] ? this.props.anchorLinks[0].key : '' });
}
}
private onLinkClick(ev: React.MouseEvent<HTMLElement>, item?: INavLink) {
this.setState({ selectedKey: item.key });
}
public render(): React.ReactElement<IPageNavigatorProps> {
return (
<div className={styles.pageNavigator}>
<div className={styles.container}>
<div className={styles.row}>
<div className={styles.column}>
<Nav selectedKey={this.state.selectedKey}
onLinkClick={this.onLinkClick}
groups={[
{
links: this.state.anchorLinks
}
]}
/>
</div>
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface IPageNavigatorWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'PageNavigatorWebPartStrings' {
const strings: IPageNavigatorWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,64 @@
/**
* This script updates the package-solution version analogue to the
* the package.json file.
*/
if (process.env.npm_package_version === undefined) {
throw 'Package version cannot be evaluated';
}
// define path to package-solution file
const solution = './config/package-solution.json',
teams = './teams/manifest.json';
// require filesystem instance
const fs = require('fs');
// get next automated package version from process variable
const nextPkgVersion = process.env.npm_package_version;
// make sure next build version match
const nextVersion = nextPkgVersion.indexOf('-') === -1 ?
nextPkgVersion : nextPkgVersion.split('-')[0];
// Update version in SPFx package-solution if exists
if (fs.existsSync(solution)) {
// read package-solution file
const solutionFileContent = fs.readFileSync(solution, 'UTF-8');
// parse file as json
const solutionContents = JSON.parse(solutionFileContent);
// set property of version to next version
solutionContents.solution.version = nextVersion + '.0';
// save file
fs.writeFileSync(
solution,
// convert file back to proper json
JSON.stringify(solutionContents, null, 2),
'UTF-8');
}
// Update version in teams manifest if exists
if (fs.existsSync(teams)) {
// read package-solution file
const teamsManifestContent = fs.readFileSync(teams, 'UTF-8');
// parse file as json
const teamsContent = JSON.parse(teamsManifestContent);
// set property of version to next version
teamsContent.version = nextVersion;
// save file
fs.writeFileSync(
teams,
// convert file back to proper json
JSON.stringify(teamsContent, null, 2),
'UTF-8');
}

View File

@ -0,0 +1,38 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

32
samples/react-spfx-webcam/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": false,
"environment": "spo",
"version": "1.9.1",
"libraryName": "react--spfx-webcam",
"libraryId": "a8075f7d-4517-44af-884c-5f0b348f8e57",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,62 @@
# SPFx Web/Mobile Camera Demo
## Summary
This is sample webpart to showcase how to open webcam and take photo in SPFx webpart. This will work in desktop/laptop with webcam and mobile device also(from browser). This can be extended to stored captured photo in document library or it can be saved as user profile photo using GRAPH API.
* [Please refer this link on How to build this from Scratch](https://www.c-sharpcorner.com/article/how-to-open-webmobile-camera-and-take-photo-from-spfx-webpart/)
![Options Available](screens/3.png?raw=true "Options Available")
![Opening webcam](screens/4.png?raw=true "Opening webcam")
![Taking photo](screens/4.png?raw=true "Taking photo")
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
## Applies to
* [SharePoint Framework](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
* [Office 365 tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
## Prerequisites
> N/A
## Solution
Solution|Author(s)
--------|---------
react-spfx-webcam | Siddharth Vaghasia(@siddh_me)
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|Sept 05, 2019|Upgraded to Latest SPFx Version 1.9.1
1.0.0|Sept 04, 2019|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
* Clone this repository
* in the command line run:
* `npm install`
* `gulp serve`
## Features
This Web Part illustrates the following concepts on top of the SharePoint Framework:
* Using react framework in SPFx webpart
* Using react-webcam npm package in SPFx webpart
* Open web cam or mobile camera to capture photo and display in img html element
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-spfx-webcam" />

View File

@ -0,0 +1,18 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"sp-fx-web-cam-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/spFxWebCam/SpFxWebCamWebPart.js",
"manifest": "./src/webparts/spFxWebCam/SpFxWebCamWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"SpFxWebCamWebPartStrings": "lib/webparts/spFxWebCam/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "react-spfx-webcam",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-spfx-webcam-client-side-solution",
"id": "a8075f7d-4517-44af-884c-5f0b348f8e57",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/react-spfx-webcam.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

7
samples/react-spfx-webcam/gulpfile.js vendored Normal file
View File

@ -0,0 +1,7 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
build.initialize(gulp);

19357
samples/react-spfx-webcam/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
{
"name": "react-spfx-webcam",
"version": "1.0.0",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/rush-stack-compiler-3.2": "^0.3.33",
"@microsoft/sp-core-library": "1.9.1",
"@microsoft/sp-lodash-subset": "1.9.1",
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
"@microsoft/sp-property-pane": "1.9.1",
"@microsoft/sp-webpart-base": "1.9.1",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/react-webcam": "^1.1.0",
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "6.189.2",
"react": "16.7.0",
"react-dom": "16.7.0",
"react-webcam": "^1.1.1"
},
"resolutions": {
"@types/react": "16.8.8"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
"@microsoft/sp-build-web": "1.9.1",
"@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-tslint-rules": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",
"gulp": "~3.9.1",
"webpack": "^4.39.3"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "0c2e8714-8a46-464f-8211-dc47216ae32d",
"alias": "SpFxWebCamWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "SPFxWebCam" },
"description": { "default": "SPFxWebCam description" },
"officeFabricIconFontName": "Camera",
"properties": {
"description": "SPFxWebCam"
}
}]
}

View File

@ -0,0 +1,63 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import * as strings from 'SpFxWebCamWebPartStrings';
import SpFxWebCam from './components/SpFxWebCam';
import { ISpFxWebCamProps } from './components/ISpFxWebCamProps';
export interface ISpFxWebCamWebPartProps {
description: string;
}
export default class SpFxWebCamWebPart extends BaseClientSideWebPart<ISpFxWebCamWebPartProps> {
public render(): void {
const element: React.ReactElement<ISpFxWebCamProps > = React.createElement(
SpFxWebCam,
{
description: this.properties.description
}
);
ReactDom.render(element, this.domElement);
//ReactDom.render(element2, document.getElementById("mywebcam"));
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,7 @@
export interface ISpFxWebCamProps {
description: string;
}

View File

@ -0,0 +1,75 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.spFxWebCam {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
margin-right: 10px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,79 @@
import * as React from 'react';
import styles from './SpFxWebCam.module.scss';
import { ISpFxWebCamProps } from './ISpFxWebCamProps';
import { escape } from '@microsoft/sp-lodash-subset';
import * as Webcam from "react-webcam";
import * as ReactDom from 'react-dom';
import { Button } from 'office-ui-fabric-react/lib/Button';
export default class SpFxWebCam extends React.Component<ISpFxWebCamProps,{imageData: string, image_name:string,webcam:Webcam,stream:any}> {
private _camContainer: HTMLElement = undefined;
private _capturedPhoto: HTMLElement = undefined;
public render(): React.ReactElement<ISpFxWebCamProps> {
return (
<div>
<div className={ styles.spFxWebCam }>
<div className={ styles.container }>
<div className={ styles.row }>
<div className={ styles.column }>
<span className={ styles.title }>SPFx Web/Mobile Camera Demo </span>
<p className={ styles.subTitle }>This is demo of how to open webcam and take photo from SPFx webpart.
It will open camera in mobile web browser also</p>
<a onClick={() => this.opencam()} className={ styles.button }>
<span className={ styles.label }>Open webcam</span>
</a>
<a onClick={() => this.capture()} className={ styles.button }>
<span className={ styles.label }>Take Photo</span>
</a>
<a onClick={() => this.close()} className={ styles.button }>
<span className={ styles.label }>Close webcam</span>
</a>
</div>
</div>
</div>
</div>
<div id="camContainer" ref={(elm) => { this._camContainer = elm; }}>
</div>
<div id="capturedPhoto" ref={(elm) => { this._capturedPhoto = elm; }}>
</div>
</div>
);
}
private setRef = (webcam) => {
this.setState({webcam:webcam})
}
public close(){
ReactDom.unmountComponentAtNode(this._camContainer);
}
private capture(){
const imageSrc = this.state.webcam.getScreenshot();
const element = React.createElement(
'img',
{
src:imageSrc
}
);
ReactDom.render(element, this._capturedPhoto);
}
private opencam () {
const element2: React.ReactElement<Webcam.WebcamProps > = React.createElement(
Webcam,
{
height:350,
width:350,
screenshotFormat:"image/jpeg",
ref:this.setRef,
}
);
//const camContainer = document.getElementById("camContainer")
ReactDom.render(element2, this._camContainer);
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface ISpFxWebCamWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'SpFxWebCamWebPartStrings' {
const strings: ISpFxWebCamWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,39 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"allowSyntheticDefaultImports": true,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}