|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"isCreatingSolution": false,
|
||||
"environment": "spo",
|
||||
"version": "1.7.0",
|
||||
"libraryName": "react-bot-framework",
|
||||
|
|
|
@ -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" />
|
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 105 KiB |
After Width: | Height: | Size: 90 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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 "";
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
10
samples/react-bot-framework/src/webparts/botFrameworkChatv4/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare interface IBotFrameworkChatv4WebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'BotFrameworkChatv4WebPartStrings' {
|
||||
const strings: IBotFrameworkChatv4WebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 933 B |
After Width: | Height: | Size: 2.5 KiB |
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -29,4 +29,5 @@ export interface IEventState {
|
|||
showRecurrenceSeriesInfo:boolean;
|
||||
newRecurrenceEvent:boolean;
|
||||
recurrenceAction:string;
|
||||
recurrenceDescription?:string;
|
||||
}
|
||||
|
|
|
@ -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' }}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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' {
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
});
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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" />
|
After Width: | Height: | Size: 3.9 MiB |
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -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 -->"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"preset": "@voitanos/jest-preset-spfx-react16",
|
||||
"rootDir": "../src"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -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);
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -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"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { INavLink } from 'office-ui-fabric-react/lib/Nav';
|
||||
|
||||
export interface IPageNavigatorProps {
|
||||
description: string;
|
||||
anchorLinks: INavLink[];
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { INavLink } from 'office-ui-fabric-react/lib/Nav';
|
||||
|
||||
export interface IPageNavigatorState {
|
||||
anchorLinks: INavLink[];
|
||||
selectedKey: string;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface IPageNavigatorWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'PageNavigatorWebPartStrings' {
|
||||
const strings: IPageNavigatorWebPartStrings;
|
||||
export = strings;
|
||||
}
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -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');
|
||||
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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" />
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -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 -->"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -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);
|
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 330 KiB |
After Width: | Height: | Size: 353 KiB |
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -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"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
|
||||
export interface ISpFxWebCamProps {
|
||||
description: string;
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface ISpFxWebCamWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'SpFxWebCamWebPartStrings' {
|
||||
const strings: ISpFxWebCamWebPartStrings;
|
||||
export = strings;
|
||||
}
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|