SPFX Tour sample WebPart - react-tour-pnpjs (#1082)
* Initial commit * first commit * first release * rework * load fix and styling * fix webpart select * css ReactTourJS fix * css fix * added assets * Update README.md * fix webPartData undefined on Text Webpart
This commit is contained in:
parent
225ecb3a16
commit
2d9b61fc4d
|
@ -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,5 @@
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"msjsdiag.debugger-for-chrome"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Install Chrome Debugger Extension for Visual Studio Code to debug your components with the
|
||||||
|
* Chrome browser: https://aka.ms/spfx-debugger-extensions
|
||||||
|
*/
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [{
|
||||||
|
"name": "Local workbench",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "https://localhost:4321/temp/workbench.html",
|
||||||
|
"webRoot": "${workspaceRoot}",
|
||||||
|
"sourceMaps": true,
|
||||||
|
"sourceMapPathOverrides": {
|
||||||
|
"webpack:///.././src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||||
|
},
|
||||||
|
"runtimeArgs": [
|
||||||
|
"--remote-debugging-port=9222"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hosted workbench",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "https://enter-your-SharePoint-site/_layouts/workbench.aspx",
|
||||||
|
"webRoot": "${workspaceRoot}",
|
||||||
|
"sourceMaps": true,
|
||||||
|
"sourceMapPathOverrides": {
|
||||||
|
"webpack:///.././src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||||
|
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||||
|
},
|
||||||
|
"runtimeArgs": [
|
||||||
|
"--remote-debugging-port=9222",
|
||||||
|
"-incognito"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Place your settings in this file to overwrite default and user settings.
|
||||||
|
{
|
||||||
|
// Configure glob patterns for excluding files and folders in the file explorer.
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.DS_Store": true,
|
||||||
|
"**/bower_components": true,
|
||||||
|
"**/coverage": true,
|
||||||
|
"**/lib-amd": true,
|
||||||
|
"src/**/*.scss.ts": true
|
||||||
|
},
|
||||||
|
"typescript.tsdk": ".\\node_modules\\typescript\\lib"
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"@microsoft/generator-sharepoint": {
|
||||||
|
"isCreatingSolution": true,
|
||||||
|
"environment": "spo",
|
||||||
|
"version": "1.9.1",
|
||||||
|
"libraryName": "react-tour-pnpjs",
|
||||||
|
"libraryId": "9060fda4-14af-4588-aa3a-b746b46420d8",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"isDomainIsolated": false,
|
||||||
|
"componentType": "webpart"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
# react-tour-pnpjs - SharePoint modern page tutorial: SPFX Tour sample WebPart.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
A SPFx WebPart using [PnP/PnPjs](https://pnp.github.io/pnpjs/), [@pnp/spfx-property-controls](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldCollectionData/) and [ReactTourJS](https://reactour.js.org/).
|
||||||
|
It allows to create a configurable tutorial/tour of a SharePoint modern page for adoption scope.
|
||||||
|
When you start the tour, a modal will be displayed, with a description of the highlighted area, and you can go to the next step or go back, thus navigating inside the page. The user will see the descriptions and will have the opportunity to preview the advice that the publisher thought for him.
|
||||||
|
The property pane shows dinamically all webparts in the current page, using [PnP/PnPjs](https://pnp.github.io/pnpjs/) to make easy page tour configuration.
|
||||||
|
|
||||||
|
## react-tour-pnpjs in action
|
||||||
|
![WebPartInAction](./assets/react-tour-pnpjs-webpart-animated.gif)
|
||||||
|
|
||||||
|
## react-tour-pnpjs tour configuration property
|
||||||
|
![WebPartInAction](./assets/react-tour-pnpjs-webpart-animated-details.png)
|
||||||
|
|
||||||
|
## react-tour-pnpjs configurations
|
||||||
|
![WebPartInAction](./assets/react-tour-pnpjs-webpart-configuration.gif)
|
||||||
|
|
||||||
|
|
||||||
|
## Used SharePoint Framework Version
|
||||||
|
|
||||||
|
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
|
||||||
|
|
||||||
|
## Applies to
|
||||||
|
|
||||||
|
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||||
|
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Solution|Author(s)
|
||||||
|
--------|---------
|
||||||
|
react-tour-pnpjs | [Federico Porceddu](https://www.federicoporceddu.com)
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
Version|Date|Comments
|
||||||
|
-------|----|--------
|
||||||
|
1.0|November 23, 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:
|
||||||
|
* restore dependencies: `npm install`
|
||||||
|
* build solution `gulp build --ship`
|
||||||
|
* bundle solution: `gulp bundle --ship`
|
||||||
|
* package solution: `gulp package-solution --ship`
|
||||||
|
* locate solution at `.\sharepoint\solution\react-tour-pnpjs.sppkg`
|
||||||
|
* upload it to your tenant app catalog
|
||||||
|
* add `react-tour-pnpjs` app to your site
|
||||||
|
* add `react-tour-pnpjs` webpart to your page to see it in action
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||||
|
|
||||||
|
* How to use [@pnp/spfx-property-controls - PropertyFieldCollectionData](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldCollectionData/).
|
||||||
|
* How to extend with custom render [@pnp/spfx-property-controls - PropertyFieldCollectionData](https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldCollectionData/).
|
||||||
|
* How to retrieve all SPFx WebPart in the current page using [PnP/PnPjs](https://pnp.github.io/pnpjs/)
|
||||||
|
* How to include external React Component [ReactTourJS](https://reactour.js.org/)
|
||||||
|
|
||||||
|
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/react-tour-pnpjs" />
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 MiB |
Binary file not shown.
After Width: | Height: | Size: 3.8 MiB |
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||||
|
"version": "2.0",
|
||||||
|
"bundles": {
|
||||||
|
"tour-web-part": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"entrypoint": "./lib/webparts/tour/TourWebPart.js",
|
||||||
|
"manifest": "./src/webparts/tour/TourWebPart.manifest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externals": {},
|
||||||
|
"localizedResources": {
|
||||||
|
"TourWebPartStrings": "lib/webparts/tour/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": "react-tour-pnpjs",
|
||||||
|
"accessKey": "<!-- ACCESS KEY -->"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||||
|
"solution": {
|
||||||
|
"name": "react-tour-pnpjs-client-side-solution",
|
||||||
|
"id": "9060fda4-14af-4588-aa3a-b746b46420d8",
|
||||||
|
"version": "1.0.1.9",
|
||||||
|
"includeClientSideAssets": true,
|
||||||
|
"isDomainIsolated": false
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"zippedPackage": "solution/react-tour-pnpjs.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);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"name": "react-tour-pnpjs",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp bundle",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"test": "gulp test"
|
||||||
|
},
|
||||||
|
"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/common": "^1.3.7",
|
||||||
|
"@pnp/graph": "^1.3.7",
|
||||||
|
"@pnp/logging": "^1.3.7",
|
||||||
|
"@pnp/odata": "^1.3.7",
|
||||||
|
"@pnp/sp": "^1.3.7",
|
||||||
|
"@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",
|
||||||
|
"body-scroll-lock": "^2.6.4",
|
||||||
|
"office-ui-fabric-react": "^6.189.2",
|
||||||
|
"react": "16.8.5",
|
||||||
|
"react-dom": "16.8.5",
|
||||||
|
"reactour": "^1.15.5",
|
||||||
|
"styled-components": "^4.4.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",
|
||||||
|
"@types/reactour": "^1.13.1",
|
||||||
|
"ajv": "~5.2.2",
|
||||||
|
"gulp": "~3.9.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||||
|
"id": "ca659f89-c656-43a6-a0e8-d0bdba90123f",
|
||||||
|
"alias": "TourWebPart",
|
||||||
|
"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": "Tour - Tutorial WebPart" },
|
||||||
|
"description": { "default": "Start page tutorial to discover all the features" },
|
||||||
|
"officeFabricIconFontName": "HintText",
|
||||||
|
"properties": {
|
||||||
|
"actionValue": "Start page tour",
|
||||||
|
"description": "Start page tutorial to discover all the features."
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
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 'TourWebPartStrings';
|
||||||
|
import Tour from './components/Tour';
|
||||||
|
import { ITourProps } from './components/ITourProps';
|
||||||
|
import { PropertyFieldCollectionData, CustomCollectionFieldType } from '@pnp/spfx-property-controls/lib/PropertyFieldCollectionData';
|
||||||
|
import { sp, ClientSidePage, ClientSideWebpart, IClientControlEmphasis } from '@pnp/sp';
|
||||||
|
|
||||||
|
|
||||||
|
export interface ITourWebPartProps {
|
||||||
|
actionValue: string;
|
||||||
|
description: string;
|
||||||
|
collectionData: any[];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default class TourWebPart extends BaseClientSideWebPart<ITourWebPartProps> {
|
||||||
|
|
||||||
|
private loadIndicator: boolean = true;
|
||||||
|
private webpartList: any[] = new Array<any[]>();
|
||||||
|
|
||||||
|
public onInit(): Promise<void> {
|
||||||
|
|
||||||
|
return super.onInit().then(_ => {
|
||||||
|
sp.setup({
|
||||||
|
spfxContext: this.context
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
const element: React.ReactElement<ITourProps> = React.createElement(
|
||||||
|
Tour,
|
||||||
|
{
|
||||||
|
actionValue: this.properties.actionValue,
|
||||||
|
description: this.properties.description,
|
||||||
|
collectionData: this.properties.collectionData,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ReactDom.render(element, this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onDispose(): void {
|
||||||
|
ReactDom.unmountComponentAtNode(this.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get dataVersion(): Version {
|
||||||
|
return Version.parse('1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async GetAllWebpart(): Promise<any[]> {
|
||||||
|
// page file
|
||||||
|
const file = sp.web.getFileByServerRelativePath(this.context.pageContext.site.serverRequestPath);
|
||||||
|
|
||||||
|
const page = await ClientSidePage.fromFile(file);
|
||||||
|
|
||||||
|
const wpData: any[] = [];
|
||||||
|
|
||||||
|
page.sections.forEach(section => {
|
||||||
|
section.columns.forEach(column => {
|
||||||
|
column.controls.forEach(control => {
|
||||||
|
var wpName = {}
|
||||||
|
var wp = {};
|
||||||
|
if (control.data.webPartData != undefined) {
|
||||||
|
wpName = `sec[${section.order}] col[${column.order}] - ${control.data.webPartData.title}`;
|
||||||
|
wp = { text: wpName, key: control.data.webPartData.instanceId };
|
||||||
|
wpData.push(wp);
|
||||||
|
} else {
|
||||||
|
wpName = `sec[${section.order}] col[${column.order}] - "Webpart"`;
|
||||||
|
wp = { text: wpName, key: control.data.id };
|
||||||
|
}
|
||||||
|
wpData.push(wp);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return wpData;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onPropertyPaneConfigurationStart(): void {
|
||||||
|
var self = this;
|
||||||
|
this.GetAllWebpart().then(res => {
|
||||||
|
self.webpartList = res;
|
||||||
|
self.loadIndicator = false;
|
||||||
|
self.context.propertyPane.refresh();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||||
|
return {
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
header: {
|
||||||
|
description: strings.PropertyPaneDescription
|
||||||
|
},
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
groupName: strings.BasicGroupName,
|
||||||
|
groupFields: [
|
||||||
|
PropertyPaneTextField('actionValue', {
|
||||||
|
label: strings.ActionValueFieldLabel
|
||||||
|
}),
|
||||||
|
PropertyPaneTextField('description', {
|
||||||
|
label: strings.DescriptionFieldLabel
|
||||||
|
}),
|
||||||
|
PropertyFieldCollectionData("collectionData", {
|
||||||
|
key: "collectionData",
|
||||||
|
label: "Tour steps",
|
||||||
|
panelHeader: "Collection data panel header",
|
||||||
|
manageBtnLabel: "Configure tour steps",
|
||||||
|
value: this.properties.collectionData,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
id: "WebPart",
|
||||||
|
title: "section[x] column[y] - WebPart Title",
|
||||||
|
type: CustomCollectionFieldType.dropdown,
|
||||||
|
options: this.webpartList,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "StepDescription",
|
||||||
|
title: "Step Description",
|
||||||
|
type: CustomCollectionFieldType.custom,
|
||||||
|
onCustomRender: (field, value, onUpdate, item, itemId) => {
|
||||||
|
return (
|
||||||
|
React.createElement("div", null,
|
||||||
|
React.createElement("textarea",
|
||||||
|
{
|
||||||
|
style: { width: "600px", height: "100px" },
|
||||||
|
placeholder: "Step description",
|
||||||
|
key: itemId,
|
||||||
|
value: value,
|
||||||
|
onChange: (event: React.FormEvent<HTMLTextAreaElement>) => {
|
||||||
|
console.log(event);
|
||||||
|
onUpdate(field.id, event.currentTarget.value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Position",
|
||||||
|
title: "Position",
|
||||||
|
type: CustomCollectionFieldType.number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Enabled",
|
||||||
|
title: "Enabled",
|
||||||
|
type: CustomCollectionFieldType.boolean,
|
||||||
|
defaultValue: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
disabled: false
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
loadingIndicatorDelayTime: 5,
|
||||||
|
showLoadingIndicator: this.loadIndicator
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export interface ITourProps {
|
||||||
|
description: string;
|
||||||
|
actionValue: string;
|
||||||
|
collectionData: any[];
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||||
|
|
||||||
|
.reactTourCustomCss {
|
||||||
|
--reactour-accent: #5cb7b7 !important;
|
||||||
|
line-height: 1.3 !important;
|
||||||
|
color: #2d2323 !important;
|
||||||
|
font-family: -apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
'Segoe UI',
|
||||||
|
'Roboto',
|
||||||
|
'Oxygen',
|
||||||
|
'Ubuntu',
|
||||||
|
'Cantarell',
|
||||||
|
'Fira Sans',
|
||||||
|
'Droid Sans',
|
||||||
|
'Helvetical Neue',
|
||||||
|
sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tour {
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tutorialButton{
|
||||||
|
width: 100%;
|
||||||
|
max-width: initial!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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,81 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import styles from './Tour.module.scss';
|
||||||
|
import { ITourProps } from './ITourProps';
|
||||||
|
import Tours from 'reactour';
|
||||||
|
import { CompoundButton } from 'office-ui-fabric-react';
|
||||||
|
import { TourHelper } from './TourHelper';
|
||||||
|
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface ITourState {
|
||||||
|
isTourOpen: boolean;
|
||||||
|
steps: any[];
|
||||||
|
tourDisabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Tour extends React.Component<ITourProps, ITourState> {
|
||||||
|
|
||||||
|
constructor(props: ITourProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isTourOpen: false,
|
||||||
|
steps: [],
|
||||||
|
tourDisabled: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
this.setState({ steps: TourHelper.getTourSteps(this.props.collectionData) });
|
||||||
|
if (this.props.collectionData != undefined && this.props.collectionData.length > 0) {
|
||||||
|
this.setState({ tourDisabled: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate(newProps) {
|
||||||
|
if (JSON.stringify(this.props.collectionData) != JSON.stringify(newProps.collectionData)) {
|
||||||
|
this.setState({ steps: TourHelper.getTourSteps(this.props.collectionData) });
|
||||||
|
if (this.props.collectionData != undefined && this.props.collectionData.length > 0) {
|
||||||
|
this.setState({ tourDisabled: false });
|
||||||
|
} else {
|
||||||
|
this.setState({ tourDisabled: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public render(): React.ReactElement<ITourState> {
|
||||||
|
return (
|
||||||
|
<div className={styles.tour}>
|
||||||
|
<CompoundButton primary text={this.props.actionValue} secondaryText={this.props.description}
|
||||||
|
disabled={this.state.tourDisabled} onClick={this._openTour} checked={this.state.isTourOpen}
|
||||||
|
className={styles.tutorialButton}>
|
||||||
|
|
||||||
|
</CompoundButton>
|
||||||
|
<Tours
|
||||||
|
onRequestClose={this._closeTour}
|
||||||
|
startAt={0}
|
||||||
|
steps={this.state.steps}
|
||||||
|
isOpen={this.state.isTourOpen}
|
||||||
|
maskClassName="mask"
|
||||||
|
className={styles.reactTourCustomCss}
|
||||||
|
accentColor={"#5cb7b7"}
|
||||||
|
rounded={5}
|
||||||
|
onAfterOpen={this._disableBody}
|
||||||
|
onBeforeClose={this._enableBody}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _disableBody = target => disableBodyScroll(target);
|
||||||
|
private _enableBody = target => enableBodyScroll(target);
|
||||||
|
|
||||||
|
private _closeTour = () => {
|
||||||
|
this.setState({ isTourOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _openTour = () => {
|
||||||
|
this.setState({ isTourOpen: true });
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import {
|
||||||
|
sp
|
||||||
|
} from "@pnp/sp";
|
||||||
|
import { graph } from "@pnp/graph";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class TourHelper {
|
||||||
|
|
||||||
|
public static getTourSteps(settings: any[]): any[] {
|
||||||
|
|
||||||
|
var result: any[] = new Array<any>();
|
||||||
|
|
||||||
|
if (settings != undefined) {
|
||||||
|
settings.forEach(ele => {
|
||||||
|
if (ele.Enabled) {
|
||||||
|
result.push(
|
||||||
|
{
|
||||||
|
selector: '[data-sp-feature-instance-id=\'' + ele.WebPart + '\']',
|
||||||
|
content: ele.StepDescription
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
define([], function() {
|
||||||
|
return {
|
||||||
|
"PropertyPaneDescription": "Configure the tutorial to explain your users all features of the page",
|
||||||
|
"BasicGroupName": "Group Name",
|
||||||
|
"DescriptionFieldLabel": "Tour Description",
|
||||||
|
"ActionValueFieldLabel":"Button Label",
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,12 @@
|
||||||
|
declare interface ITourWebPartStrings {
|
||||||
|
PropertyPaneDescription: string;
|
||||||
|
BasicGroupName: string;
|
||||||
|
DescriptionFieldLabel: string;
|
||||||
|
ActionValueFieldLabel: string;
|
||||||
|
PropertyPaneActionValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'TourWebPartStrings' {
|
||||||
|
const strings: ITourWebPartStrings;
|
||||||
|
export = strings;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"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,
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue