react site designs manager (#829)

* commit first

* Update README.md

* Update README.md
This commit is contained in:
joaojmendes 2019-04-08 10:40:23 +01:00 committed by Vesa Juvonen
parent 553ecbfeeb
commit 4e1a07f944
102 changed files with 25528 additions and 0 deletions

View File

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

View File

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

View File

@ -0,0 +1,29 @@
{
"@pnp/generator-spfx": {
"framework": "react",
"pnpFramework": "reactjs.plus",
"pnp-libraries": [
"jquery@3",
"@pnp/pnpjs",
"@pnp/spfx-property-controls",
"@pnp/spfx-controls-react"
],
"pnp-ci": [
"azure"
],
"pnp-vetting": [],
"spfxenv": "spo"
},
"@microsoft/generator-sharepoint": {
"environment": "spo",
"framework": "react",
"plusBeta": true,
"isCreatingSolution": true,
"version": "1.8.0",
"libraryName": "react-manage-sitedesigns",
"libraryId": "50025bb6-5fda-4a16-b45e-5f27b19aac72",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,140 @@
# React Site Designs Manager
## Summary
This web part allows tenant administrators to manage site designs through a graphical interface.
We can create, edit, delete work whith site scripts associated to a site design, manage permissions and apply site design to one or more sites.
Only users with Tenant Admin Role are allowed to managed tenant properties.
## Site Designs List
![site design list](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen1.jpg)
## Add, Edit and Delete site designs
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen2.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen3.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen3.1.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen4.jpg)
## Site Designs Rights
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen5.jpg)
![tenant properties](/assets/screen6.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen7.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen8.jpg)
## Site Design Site Scripts
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen9.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen10.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen11.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen12.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen13.jpg)
## Apply Site Design
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen14.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen15.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen16.jpg)
![tenant properties](https://github.com/joaojmendes/sp-dev-fx-webparts/blob/dev/samples/react-manage-sitedesigns/assets/screen17.jpg)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-GA-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)
> Update accordingly as needed.
## WebPart Properties
Property |Type|Required| comments
--------------------|----|--------|----------
WebPart Title| Text| no|
## Solution
The Web Part Use PnPjs library, Office-ui-fabric-react components.
Solution|Author(s)
--------|---------
Site Design Manager WebPart|João Mendes
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|April 08, 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 build`
- `gulp bundle --ship`
- `gulp package-solution --ship`
- `Add to AppCatalog and deploy`
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -0,0 +1,71 @@
resources:
- repo: self
trigger:
- master
- develop
queue:
name: Hosted VS2017
demands:
- npm
- node.js
steps:
#install node 8.x
- task: NodeTool@0
displayName: 'Use Node 8.x'
inputs:
versionSpec: 8.x
checkLatest: true
#install nodejs modules with npm
- task: Npm@1
displayName: 'npm install'
inputs:
workingDir: '$(Build.SourcesDirectory)'
verbose: false
#start unit tests
- task: Gulp@0
displayName: 'gulp test'
inputs:
gulpFile: '$(Build.SourcesDirectory)/gulpfile.js'
targets: test
publishJUnitResults: true
testResultsFiles: '**/test-*.xml'
#publish test results
- task: PublishCodeCoverageResults@1
displayName: 'Publish Code Coverage Results $(Build.SourcesDirectory)/temp/coverage/cobertura/cobertura.xml'
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: '$(Build.SourcesDirectory)/temp/coverage/cobertura/cobertura.xml'
reportDirectory: '$(Build.SourcesDirectory)/temp/coverage/cobertura'
#bundle code with gulp
- task: Gulp@0
displayName: 'gulp bundle'
inputs:
gulpFile: '$(Build.SourcesDirectory)/gulpfile.js'
targets: bundle
arguments: '--ship'
continueOnError: true
#package solution with gulp
- task: Gulp@0
displayName: 'gulp package-solution'
inputs:
gulpFile: '$(Build.SourcesDirectory)/gulpfile.js'
targets: 'package-solution'
arguments: '--ship'
#copy files to artifact repository
- task: CopyFiles@2
displayName: 'Copy Files to: $(build.artifactstagingdirectory)/drop'
inputs:
Contents: '**\*.sppkg'
TargetFolder: '$(build.artifactstagingdirectory)/drop'
#publish artifacts
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: drop'
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)/drop'

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,30 @@
"use strict";
var existingKarmaConfig = require('@microsoft/sp-build-web/lib/karma/karma.config');
var junitReporter = require('karma-junit-reporter');
module.exports = function (config) {
existingKarmaConfig(config);
config.reporters.push('junit');
config.set({
basePath: './..',
});
config.junitReporter = {
outputDir: 'temp/', // results will be saved as $outputDir/$browserName.xml
outputFile: 'test-results.xml', // if included, results will be saved as $outputDir/$browserName/$outputFile
suite: 'karma', // suite will become the package name attribute in xml testsuite element
useBrowserName: true, // add browser name to report and classes names
};
var coberturaSubDir = 'cobertura';
var coverageSubDir = 'lcov';
var coberturaFileName = 'cobertura.xml';
config.coverageReporter.reporters.push({type: 'cobertura', subdir: './' + coberturaSubDir, file: coberturaFileName});
config.coverageReporter.reporters.push({
type: 'lcov',
subdir: './' + coverageSubDir + '/',
file: 'lcov.info'
});
config.browserNoActivityTimeout = 60000;
config.plugins.push(junitReporter);
};

View File

@ -0,0 +1,14 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-manage-sitedesigns-client-side-solution",
"id": "50025bb6-5fda-4a16-b45e-5f27b19aac72",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/react-manage-sitedesigns.sppkg"
}
}

View File

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

View File

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

View File

@ -0,0 +1,43 @@
'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);
/**
* Continuous Integration
*/
const buildConfig = build.getConfig();
const karmaTaskCandidates = buildConfig.uniqueTasks.filter(task => task.name === 'karma');
if (karmaTaskCandidates && karmaTaskCandidates.length > 0) {
const karmaTask = karmaTaskCandidates[0];
karmaTask.taskConfig.configPath = './config/karma.config.js';
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
{
"name": "react-manage-sitedesigns",
"version": "0.0.1",
"private": true,
"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.8.0-plusbeta",
"@microsoft/sp-lodash-subset": "1.8.0-plusbeta",
"@microsoft/sp-office-ui-fabric-core": "1.8.0-plusbeta",
"@microsoft/sp-property-pane": "1.8.0-plusbeta",
"@microsoft/sp-webpart-base": "1.8.0-plusbeta",
"@pnp/pnpjs": "^1.3.0",
"@pnp/spfx-controls-react": "1.12.0",
"@pnp/spfx-property-controls": "1.14.1",
"@types/es6-promise": "0.0.33",
"@types/jquery": "^3.3.29",
"@types/react": "16.7.22",
"@types/react-dom": "16.0.5",
"@types/webpack-env": "1.13.1",
"jquery": "^3.3.1",
"jsoneditor": "^5.31.1",
"jsoneditor-react": "^1.0.0",
"react": "16.7.0",
"react-dom": "16.7.0"
},
"resolutions": {
"@types/react": "16.7.22"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-2.7": "0.4.0",
"@microsoft/rush-stack-compiler-3.2": "0.2.11",
"@microsoft/sp-build-web": "1.8.0-plusbeta",
"@microsoft/sp-module-interfaces": "1.8.0-plusbeta",
"@microsoft/sp-tslint-rules": "1.8.0-plusbeta",
"@microsoft/sp-webpart-workbench": "1.8.0-plusbeta",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"@voitanos/jest-preset-spfx-react16": "^1.1.0",
"ajv": "^5.5.2",
"gulp": "~3.9.1",
"gulp-sequence": "1.0.0",
"jest": "^23.6.0",
"karma-junit-reporter": "^1.2.0",
"tslint": "5.14.0",
"typescript": "^3.2.4"
}
}

View File

@ -0,0 +1,181 @@
// João Mendes
import * as React from 'react';
import styles from '../../webparts/siteDesigns/components/SiteDesigns.module.scss';
import { IAddPrincipalProps } from './IAddPrincipalProps';
import { escape } from '@microsoft/sp-lodash-subset';
import spservice from '../../services/spservices';
import * as strings from 'SiteDesignsWebPartStrings';
import { IAddPrincipalState } from './IAddPrincipalState';
import { SiteScriptInfo, SiteScriptUpdateInfo, SiteDesignInfo, SiteDesignPrincipals } from '@pnp/sp';
import {
Panel,
PanelType,
TextField,
Toggle,
IPersonaProps,
DialogFooter,
PrimaryButton, DefaultButton,
Spinner, SpinnerSize,
MessageBar, MessageBarType
} from 'office-ui-fabric-react';
import { PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker";
export default class AddPrincipal extends React.Component<IAddPrincipalProps, IAddPrincipalState> {
private spService: spservice;
private selectedUsers: IPersonaProps[] = [];
public constructor(props) {
super(props);
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this.onCancel = this.onCancel.bind(this);
this.getPeoplePickerItems = this.getPeoplePickerItems.bind(this);
this.onSave = this.onSave.bind(this);
this.state = ({
isLoading: false,
readOnly: true,
showPanel: false,
siteDesignInfo: this.props.siteDesignInfo,
showError: false,
errorMessage: '',
disableSaveButton: true,
saving: false,
});
}
/**
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberofEditSiteDesign
*/
private onCancel(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
this.props.onDismiss();
}
/**
* Save SiteDesign Event
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof EditSiteDesign
*/
private async onSave(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
const principals: string[] = [];
for (const user of this.selectedUsers){
principals.push(user.secondaryText);
}
// read SiteScript
try {
this.setState({ saving: true, disableSaveButton: true });
const result = await this.spService.grantSiteDesignRights(this.props.siteDesignInfo.Id, principals);
this.props.onDismiss(true);
} catch (error) {
console.log(error.message);
this.setState({ saving: false, disableSaveButton: true, showError: true, errorMessage: error.message });
}
}
private getPeoplePickerItems(items: any[]) {
this.selectedUsers = items;
this.setState({ showError:false, errorMessage:'', disableSaveButton: this.selectedUsers.length > 0 ? false : true });
}
// Component Did Mount
/**
*
* @memberof EditSiteDesign
*/
public async componentDidMount() {
//
}
/**
* On Render
*
* @returns {React.ReactElement<IAddSiteDesignProps>}
* @memberof EditSiteDesign
*/
public render(): React.ReactElement<IAddPrincipalProps> {
return (
<div className={styles.siteDesigns}>
<Panel isOpen={this.props.showPanel}
onDismiss={this.onCancel}
type={PanelType.custom}
customWidth={"480px"}
headerText="Add Principals">
<TextField
label={strings.SiteDesignIdLabel}
readOnly={this.state.readOnly}
value={this.state.siteDesignInfo ? this.state.siteDesignInfo.Id : ''}
style={{ backgroundColor: "#f8f8f8" }}
/>
<TextField
label={strings.AddSiteDesignTitleLabel}
readOnly={this.state.readOnly}
value={this.state.siteDesignInfo ? this.state.siteDesignInfo.Title : ''}
style={{ backgroundColor: "#f8f8f8" }}
/>
<TextField
label={strings.AddSiteDesignDescriptionLabel}
readOnly={this.state.readOnly}
value={this.state.siteDesignInfo ? this.state.siteDesignInfo.Description : ''}
multiline
style={{ backgroundColor: "#f8f8f8" }}
/>
<Toggle
defaultChecked={this.props.siteDesignInfo.IsDefault}
label={strings.AddSiteDesignIsDefaultLabel}
onText="On"
offText="Off"
disabled
/>
<br />
<PeoplePicker
context={this.props.context}
titleText="Add Users"
personSelectionLimit={3}
groupName={""} // Leave this blank in case you want to filter from all users
showtooltip={true}
isRequired={true}
selectedItems={this.getPeoplePickerItems}
showHiddenInUI={false}
principalTypes={[PrincipalType.User]}
resolveDelay={1000} />
<br />
<DialogFooter>
{
this.state.saving &&
<div style={{ display: "inline-block", marginRight: '10px', verticalAlign: 'middle' }}>
<Spinner size={SpinnerSize.small} ariaLive="assertive" />
</div>
}
<PrimaryButton onClick={this.onSave} text={strings.AddPrincipalPanelButtonSaveText} disabled={this.state.disableSaveButton} />
<DefaultButton onClick={this.onCancel} text={strings.AddPrincipalPanelButtonCancelText} />
</DialogFooter>
{
this.state.showError &&
<div style={{ marginTop: '15px' }}>
<MessageBar messageBarType={MessageBarType.error} >
<span>{this.state.errorMessage}</span>
</MessageBar>
</div>
}
</Panel>
</div>
);
}
}

View File

@ -0,0 +1,14 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
import { SiteDesignPrincipals } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { IListViewItems } from '../../webparts/siteDesigns/components/IListViewItems';
export interface IAddPrincipalProps {
context: WebPartContext;
onDismiss(refresh?: boolean): void;
showPanel: boolean;
siteDesignInfo: IListViewItems;
}

View File

@ -0,0 +1,16 @@
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { IListViewItems } from '../../webparts/siteDesigns/components/IListViewItems';
import { SiteDesignInfo, SiteScriptInfo } from '@pnp/sp';
export interface IAddPrincipalState {
isLoading: boolean;
siteDesignInfo?: IListViewItems;
showPanel: boolean;
showError: boolean;
errorMessage: string;
readOnly: boolean;
disableSaveButton: boolean;
saving: boolean;
}

View File

@ -0,0 +1,392 @@
// João Mendes
// Mar 2019
//
import * as React from 'react';
import styles from '../../webparts/siteDesigns/components/SiteDesigns.module.scss';
import { IAddSiteDesignProps } from './IAddSiteDesignProps';
import { escape } from '@microsoft/sp-lodash-subset';
import spservice from '../../services/spservices';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import * as strings from 'SiteDesignsWebPartStrings';
import { IAddSiteDesignState } from './IAddSiteDesignState';
import { SiteScriptInfo, SiteScriptUpdateInfo, SiteDesignCreationInfo } from '@pnp/sp';
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Toggle } from 'office-ui-fabric-react/lib/Toggle';
import { IImageProps, Image, ImageFit } from 'office-ui-fabric-react/lib/Image';
import { ISiteScript } from '../../types/ISiteScript';
import { Dropdown, IDropdown, DropdownMenuItemType, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import { getGUID } from '@pnp/common';
export default class AddSiteDesign extends React.Component<IAddSiteDesignProps, IAddSiteDesignState> {
private spService: spservice;
private siteScripts: SiteScriptInfo[];
private AddScriptDialog = React.lazy(() => import('../../controls/AddSiteScript/AddSiteScript' /* webpackChunkName: "addscriptdialog" */));
public constructor(props) {
super(props);
// Initialize state
this.state = ({
isLoading: false,
readOnly: false,
showPanel: false,
siteDesignCreationInfo: { Description: '', Title: '', IsDefault: false, PreviewImageAltText: '', PreviewImageUrl: '', SiteScriptIds: [], WebTemplate: '64' },
panelMode: panelMode.New,
showError: false,
errorMessage: '',
disableSaveButton: true,
sitescriptslist: [],
selectedItems: [],
showPanelAddScript: false,
saving: false,
selectedItemWebTemplate: 64
});
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this.onIsDefault = this.onIsDefault.bind(this);
this.onGetErrorMessageDescription = this.onGetErrorMessageDescription.bind(this);
this.onGetErrorMessageTitle = this.onGetErrorMessageTitle.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onGetErrorMessageImageUrl = this.onGetErrorMessageImageUrl.bind(this);
this.onChangeMultiSelect = this.onChangeMultiSelect.bind(this);
this.onAddScript = this.onAddScript.bind(this);
this.onDismissAddScriptPanel = this.onDismissAddScriptPanel.bind(this);
this.onSave = this.onSave.bind(this);
this.onSelectedItemWebTemplate = this.onSelectedItemWebTemplate.bind(this);
}
/**
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof AddSiteDesign
*/
private onCancel(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
this.props.onDismiss();
}
/**
* Save SiteDesign Event
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof AddSiteDesign
*/
private async onSave(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
const _siteDesignCreationInfo: SiteDesignCreationInfo = this.state.siteDesignCreationInfo;
// read SiteScripts
for (const item of this.state.selectedItems) {
_siteDesignCreationInfo.SiteScriptIds.push(item);
}
try {
this.setState({ saving: true, disableSaveButton: true });
const result = await this.spService.createSiteDesign(_siteDesignCreationInfo);
this.props.onDismiss(true);
} catch (error) {
console.log(error.message);
this.setState({ saving: false, disableSaveButton: true, showError: true, errorMessage: error.message });
}
}
/**
* Add SiteScrit Event
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof AddSiteDesign
*/
private onAddScript(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
this.setState({ showPanelAddScript: true });
}
private onDismissAddScriptPanel(refresh: boolean) {
this.setState({ showPanelAddScript: false });
if (refresh) {
this.loadSiteScripts();
}
}
/**
* Load SiteScript
* @private
* @memberof AddSiteDesign
*/
private async loadSiteScripts() {
this.siteScripts = await this.spService.getSiteScripts();
let siteScriptsList: { key: string, text: string }[] = [];
if (this.siteScripts) {
for (const sitescript of this.siteScripts) {
siteScriptsList.push({
key: sitescript.Id,
text: sitescript.Title
});
}
this.setState({ sitescriptslist: siteScriptsList.sort() });
}
}
// Component Did Mount
/**
*
* @memberof AddSiteDesign
*/
public async componentDidMount() {
// LoadTenantProperties
await this.loadSiteScripts();
}
/**
*
*
* @memberof AddSiteDesign
*/
public async onChangeMultiSelect(event: React.FormEvent<HTMLDivElement>, item: IDropdownOption) {
const updatedSelectedItem = this.state.selectedItems ? this.copyArray(this.state.selectedItems) : [];
const hasTitlevalue = this.state.siteDesignCreationInfo.Title;
if (item.selected) {
// add the option if it's checked
updatedSelectedItem.push(item.key);
this.setState({ errorMessage: '', selectedItems: updatedSelectedItem, disableSaveButton: hasTitlevalue ? false : true });
} else {
// remove the option if it's unchecked
const currIndex = updatedSelectedItem.indexOf(item.key);
if (currIndex > -1) {
updatedSelectedItem.splice(currIndex, 1);
}
this.setState({ errorMessage: '', selectedItems: updatedSelectedItem, disableSaveButton: updatedSelectedItem.length > 0 ? false : true });
}
}
/**
*
*
* @private
* @param {React.FormEvent<HTMLDivElement>} event
* @param {IDropdownOption} item
* @memberof AddSiteDesign
*/
private onSelectedItemWebTemplate(event: React.FormEvent<HTMLDivElement>, item: IDropdownOption) {
const _siteDesignCreationInfo = this.state.siteDesignCreationInfo;
_siteDesignCreationInfo.WebTemplate = item.key.toString();
this.setState({ selectedItemWebTemplate: item.key, siteDesignCreationInfo: _siteDesignCreationInfo });
}
/**
*
* @memberof AddSiteDesign
*/
public copyArray(array: any[]): any[] {
const newArray: any[] = [];
for (let i = 0; i < array.length; i++) {
newArray[i] = array[i];
}
return newArray;
}
// Validate Value
/**
* Validate Title
* @private
* @param {string} value
* @returns
* @memberof AddSiteDesign
*/
private onGetErrorMessageTitle(value: string) {
let returnvalue: string = '';
const _siteDesignCreationInfo = this.state.siteDesignCreationInfo;
const numberSiteScriptSelected = this.state.selectedItems.length;
if (value.trim().length > 0) {
_siteDesignCreationInfo.Title = value;
this.setState({ disableSaveButton: numberSiteScriptSelected > 0 ? false : true, siteDesignCreationInfo: _siteDesignCreationInfo });
} else {
_siteDesignCreationInfo.Title = value;
this.setState({ errorMessage: '', disableSaveButton: true, siteDesignCreationInfo: _siteDesignCreationInfo });
returnvalue = strings.AddSiteDesignPanelTitleErrorMessage;
}
return returnvalue;
}
/**
* Validate Description
*
* @private
* @param {string} value
* @returns
* @memberof AddSiteDesign
*/
private onGetErrorMessageDescription(value: string) {
let returnvalue: string = '';
const _siteDesignCreationInfo = this.state.siteDesignCreationInfo;
_siteDesignCreationInfo.Description = value;
this.setState({ siteDesignCreationInfo: _siteDesignCreationInfo });
return returnvalue;
}
/**
*
* @private
* @param {string} value
* @returns {string} returnvale
* @memberof AddSiteDesign
*/
private onGetErrorMessageImageUrl(value: string) {
let returnvalue: string = '';
const _siteDesignCreationInfo = this.state.siteDesignCreationInfo;
if (value.length > 0) {
try {
const _URL = new URL(value);
_siteDesignCreationInfo.PreviewImageUrl = value;
} catch (error) {
_siteDesignCreationInfo.PreviewImageUrl = value;
returnvalue = error.message;
}
} else {
_siteDesignCreationInfo.PreviewImageUrl = value;
}
this.setState({ siteDesignCreationInfo: _siteDesignCreationInfo });
return returnvalue;
}
/**
*
* @private
* @param {React.MouseEvent<HTMLElement>} ev
* @param {boolean} checked
* @memberof AddSiteDesign
*/
private onIsDefault(ev: React.MouseEvent<HTMLElement>, checked: boolean) {
const _siteDesignCreationInfo = this.state.siteDesignCreationInfo;
_siteDesignCreationInfo.IsDefault = checked;
this.setState({ siteDesignCreationInfo: _siteDesignCreationInfo });
}
/**
* On Render
*
* @returns {React.ReactElement<IAddSiteDesignProps>}
* @memberof AddSiteDesign
*/
public render(): React.ReactElement<IAddSiteDesignProps> {
return (
<div className={styles.siteDesigns}>
<Panel isOpen={this.props.showPanel}
onDismiss={this.onCancel}
type={PanelType.medium}
headerText="Add Site Design">
<TextField
label={strings.AddSiteDesignTitleLabel}
readOnly={this.state.readOnly}
required={true}
value={this.state.siteDesignCreationInfo ? this.state.siteDesignCreationInfo.Title : ''}
deferredValidationTime={1500}
onGetErrorMessage={this.onGetErrorMessageTitle} />
<Dropdown
placeholder="SelectWebTemplate"
label="WebTemplate"
selectedKey={this.state.selectedItemWebTemplate}
onChange={this.onSelectedItemWebTemplate}
options={[
{ key: 64, text: 'Team Site' },
{ key: 68, text: 'Comunication Site' }
]}
/>
<TextField
label={strings.AddSiteDesignDescriptionLabel}
readOnly={this.state.readOnly}
value={this.state.siteDesignCreationInfo ? this.state.siteDesignCreationInfo.Description : ''}
deferredValidationTime={1500}
onGetErrorMessage={this.onGetErrorMessageDescription} />
<TextField
label={strings.AddSiteDesignImageUrlLabel}
readOnly={this.state.readOnly}
value={this.state.siteDesignCreationInfo ? this.state.siteDesignCreationInfo.PreviewImageUrl : ''}
deferredValidationTime={1500}
onGetErrorMessage={this.onGetErrorMessageImageUrl} />
<br />
{
this.state.siteDesignCreationInfo.PreviewImageUrl &&
<Image src={this.state.siteDesignCreationInfo ? this.state.siteDesignCreationInfo.PreviewImageUrl : ''}
imageFit={ImageFit.cover}
width={200}
height={200}
/>
}
<Toggle
defaultChecked={false}
label={strings.AddSiteDesignIsDefaultLabel}
onText="On"
offText="Off"
onChange={this.onIsDefault}
/>
<div style={{ paddingTop: '10px', textAlign: 'right' }}>
<ActionButton
data-automation-id="test"
iconProps={{ iconName: 'Add' }}
allowDisabledFocus={true}
title={"Add Site Script"}
onClick={this.onAddScript}
>
{strings.AddSiteDesignPanelActionButtonText}
</ActionButton>
</div>
<div>
<p> {strings.AddSiteDesignPanelScriptOrderInfo} </p>
<Dropdown
placeholder={strings.AddSiteDesignPanelDropDownPlaceholderText}
label={strings.AddSiteDesignPanelDropDownLabel}
selectedKeys={this.state.selectedItems}
onChange={this.onChangeMultiSelect}
multiSelect
options={this.state.sitescriptslist}
/>
</div>
<React.Suspense fallback={<div>Loading...</div>}>
<this.AddScriptDialog
hideDialog={!this.state.showPanelAddScript}
onDismiss={this.onDismissAddScriptPanel}
context={this.props.context} />
</React.Suspense>
<br />
<DialogFooter>
{
this.state.saving &&
<div style={{ display: "inline-block", marginRight: '10px', verticalAlign: 'middle' }}>
<Spinner size={SpinnerSize.small} ariaLive="assertive" />
</div>
}
<PrimaryButton onClick={this.onSave} text={strings.AddSiteDesignPanelButtonSaveText} disabled={this.state.disableSaveButton} />
<DefaultButton onClick={this.onCancel} text={strings.AddSiteDesignPanelButtonCancelText} />
</DialogFooter>
{
this.state.showError &&
<div style={{ marginTop: '15px' }}>
<MessageBar messageBarType={MessageBarType.error} >
<span>{this.state.errorMessage}</span>
</MessageBar>
</div>
}
</Panel>
</div>
);
}
}

View File

@ -0,0 +1,28 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
import { SiteScriptInfo, SiteDesignInfo } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
export interface IAddSiteDesignProps {
context: WebPartContext;
mode: panelMode;
onDismiss(refresh?: boolean): void;
showPanel: boolean;
}
interface ISiteDesignSelectedItem {
key: string;
Description: string;
Id: string;
Title: string;
WebTemplate:string;
SiteScriptIds: string;
numberSiteScripts: number;
IsDefault: boolean;
PreviewImageAltText: string;
PreviewImageUrl: string;
Version: string;
}

View File

@ -0,0 +1,21 @@
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { SiteDesignCreationInfo, SiteScriptInfo } from '@pnp/sp';
import { ISiteScript } from '../../types/ISiteScript';
export interface IAddSiteDesignState {
isLoading: boolean;
siteDesignCreationInfo?: SiteDesignCreationInfo;
showPanel: boolean;
panelMode: panelMode;
showError: boolean;
errorMessage: string;
readOnly: boolean;
disableSaveButton: boolean;
sitescriptslist: { key: string | number | undefined, text: string }[];
selectedItems: string[];
showPanelAddScript: boolean;
saving: boolean;
selectedItemWebTemplate: string | number;
}

View File

@ -0,0 +1,239 @@
// João Mendes
// Mar 2019
//
import * as React from 'react';
import styles from '../../webparts/siteDesigns/components/SiteDesigns.module.scss';
import { IAddSiteScriptProps } from './IAddSiteScriptProps';
import { escape } from '@microsoft/sp-lodash-subset';
import spservice from '../../services/spservices';
import * as strings from 'SiteDesignsWebPartStrings';
import { IAddSiteScriptState } from './IAddSiteScriptState';
import { SiteDesignInfo, SiteScriptInfo, SiteScriptUpdateInfo } from '@pnp/sp';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { ISiteScript } from '../../types/ISiteScript';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
export default class AddSiteScript extends React.Component<IAddSiteScriptProps, IAddSiteScriptState> {
private spService: spservice;
private siteScriptshowError: boolean = false;
private siteTitleshowError: boolean = false;
private JsonEditorWrapper = React.lazy(() => import('../json-editor-wrapper' /* webpackChunkName: "jsoneditorwrapper" */));
private currentSiteScript: ISiteScript = {
"$schema": "schema.json",
"actions": [],
"bindata": {},
"version": 1
};
/**
*Creates an instance of AddSiteScript.
* @param {*} props
* @memberof AddSiteScript
*/
public constructor(props) {
super(props);
// Initialize state
this.state = ({
saving: false,
hideDialog: true,
readOnly: false,
title: '',
showError: false,
errorMessage: '',
disableSaveButton: true,
currentSiteScript: this.currentSiteScript,
description: ''
});
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this.onGetErrorMessageDescription = this.onGetErrorMessageDescription.bind(this);
this.onGetErrorMessageTitle = this.onGetErrorMessageTitle.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onSave = this.onSave.bind(this);
this.setSiteScript = this.setSiteScript.bind(this);
this.onValidateSiteScript = this.onValidateSiteScript.bind(this);
}
/**
*
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof AddSiteScript
*/
private onCancel(ev: React.MouseEvent<HTMLButtonElement>) {
this.props.onDismiss();
}
private async onSave(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
try {
this.setState({ saving: true, disableSaveButton: true });
const result = await this.spService.createSiteScript(this.state.title, this.state.description, this.state.currentSiteScript);
this.props.onDismiss(true);
} catch (error) {
console.log(error.message);
this.setState({ saving: false, disableSaveButton: true, showError: true, errorMessage: error.message });
}
}
/**
*
* @memberof AddSiteScript
*/
public async componentDidMount() {
// LoadTenantProperties
}
/**
*
* @private
* @param {*} value
* @memberof AddSiteScript
*/
private setSiteScript(value: ISiteScript) {
this.setState({ currentSiteScript: value });
}
/**
*
*
* @private
* @param {string} error (true/false)
* @memberof AddSiteScript
*/
private onValidateSiteScript(valid: boolean) {
if (valid) {
this.siteScriptshowError = false;
if (this.siteTitleshowError) {
this.setState({ errorMessage: '', disableSaveButton: true, showError: false });
} else {
this.setState({ errorMessage: '', disableSaveButton: false, showError: false });
}
} else {
this.siteScriptshowError = true;
this.setState({ showError: true, errorMessage: strings.JSONSchemaErrorMessage, disableSaveButton: true });
}
}
// Validate Value
/**
*
* Validate Title
* @private
* @param {string} value
* @returns
* @memberof AddSiteScript
*/
private onGetErrorMessageTitle(value: string) {
let returnvalue: string = '';
let siteScriptTitle = this.state.title;
const { showError } = this.state;
if (value.trim().length > 0) {
siteScriptTitle = value;
this.siteTitleshowError = false;
if (this.siteScriptshowError) {
this.setState({ disableSaveButton: true, title: siteScriptTitle });
} else {
this.setState({ disableSaveButton: false, title: siteScriptTitle });
}
} else {
siteScriptTitle = value;
this.siteTitleshowError = true;
this.setState({ disableSaveButton: true, title: siteScriptTitle });
// returnvalue = 'Site Script title is required';
}
return returnvalue;
}
/**
*
*
* @private
* @param {string} value
* @returns
* @memberof AddSiteScript
*/
private onGetErrorMessageDescription(value: string) {
let returnvalue: string = '';
this.setState({ description: value });
return returnvalue;
}
/**
*
*
* @returns {React.ReactElement<IAddSiteScriptProps>}
* @memberof AddSiteScript
*/
public render(): React.ReactElement<IAddSiteScriptProps> {
return (
<div className={styles.siteDesigns} >
<Dialog
hidden={this.props.hideDialog}
onDismiss={this.onCancel}
minWidth={"600px"}
dialogContentProps={{
type: DialogType.normal,
title: strings.AddSiteScriptDialogTitle,
subText: strings.AddSiteScriptDialogSubText
}}
modalProps={{
isBlocking: true,
}}
>
{
this.state.showError &&
<MessageBar messageBarType={MessageBarType.error}>
<span>{this.state.errorMessage}</span>
</MessageBar>
}
<TextField
label={strings.AddSiteScriptTitleLabel}
readOnly={this.state.readOnly}
required={true}
value={this.state.title ? this.state.title : ''}
deferredValidationTime={300}
onGetErrorMessage={this.onGetErrorMessageTitle} />
<TextField
label={strings.AddSiteScriptDescriptionLabel}
readOnly={this.state.readOnly}
value={this.state.title ? this.state.description : ''}
deferredValidationTime={300}
onGetErrorMessage={this.onGetErrorMessageDescription} />
<br />
{
<React.Suspense fallback={<div>Loading...</div>}>
<this.JsonEditorWrapper
currentSiteScript={this.state.currentSiteScript}
setSiteScript={this.setSiteScript}
onValidate={this.onValidateSiteScript}
/>
</React.Suspense>
}
<DialogFooter>
{
this.state.saving &&
<div style={{ display: "inline-block", marginRight: '10px', verticalAlign: 'middle' }}>
<Spinner size={SpinnerSize.small} ariaLive="assertive" />
</div>
}
<PrimaryButton onClick={this.onSave} text={strings.AddSiteScriptPanelButtonSave} disabled={this.state.disableSaveButton} />
<DefaultButton onClick={this.onCancel} text={strings.AddSiteScriptPanelButtonCancelText} />
</DialogFooter>
</Dialog>
</div>
);
}
}

View File

@ -0,0 +1,16 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
import { SiteScriptInfo, SiteDesignInfo } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
export interface IAddSiteScriptProps {
context: WebPartContext;
hideDialog: boolean;
onDismiss(refresh?: boolean): void;
}

View File

@ -0,0 +1,17 @@
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { SiteDesignInfo, SiteScriptInfo } from '@pnp/sp';
import { ISiteScript } from '../../types/ISiteScript';
export interface IAddSiteScriptState{
title:string;
description:string;
showError: boolean;
errorMessage: string;
readOnly: boolean;
disableSaveButton: boolean;
currentSiteScript: ISiteScript;
hideDialog:boolean;
saving:boolean;
}

View File

@ -0,0 +1,309 @@
// João Mendes
// Mar 2019
//
import * as React from 'react';
import styles from '../../webparts/siteDesigns/components/SiteDesigns.module.scss';
import { IAddSiteScriptToSiteDesignProps } from './IAddSiteScriptToSiteDesignProps';
import { escape } from '@microsoft/sp-lodash-subset';
import spservice from '../../services/spservices';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import * as strings from 'SiteDesignsWebPartStrings';
import { IAddSiteScriptToSiteDesignState } from './IAddSiteScriptToSiteDesignState';
import { SiteScriptInfo, SiteScriptUpdateInfo, SiteDesignCreationInfo, SiteDesignUpdateInfo } from '@pnp/sp';
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Toggle } from 'office-ui-fabric-react/lib/Toggle';
import { IImageProps, Image, ImageFit } from 'office-ui-fabric-react/lib/Image';
import { ISiteScript } from '../../types/ISiteScript';
import { Dropdown, IDropdown, DropdownMenuItemType, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import { getGUID } from '@pnp/common';
import { FieldTextRenderer } from "@pnp/spfx-controls-react/lib/FieldTextRenderer";
export default class AddSiteScriptToSiteDesign extends React.Component<IAddSiteScriptToSiteDesignProps, IAddSiteScriptToSiteDesignState> {
private spService: spservice;
private siteScripts: SiteScriptInfo[];
private currentSiteScriptsIds: string[] = [];
private AddScriptDialog = React.lazy(() => import('../AddSiteScript/AddSiteScript' /* webpackChunkName: "addscriptdialog" */));
public constructor(props) {
super(props);
// Initialize state
this.state = ({
isLoading: false,
readOnly: true,
showPanel: false,
panelMode: panelMode.New,
showError: false,
errorMessage: '',
disableSaveButton: true,
siteScriptsList: [],
selectedItems: [],
showPanelAddScript: false,
saving: false,
});
this.currentSiteScriptsIds = this.props.siteDesignInfo.SiteScriptIds.split(',');
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this.onCancel = this.onCancel.bind(this);
this.onChangeMultiSelect = this.onChangeMultiSelect.bind(this);
this.onAddScript = this.onAddScript.bind(this);
this.onDismissAddScriptPanel = this.onDismissAddScriptPanel.bind(this);
this.onSave = this.onSave.bind(this);
}
/**
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof AddSiteDesign
*/
private onCancel(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
this.props.onDismiss();
}
/**
* Save SiteDesign Event
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof AddSiteDesign
*/
private async onSave(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
try {
for (const item of this.state.selectedItems) {
this.currentSiteScriptsIds.push(item);
}
const siteDesignUpdateInfo: SiteDesignUpdateInfo = {
Id: this.props.siteDesignInfo.Id,
SiteScriptIds: this.currentSiteScriptsIds
};
this.setState({ saving: true, disableSaveButton: true });
const result = await this.spService.updateSiteDesign(siteDesignUpdateInfo);
this.props.onDismiss(true);
} catch (error) {
console.log(error.message);
this.setState({ saving: false, disableSaveButton: true, showError: true, errorMessage: error.message });
}
}
/**
* Add SiteScrit Event
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof AddSiteDesign
*/
private onAddScript(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
this.setState({ showPanelAddScript: true });
}
private onDismissAddScriptPanel(refresh: boolean) {
this.setState({ showPanelAddScript: false });
if (refresh) {
this.loadSiteScripts();
}
}
/**
* Check if SiteScript already exists in Site Design
*
* @private
* @param {string} siteScriptId
* @returns
* @memberof AddSiteScriptToSiteDesign
*/
private async checkSiteScriptExists(siteScriptId: string) {
let found: boolean = false;
for (const currentSitescriptId of this.currentSiteScriptsIds) {
if (currentSitescriptId === siteScriptId) {
found = true;
break;
}
}
return found;
}
/**
* Load SiteScript
* @private
* @memberof AddSiteDesign
*/
private async loadSiteScripts() {
this.siteScripts = await this.spService.getSiteScripts();
let siteScriptsList: IDropdownOption[] = [];
if (this.siteScripts) {
for (const siteScript of this.siteScripts) {
const exists = await this.checkSiteScriptExists(siteScript.Id);
if (!exists) {
siteScriptsList.push({
key: siteScript.Id,
text: siteScript.Title
});
}
}
this.setState({ siteScriptsList: siteScriptsList.sort() });
}
}
// Component Did Mount
/**
*
* @memberof AddSiteDesign
*/
public async componentDidMount() {
// LoadTenantProperties
await this.loadSiteScripts();
}
/**
*
*
* @memberof AddSiteDesign
*/
public async onChangeMultiSelect(event: React.FormEvent<HTMLDivElement>, item: IDropdownOption) {
const updatedSelectedItem = this.state.selectedItems ? this.copyArray(this.state.selectedItems) : [];
if (item.selected) {
// add the option if it's checked
updatedSelectedItem.push(item.key);
this.setState({ errorMessage: '', selectedItems: updatedSelectedItem, disableSaveButton: false });
} else {
// remove the option if it's unchecked
const currIndex = updatedSelectedItem.indexOf(item.key);
if (currIndex > -1) {
updatedSelectedItem.splice(currIndex, 1);
}
this.setState({ errorMessage: '', selectedItems: updatedSelectedItem, disableSaveButton: updatedSelectedItem.length > 0 ? false : true });
}
}
/**
*
* @memberof AddSiteDesign
*/
public copyArray(array: any[]): any[] {
const newArray: any[] = [];
for (let i = 0; i < array.length; i++) {
newArray[i] = array[i];
}
return newArray;
}
/**
* On Render
*
* @returns {React.ReactElement<IAddSiteDesignProps>}
* @memberof AddSiteDesign
*/
public render(): React.ReactElement<IAddSiteScriptToSiteDesignProps> {
return (
<div className={styles.siteDesigns}>
<Panel isOpen={this.props.showPanel}
onDismiss={this.onCancel}
type={PanelType.medium}
headerText={strings.AddSiteScriptToSiteDesignPanelTitle}>
<TextField
label={strings.AddSiteDesignTitleLabel}
readOnly={this.state.readOnly}
value={this.props.siteDesignInfo.Title}
style={{backgroundColor: "#f8f8f8"}}
/>
<TextField
label={"WebTemplate"}
readOnly={this.state.readOnly}
value={this.props.siteDesignInfo.WebTemplate}
style={{backgroundColor: "#f8f8f8"}}
/>
<TextField
label={strings.AddSiteDesignDescriptionLabel}
readOnly={this.state.readOnly}
value={this.props.siteDesignInfo.Description}
style={{backgroundColor: "#f8f8f8"}}
/>
<br />
{
this.props.siteDesignInfo.PreviewImageUrl &&
<Image src={this.props.siteDesignInfo ? this.props.siteDesignInfo.PreviewImageUrl : ''}
imageFit={ImageFit.cover}
width={200}
height={200}
/>
}
<Toggle
defaultChecked={this.props.siteDesignInfo.IsDefault}
label={strings.AddSiteDesignIsDefaultLabel}
onText="On"
offText="Off"
disabled
/>
<div style={{ paddingTop: '10px', textAlign: 'right' }}>
<ActionButton
data-automation-id="test"
iconProps={{ iconName: 'Add' }}
allowDisabledFocus={true}
title={strings.actionButtonTitle}
onClick={this.onAddScript}
>
Add SiteScript
</ActionButton>
</div>
<div>
<Dropdown
placeholder={strings.DropDownSelectSiteScriptPlaceHolder}
label={strings.DropDownSelectSiteScriptLabel}
selectedKeys={this.state.selectedItems}
onChange={this.onChangeMultiSelect}
multiSelect
options={this.state.siteScriptsList}
/>
</div>
<React.Suspense fallback={<div>Loading...</div>}>
<this.AddScriptDialog
hideDialog={!this.state.showPanelAddScript}
onDismiss={this.onDismissAddScriptPanel}
context={this.props.context} />
</React.Suspense>
<br />
<DialogFooter>
{
this.state.saving &&
<div style={{ display: "inline-block", marginRight: '10px', verticalAlign: 'middle' }}>
<Spinner size={SpinnerSize.small} ariaLive="assertive" />
</div>
}
<PrimaryButton onClick={this.onSave} text={strings.AddSiteScriptToSiteDesignPanelButtonSaveText} disabled={this.state.disableSaveButton} />
<DefaultButton onClick={this.onCancel} text={strings.AddSiteScriptToSiteDesignPanelButtonCancelText} />
</DialogFooter>
{
this.state.showError &&
<div style={{ marginTop: '15px' }}>
<MessageBar messageBarType={MessageBarType.error} >
<span>{this.state.errorMessage}</span>
</MessageBar>
</div>
}
</Panel>
</div>
);
}
}

View File

@ -0,0 +1,26 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
import { SiteScriptInfo, SiteDesignInfo } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
export interface IAddSiteScriptToSiteDesignProps {
context: WebPartContext;
onDismiss(refresh?: boolean): void;
showPanel: boolean;
siteDesignInfo: ISiteDesignSelectedItem;
}
interface ISiteDesignSelectedItem {
key: string;
Description: string;
Id: string;
Title: string;
WebTemplate:string;
SiteScriptIds: string;
numberSiteScripts: number;
IsDefault: boolean;
PreviewImageAltText: string;
PreviewImageUrl: string;
Version: string;
}

View File

@ -0,0 +1,20 @@
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { SiteDesignCreationInfo, SiteScriptInfo } from '@pnp/sp';
import { ISiteScript } from '../../types/ISiteScript';
export interface IAddSiteScriptToSiteDesignState {
isLoading: boolean;
showPanel: boolean;
panelMode: panelMode;
showError: boolean;
errorMessage: string;
readOnly: boolean;
disableSaveButton: boolean;
siteScriptsList: { key: string | number | undefined, text: string }[];
selectedItems: string[];
showPanelAddScript: boolean;
saving: boolean;
}

View File

@ -0,0 +1,209 @@
// João Mendes
// Mar 2019
//
import * as React from 'react';
import styles from '../../webparts/siteDesigns/components/SiteDesigns.module.scss';
import { IApplySiteDesignProps } from './IApplySiteDesignProps';
import { escape } from '@microsoft/sp-lodash-subset';
import spservice from '../../services/spservices';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import * as strings from 'SiteDesignsWebPartStrings';
import { IApplySiteDesignState } from './IApplySiteDesignState';
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Toggle } from 'office-ui-fabric-react/lib/Toggle';
import { IImageProps, Image, ImageFit } from 'office-ui-fabric-react/lib/Image';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import { IViewSite } from '../SelectSite/IViewSite';
import { IAddSiteDesignTaskToCurrentWebResult } from '../../services/IAddSiteDesignTaskToCurrentWebResult';
export default class ApplySiteDesign extends React.Component<IApplySiteDesignProps, IApplySiteDesignState> {
private spService: spservice;
private siteDesignsApplyedInfo: {addSiteDesignTaskResult:IAddSiteDesignTaskToCurrentWebResult, siteUrl:string}[] = [];
private selectedWebSites: IViewSite[] = [];
private SelectSite = React.lazy(() => import('../SelectSite/SelectSite' /* webpackChunkName: "selectsite" */));
public constructor(props) {
super(props);
// Initialize state
this.state = ({
isLoading: false,
readOnly: true,
showPanel: false,
panelMode: panelMode.New,
showError: false,
errorMessage: '',
disableSaveButton: true,
selectedItems: [],
saving: false,
});
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this.onCancel = this.onCancel.bind(this);
this.onSave = this.onSave.bind(this);
this.ontSelectSite = this.ontSelectSite.bind(this);
}
/**
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof ApplySiteDesign
*/
private onCancel(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
this.props.onDismiss();
}
/**
* Save SiteDesign Event
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof ApplySiteDesign
*/
private async onSave(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
this.setState({ saving: true, disableSaveButton: true, showError: false, errorMessage: '' });
try {
if (this.selectedWebSites.length > 0) {
let addSiteDesignTaskResult: IAddSiteDesignTaskToCurrentWebResult = null;
for (const item of this.selectedWebSites) {
addSiteDesignTaskResult = await this.spService.AddSiteDesignTask(item.url,this.props.siteDesignInfo.Id);
this.siteDesignsApplyedInfo.push({addSiteDesignTaskResult: addSiteDesignTaskResult, siteUrl:item.url});
}
}
this.props.onDismiss(this.siteDesignsApplyedInfo,true);
} catch (error) {
console.log(error.message);
this.setState({ saving: false, disableSaveButton: true, showError: true, errorMessage: error.message });
}
}
/**
*
* @private
* @param {IViewSite[]} items
* @memberof ApplySiteDesign
*/
private ontSelectSite(items: IViewSite[]) {
if (items.length > 0) {
this.selectedWebSites = items;
this.setState({
disableSaveButton: false
});
} else {
this.selectedWebSites = [];
this.setState({
disableSaveButton: true
});
}
}
/**
* Load SiteScript
* @private
* @memberof ApplySiteDesign
*/
private async loadSiteScripts() {
}
// Component Did Mount
/**
*
* @memberof ApplySiteDesign
*/
public async componentDidMount() {
// LoadTenantProperties
}
/**
* On Render
*
* @returns {React.ReactElement<IApplySiteDesignProps>}
* @memberof ApplySiteDesign
*/
public render(): React.ReactElement<IApplySiteDesignProps> {
return (
<div className={styles.siteDesigns}>
<Panel isOpen={this.props.showPanel}
onDismiss={this.onCancel}
type={PanelType.medium}
headerText={strings.ApplyPanelTitle}>
<TextField
label={strings.AddSiteDesignTitleLabel}
readOnly={this.state.readOnly}
value={this.props.siteDesignInfo.Title}
style={{ backgroundColor: "#f8f8f8" }}
/>
<TextField
label={"WebTemplate"}
readOnly={this.state.readOnly}
value={this.props.siteDesignInfo.WebTemplate}
style={{ backgroundColor: "#f8f8f8" }}
/>
<TextField
label={strings.AddSiteDesignDescriptionLabel}
readOnly={this.state.readOnly}
value={this.props.siteDesignInfo.Description}
style={{ backgroundColor: "#f8f8f8" }}
/>
<br />
{
this.props.siteDesignInfo.PreviewImageUrl &&
<Image src={this.props.siteDesignInfo ? this.props.siteDesignInfo.PreviewImageUrl : ''}
imageFit={ImageFit.cover}
width={200}
height={200}
/>
}
<Toggle
defaultChecked={this.props.siteDesignInfo.IsDefault}
label={strings.AddSiteDesignIsDefaultLabel}
onText="On"
offText="Off"
disabled
/>
<div>
<React.Suspense fallback={<div>Loading...</div>}>
<this.SelectSite
context={this.props.context}
onSelectItem={this.ontSelectSite}
/>
</React.Suspense>
</div>
<br />
<DialogFooter>
{
this.state.saving &&
<div style={{ display: "inline-block", marginRight: '10px', verticalAlign: 'middle' }}>
<Spinner size={SpinnerSize.small} ariaLive="assertive" />
</div>
}
<PrimaryButton onClick={this.onSave} text={strings.ApplyPanelApplyButtonLabel} disabled={this.state.disableSaveButton} />
<DefaultButton onClick={this.onCancel} text={strings.ApplyPanelButtonCancelLabel}/>
</DialogFooter>
{
this.state.showError &&
<div style={{ marginTop: '15px' }}>
<MessageBar messageBarType={MessageBarType.error} >
<span>{this.state.errorMessage}</span>
</MessageBar>
</div>
}
</Panel>
</div>
);
}
}

View File

@ -0,0 +1,27 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
import { SiteScriptInfo, SiteDesignInfo } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { IAddSiteDesignTaskToCurrentWebResult } from './../../services/IAddSiteDesignTaskToCurrentWebResult';
export interface IApplySiteDesignProps {
context: WebPartContext;
onDismiss(siteDesignsApplyedInfo?: { addSiteDesignTaskResult: IAddSiteDesignTaskToCurrentWebResult, siteUrl: string }[],refresh?: boolean): void;
showPanel: boolean;
siteDesignInfo: ISiteDesignSelectedItem;
}
interface ISiteDesignSelectedItem {
key: string;
Description: string;
Id: string;
Title: string;
WebTemplate:string;
SiteScriptIds: string;
numberSiteScripts: number;
IsDefault: boolean;
PreviewImageAltText: string;
PreviewImageUrl: string;
Version: string;
}

View File

@ -0,0 +1,20 @@
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { SiteDesignCreationInfo, SiteScriptInfo } from '@pnp/sp';
import { IViewSite } from '../SelectSite/IViewSite';
export interface IApplySiteDesignState {
isLoading: boolean;
showPanel: boolean;
panelMode: panelMode;
showError: boolean;
errorMessage: string;
readOnly: boolean;
disableSaveButton: boolean;
selectedItems: IViewSite[];
saving: boolean;
}

View File

@ -0,0 +1,182 @@
// João Mendes
import * as React from 'react';
import styles from '../../webparts/siteDesigns/components/SiteDesigns.module.scss';
import { IDeleteSiteDesignProps } from './IDeleteSiteDesignProps';
import { escape } from '@microsoft/sp-lodash-subset';
import spservice from '../../services/spservices';
import * as strings from 'SiteDesignsWebPartStrings';
import { IDeleteSiteDesignState } from './IDeleteSiteDesignState';
import { SiteScriptInfo, SiteScriptUpdateInfo, SiteDesignInfo, SiteDesignUpdateInfo } from '@pnp/sp';
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Toggle } from 'office-ui-fabric-react/lib/Toggle';
import { IImageProps, Image, ImageFit } from 'office-ui-fabric-react/lib/Image';
import { Dropdown, IDropdown, DropdownMenuItemType, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
export default class DeleteSiteDesign extends React.Component<IDeleteSiteDesignProps, IDeleteSiteDesignState> {
private spService: spservice;
public constructor(props) {
super(props);
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this.onCancel = this.onCancel.bind(this);
this.onDelete = this.onDelete.bind(this);
this.state = ({
isLoading: false,
readOnly: true,
showPanel: false,
siteDesignInfo: this.props.siteDesignInfo,
showError: false,
errorMessage: '',
disableDeleteButton: false,
saving: false,
selectedItemWebTemplate: parseInt(this.props.siteDesignInfo.WebTemplate)
});
}
/**
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberofEditSiteDesign
*/
private onCancel(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
this.props.onDismiss();
}
/**
* Save SiteDesign Event
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof EditSiteDesign
*/
private async onDelete(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
const siteDesignUpdateInfo: SiteDesignUpdateInfo= {
Id: this.state.siteDesignInfo.Id,
Description: this.state.siteDesignInfo.Description,
IsDefault: this.state.siteDesignInfo.IsDefault,
PreviewImageUrl: this.state.siteDesignInfo.PreviewImageUrl,
Title: this.state.siteDesignInfo.Title,
WebTemplate: this.state.siteDesignInfo.WebTemplate,
};
// read SiteScript
try {
this.setState({ saving: true, disableDeleteButton: true });
await this.spService.deleteSiteDesign(siteDesignUpdateInfo);
this.props.onDismiss(true);
} catch (error) {
console.log(error.message);
this.setState({ saving: false, disableDeleteButton: true, showError: true, errorMessage: error.message });
}
}
// Component Did Mount
/**
*
* @memberof EditSiteDesign
*/
public async componentDidMount() {
//
}
/**
* On Render
*
* @returns {React.ReactElement<IAddSiteDesignProps>}
* @memberof EditSiteDesign
*/
public render(): React.ReactElement<IDeleteSiteDesignProps> {
return (
<div className={styles.siteDesigns}>
<Panel isOpen={this.props.showPanel}
onDismiss={this.onCancel}
type={PanelType.medium}
headerText={strings.DeleteSiteDesignPanelTitle}>
<TextField
label={strings.SiteDesignIdLabel}
readOnly={this.state.readOnly}
value={this.state.siteDesignInfo ? this.state.siteDesignInfo.Id : ''}
style={{ backgroundColor: "#f8f8f8" }}
/>
<TextField
label={strings.AddSiteDesignTitleLabel}
readOnly={this.state.readOnly}
required={true}
value={this.state.siteDesignInfo ? this.state.siteDesignInfo.Title : ''}
style={{ backgroundColor: "#f8f8f8" }}
/>
<Dropdown
placeholder="SelectWebTemplate"
style={{ backgroundColor: "#f8f8f8" }}
label="WebTemplate"
selectedKey={this.state.selectedItemWebTemplate}
options={[
{ key: 64, text: 'Team Site' },
{ key: 68, text: 'Comunication Site' }
]}
/>
<TextField
label={strings.AddSiteDesignDescriptionLabel}
readOnly={this.state.readOnly}
value={this.state.siteDesignInfo ? this.state.siteDesignInfo.Description : ''}
style={{ backgroundColor: "#f8f8f8" }}
multiline
/>
<TextField
label={strings.AddSiteDesignImageUrlLabel}
readOnly={this.state.readOnly}
value={this.state.siteDesignInfo ? this.state.siteDesignInfo.PreviewImageUrl : ''}
style={{ backgroundColor: "#f8f8f8" }}
/>
<br />
{
this.state.siteDesignInfo.PreviewImageUrl &&
<Image src={this.state.siteDesignInfo ? this.state.siteDesignInfo.PreviewImageUrl : ''}
imageFit={ImageFit.cover}
width={200}
height={200}
/>
}
<Toggle
defaultChecked={this.state.siteDesignInfo.IsDefault}
label={strings.AddSiteDesignIsDefaultLabel}
onText="On"
offText="Off"
disabled={true}
/>
<br />
<DialogFooter>
{
this.state.saving &&
<div style={{ display: "inline-block", marginRight: '10px', verticalAlign: 'middle' }}>
<Spinner size={SpinnerSize.small} ariaLive="assertive" />
</div>
}
<PrimaryButton onClick={this.onDelete} text={strings.DeleteSiteDesignPanelButtonDeleteText} disabled={this.state.disableDeleteButton} />
<DefaultButton onClick={this.onCancel} text={strings.DeleteSiteDesignPanelButtonCanceltext} />
</DialogFooter>
{
this.state.showError &&
<div style={{ marginTop: '15px' }}>
<MessageBar messageBarType={MessageBarType.error} >
<span>{this.state.errorMessage}</span>
</MessageBar>
</div>
}
</Panel>
</div>
);
}
}

View File

@ -0,0 +1,14 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
import { SiteScriptInfo, SiteDesignInfo } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { IListViewItems } from '../../webparts/siteDesigns/components/IListViewItems';
export interface IDeleteSiteDesignProps {
context: WebPartContext;
mode: panelMode;
onDismiss(refresh?: boolean): void;
showPanel: boolean;
siteDesignInfo: IListViewItems;
}

View File

@ -0,0 +1,16 @@
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { IListViewItems } from '../../webparts/siteDesigns/components/IListViewItems';
import { SiteDesignInfo, SiteScriptInfo } from '@pnp/sp';
export interface IDeleteSiteDesignState {
isLoading: boolean;
siteDesignInfo?: IListViewItems;
showPanel: boolean;
showError: boolean;
errorMessage: string;
readOnly: boolean;
disableDeleteButton: boolean;
saving: boolean;
selectedItemWebTemplate: string | number;
}

View File

@ -0,0 +1,281 @@
// João Mendes
import * as React from 'react';
import styles from '../../webparts/siteDesigns/components/SiteDesigns.module.scss';
import { IEditSiteDesignProps } from './IEditSiteDesignProps';
import { escape } from '@microsoft/sp-lodash-subset';
import spservice from '../../services/spservices';
import * as strings from 'SiteDesignsWebPartStrings';
import { IEditSiteDesignState } from './IEditSiteDesignState';
import { SiteScriptInfo, SiteScriptUpdateInfo, SiteDesignInfo, SiteDesignUpdateInfo } from '@pnp/sp';
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Toggle } from 'office-ui-fabric-react/lib/Toggle';
import { IImageProps, Image, ImageFit } from 'office-ui-fabric-react/lib/Image';
import { Dropdown, IDropdown, DropdownMenuItemType, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
export default class EditSiteDesign extends React.Component<IEditSiteDesignProps, IEditSiteDesignState> {
private spService: spservice;
public constructor(props) {
super(props);
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this.onIsDefault = this.onIsDefault.bind(this);
this.onGetErrorMessageDescription = this.onGetErrorMessageDescription.bind(this);
this.onGetErrorMessageTitle = this.onGetErrorMessageTitle.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onGetErrorMessageImageUrl = this.onGetErrorMessageImageUrl.bind(this);
this.onSave = this.onSave.bind(this);
this.onSelectedItemWebTemplate = this.onSelectedItemWebTemplate.bind(this);
this.state = ({
isLoading: false,
readOnly: false,
showPanel: false,
siteDesignInfo: this.props.siteDesignInfo,
showError: false,
errorMessage: '',
disableSaveButton: true,
saving: false,
selectedItemWebTemplate: parseInt(this.props.siteDesignInfo.WebTemplate)
});
}
/**
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberofEditSiteDesign
*/
private onCancel(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
this.props.onDismiss();
}
/**
* Save SiteDesign Event
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof EditSiteDesign
*/
private async onSave(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
const siteDesignUpdateInfo: SiteDesignUpdateInfo= {
Id: this.state.siteDesignInfo.Id,
Description: this.state.siteDesignInfo.Description,
IsDefault: this.state.siteDesignInfo.IsDefault,
PreviewImageUrl: this.state.siteDesignInfo.PreviewImageUrl,
Title: this.state.siteDesignInfo.Title,
WebTemplate: this.state.siteDesignInfo.WebTemplate,
};
// read SiteScript
try {
this.setState({ saving: true, disableSaveButton: true });
const result = await this.spService.updateSiteDesign(siteDesignUpdateInfo);
this.props.onDismiss(true);
} catch (error) {
console.log(error.message);
this.setState({ saving: false, disableSaveButton: true, showError: true, errorMessage: error.message });
}
}
// Component Did Mount
/**
*
* @memberof EditSiteDesign
*/
public async componentDidMount() {
//
}
/**
* Select WebTemplate
*
* @private
* @param {React.FormEvent<HTMLDivElement>} event
* @param {IDropdownOption} item
* @memberof EditSiteDesign
*/
private onSelectedItemWebTemplate(event: React.FormEvent<HTMLDivElement>, item: IDropdownOption) {
const siteDesignInfo = this.state.siteDesignInfo;
siteDesignInfo.WebTemplate = item.key.toString();
this.setState({ selectedItemWebTemplate: item.key, siteDesignInfo: siteDesignInfo });
}
// Validate Value
/**
* Validate Title
* @private
* @param {string} value
* @returns
* @memberof EditSiteDesign
*/
private onGetErrorMessageTitle(value: string) {
let returnvalue: string = '';
const siteDesignInfo = this.state.siteDesignInfo;
if (value.trim().length > 0) {
siteDesignInfo.Title = value;
this.setState({ disableSaveButton: false, siteDesignInfo: siteDesignInfo });
} else {
siteDesignInfo.Title = value;
this.setState({ errorMessage: '', disableSaveButton: true, siteDesignInfo: siteDesignInfo });
returnvalue = "SiteDesign tile is required";
}
return returnvalue;
}
/**
* Validate Description
*
* @private
* @param {string} value
* @returns
* @memberof EditSiteDesign
*/
private onGetErrorMessageDescription(value: string) {
let returnvalue: string = '';
const siteDesignInfo = this.state.siteDesignInfo;
siteDesignInfo.Description = value;
this.setState({ siteDesignInfo: siteDesignInfo });
return returnvalue;
}
/**
*
* @private
* @param {string} value
* @returns {string} returnvale
* @memberof EditSiteDesign
*/
private onGetErrorMessageImageUrl(value: string) {
let returnvalue: string = '';
const siteDesignInfo = this.state.siteDesignInfo;
if (value.length > 0) {
try {
const _URL = new URL(value);
siteDesignInfo.PreviewImageUrl = value;
} catch (error) {
siteDesignInfo.PreviewImageUrl = value;
returnvalue = error.message;
}
} else {
siteDesignInfo.PreviewImageUrl = value;
}
this.setState({ siteDesignInfo: siteDesignInfo });
return returnvalue;
}
/**
*
* @private
* @param {React.MouseEvent<HTMLElement>} ev
* @param {boolean} checked
* @memberof EditSiteDesign
*/
private onIsDefault(ev: React.MouseEvent<HTMLElement>, checked: boolean) {
const siteDesignInfo = this.state.siteDesignInfo;
siteDesignInfo.IsDefault = checked;
this.setState({ siteDesignInfo: siteDesignInfo });
}
/**
* On Render
*
* @returns {React.ReactElement<IAddSiteDesignProps>}
* @memberof EditSiteDesign
*/
public render(): React.ReactElement<IEditSiteDesignProps> {
return (
<div className={styles.siteDesigns}>
<Panel isOpen={this.props.showPanel}
onDismiss={this.onCancel}
type={PanelType.medium}
headerText="Edit Site Design">
<TextField
label={strings.AddSiteDesignTitleLabel}
readOnly={this.state.readOnly}
required={true}
value={this.state.siteDesignInfo ? this.state.siteDesignInfo.Title : ''}
deferredValidationTime={200}
onGetErrorMessage={this.onGetErrorMessageTitle} />
<Dropdown
placeholder={strings.DropDownSelectSiteScriptPlaceHolder}
label={strings.DropDownSelectSiteScriptLabel}
selectedKey={this.state.selectedItemWebTemplate}
onChange={this.onSelectedItemWebTemplate}
options={[
{ key: 64, text: strings.WebTemplateTeamSite },
{ key: 68, text: strings.WebTemplateCommunicationSite }
]}
/>
<TextField
label={strings.AddSiteDesignDescriptionLabel}
readOnly={this.state.readOnly}
value={this.state.siteDesignInfo ? this.state.siteDesignInfo.Description : ''}
deferredValidationTime={200}
multiline
onGetErrorMessage={this.onGetErrorMessageDescription} />
<TextField
label={strings.AddSiteDesignImageUrlLabel}
readOnly={this.state.readOnly}
value={this.state.siteDesignInfo ? this.state.siteDesignInfo.PreviewImageUrl : ''}
deferredValidationTime={200}
onGetErrorMessage={this.onGetErrorMessageImageUrl} />
<br />
{
this.state.siteDesignInfo.PreviewImageUrl &&
<Image src={this.state.siteDesignInfo ? this.state.siteDesignInfo.PreviewImageUrl : ''}
imageFit={ImageFit.cover}
width={200}
height={200}
/>
}
<Toggle
defaultChecked={this.state.siteDesignInfo.IsDefault}
label={strings.AddSiteDesignIsDefaultLabel}
onText="On"
offText="Off"
onChange={this.onIsDefault}
/>
<br />
<DialogFooter>
{
this.state.saving &&
<div style={{ display: "inline-block", marginRight: '10px', verticalAlign: 'middle' }}>
<Spinner size={SpinnerSize.small} ariaLive="assertive" />
</div>
}
<PrimaryButton onClick={this.onSave} text={strings.EditSiteDesignPanelButtonSaveText} disabled={this.state.disableSaveButton} />
<DefaultButton onClick={this.onCancel} text={strings.EditSiteDesignPanelButtonCancelText} />
</DialogFooter>
{
this.state.showError &&
<div style={{ marginTop: '15px' }}>
<MessageBar messageBarType={MessageBarType.error} >
<span>{this.state.errorMessage}</span>
</MessageBar>
</div>
}
</Panel>
</div>
);
}
}

View File

@ -0,0 +1,14 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
import { SiteScriptInfo, SiteDesignInfo } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { IListViewItems } from '../../webparts/siteDesigns/components/IListViewItems';
export interface IEditSiteDesignProps {
context: WebPartContext;
mode: panelMode;
onDismiss(refresh?: boolean): void;
showPanel: boolean;
siteDesignInfo: IListViewItems;
}

View File

@ -0,0 +1,16 @@
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { IListViewItems } from '../../webparts/siteDesigns/components/IListViewItems';
import { SiteDesignInfo, SiteScriptInfo } from '@pnp/sp';
export interface IEditSiteDesignState {
isLoading: boolean;
siteDesignInfo?: IListViewItems;
showPanel: boolean;
showError: boolean;
errorMessage: string;
readOnly: boolean;
disableSaveButton: boolean;
saving: boolean;
selectedItemWebTemplate: string | number;
}

View File

@ -0,0 +1,274 @@
// João Mendes
// Mar 2019
//
import * as React from 'react';
import styles from '../../webparts/siteDesigns/components/SiteDesigns.module.scss';
import { IEditSiteScriptProps } from './IEditSiteScriptProps';
import { escape } from '@microsoft/sp-lodash-subset';
import spservice from '../../services/spservices';
import * as strings from 'SiteDesignsWebPartStrings';
import { IEditSiteScriptState } from './IEditSiteScriptState';
import { SiteDesignInfo, SiteScriptInfo, SiteScriptUpdateInfo } from '@pnp/sp';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { ISiteScript } from '../../types/ISiteScript';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
export default class EditSiteScript extends React.Component<IEditSiteScriptProps, IEditSiteScriptState> {
private spService: spservice;
private siteScriptShowError: boolean = false;
private siteTitleShowError: boolean = false;
private siteScriptInfo: SiteScriptInfo;
private JsonEditorWrapper = React.lazy(() => import('../json-editor-wrapper' /* webpackChunkName: "testcomponent" */));
private currentSiteScript: ISiteScript = {
"$schema": "schema.json",
"actions": [],
"bindata": {},
"version": 1
};
/**
*Creates an instance of EditSiteScript.
* @param {*} props
* @memberof EditSiteScript
*/
public constructor(props) {
super(props);
// Initialize state
this.state = ({
saving: false,
hideDialog: true,
readOnly: false,
showError: false,
errorMessage: '',
disableSaveButton: true,
currentSiteScript: this.currentSiteScript,
title: '',
description: '',
isLoading: false
});
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this.onGetErrorMessageDescription = this.onGetErrorMessageDescription.bind(this);
this.onGetErrorMessageTitle = this.onGetErrorMessageTitle.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onSave = this.onSave.bind(this);
this.setSiteScript = this.setSiteScript.bind(this);
this.onValidateSiteScript = this.onValidateSiteScript.bind(this);
}
/**
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof EditSiteScript
*/
private onCancel(ev: React.MouseEvent<HTMLButtonElement>) {
this.props.onDismiss();
}
/**
*
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof EditSiteScript
*/
private async onSave(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
try {
const siteScriptUpdateInfo: SiteScriptUpdateInfo = { Id: this.props.siteScriptId, Title: this.state.title, Description: this.state.description, Content: JSON.stringify(this.state.currentSiteScript) };
this.setState({ saving: true, disableSaveButton: true });
const result = await this.spService.updateSiteScript(siteScriptUpdateInfo);
this.props.onDismiss(true);
} catch (error) {
console.log(error.message);
this.setState({ saving: false, disableSaveButton: true, showError: true, errorMessage: error.message });
}
}
/**
*
* @memberof EditSiteScript
*/
public async componentDidMount() {
// LoadTenantProperties
try {
this.setState({ isLoading: true });
this.siteScriptInfo = await this.spService.getSiteScriptMetadata(this.props.siteScriptId);
const siteScript: ISiteScript = JSON.parse(this.siteScriptInfo.Content);
this.setState({ isLoading: false, description: this.siteScriptInfo.Description, title: this.siteScriptInfo.Title, currentSiteScript: siteScript });
} catch (error) {
console.log(error.message);
this.setState({ isLoading: false, saving: false, disableSaveButton: true, showError: true, errorMessage: error.message });
}
}
/**
*
* @private
* @param {*} value
* @memberof EditSiteScript
*/
private setSiteScript(value: ISiteScript) {
this.setState({ currentSiteScript: value });
}
/**
*
*
* @private
* @param {string} error (true/false)
* @memberof EditSiteScript
*/
private onValidateSiteScript(valid: boolean) {
if (valid) {
this.siteScriptShowError = false;
if (this.siteTitleShowError) {
this.setState({ errorMessage: '', disableSaveButton: true, showError: false });
} else {
this.setState({ errorMessage: '', disableSaveButton: false, showError: false });
}
} else {
this.siteScriptShowError = true;
this.setState({ showError: true, errorMessage: strings.JSONSchemaNotValidMessage, disableSaveButton: true });
}
}
// Validate Value
/**
*
* Validate Title
* @private
* @param {string} value
* @returns
* @memberof EditSiteScript
*/
private onGetErrorMessageTitle(value: string) {
let returnvalue: string = '';
let siteScriptTitle = this.state.title;
if (value.trim().length > 0) {
siteScriptTitle = value;
this.siteTitleShowError = false;
if (this.siteScriptShowError) {
this.setState({ disableSaveButton: true, title: siteScriptTitle });
} else {
this.setState({ disableSaveButton: false, title: siteScriptTitle });
}
} else {
siteScriptTitle = value;
this.siteTitleShowError = true;
this.setState({ disableSaveButton: true, title: siteScriptTitle });
// returnvalue = 'Site Script title is required';
}
return returnvalue;
}
/**
*
*
* @private
* @param {string} value
* @returns
* @memberof EditSiteScript
*/
private onGetErrorMessageDescription(value: string) {
let returnvalue: string = '';
this.setState({ description: value });
return returnvalue;
}
/**
*
*
* @returns {React.ReactElement<IEditSiteScriptProps>}
* @memberof EditSiteScript
*/
public render(): React.ReactElement<IEditSiteScriptProps> {
return (
<div className={styles.siteDesigns} >
<Dialog
hidden={this.props.hideDialog}
onDismiss={this.onCancel}
minWidth={"600px"}
dialogContentProps={{
type: DialogType.normal,
title: strings.EditSiteScriptDialogTitle,
subText: strings.AddSiteScriptDialogSubText
}}
modalProps={{
isBlocking: true,
}}
>
{
this.state.isLoading &&
<Spinner size={SpinnerSize.small} label={strings.LoadingLabel} ariaLive="assertive" />
}
{
this.state.showError &&
<MessageBar messageBarType={MessageBarType.error}>
<span>{this.state.errorMessage}</span>
</MessageBar>
}
{
!this.state.isLoading &&
<TextField
label={strings.SiteScriptIdLabel}
readOnly={true}
value={this.props.siteScriptId}
style={{backgroundColor: "#f8f8f8"}}
/>
}
{
!this.state.isLoading &&
<TextField
label={strings.AddSiteScriptTitleLabel}
readOnly={this.state.readOnly}
required={true}
value={this.state.title ? this.state.title : ''}
deferredValidationTime={300}
onGetErrorMessage={this.onGetErrorMessageTitle} />
}
{
!this.state.isLoading &&
<TextField
label={strings.AddSiteScriptDescriptionLabel}
readOnly={this.state.readOnly}
value={this.state.title ? this.state.description : ''}
deferredValidationTime={300}
onGetErrorMessage={this.onGetErrorMessageDescription} />
}
<br />
{
!this.state.isLoading &&
<React.Suspense fallback={<div>Loading...</div>}>
<this.JsonEditorWrapper
currentSiteScript={this.state.currentSiteScript}
setSiteScript={this.setSiteScript}
onValidate={this.onValidateSiteScript}
/>
</React.Suspense>
}
<DialogFooter>
{
this.state.saving &&
<div style={{ display: "inline-block", marginRight: '10px', verticalAlign: 'middle' }}>
<Spinner size={SpinnerSize.small} ariaLive="assertive" />
</div>
}
<PrimaryButton onClick={this.onSave} text={strings.EditSiteScriptSaveButtonLabel} disabled={this.state.disableSaveButton} />
<DefaultButton onClick={this.onCancel} text={strings.EditSiteScriptPanelButtonCancel} />
</DialogFooter>
</Dialog>
</div>
);
}
}

View File

@ -0,0 +1,16 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
import { SiteScriptInfo, SiteDesignInfo } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
export interface IEditSiteScriptProps {
context: WebPartContext;
hideDialog: boolean;
onDismiss(refresh?: boolean): void;
siteScriptId:string;
}

View File

@ -0,0 +1,18 @@
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { SiteDesignInfo, SiteScriptInfo } from '@pnp/sp';
import { ISiteScript } from '../../types/ISiteScript';
export interface IEditSiteScriptState {
title: string;
description: string;
showError: boolean;
errorMessage: string;
readOnly: boolean;
disableSaveButton: boolean;
currentSiteScript: ISiteScript;
hideDialog: boolean;
saving: boolean;
isLoading: boolean;
}

View File

@ -0,0 +1,11 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
import { SiteScriptInfo, SiteDesignInfo } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import { IViewSite } from './IViewSite';
export interface ISelectSiteProps {
context: WebPartContext;
onSelectItem: (item:IViewSite[]) => void;
}

View File

@ -0,0 +1,15 @@
import {IViewSite } from './IViewSite';
export interface ISelectSiteState {
isLoading: boolean;
showError: boolean;
errorMessage: string;
selectedItems: IViewSite[];
items: IViewSite[];
hasError:boolean;
showList:boolean;
}

View File

@ -0,0 +1,7 @@
export interface IViewSite {
key: string;
webtemplate: string;
title: string;
url: string;
}

View File

@ -0,0 +1,32 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.selectSite {
margin-top: 25px
}
.title {
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
vertical-align: top;
margin-right: 15px;
display: inline-block;
@include ms-font-m;
}
.label {
vertical-align: top;
margin-right: 15px;
display: inline-block;
@include ms-font-m;
width: 100px;
}
.listItem {
@include ms-font-l;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}

View File

@ -0,0 +1,161 @@
// João Mendes
// Mar 2019
//
import * as React from 'react';
import styles from './SelectSite.module.scss';
import { ISelectSiteProps } from './ISelectSiteProps';
import { escape } from '@microsoft/sp-lodash-subset';
import spservice from '../../services/spservices';
import * as strings from 'SiteDesignsWebPartStrings';
import { ISelectSiteState } from './ISelectSiteState';
import {
ActionButton,
Icon,
Spinner,
SpinnerSize,
MessageBar,
MessageBarType,
SearchBox,
Link
} from 'office-ui-fabric-react';
import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from "@pnp/spfx-controls-react/lib/ListView";
import { IViewSite } from './IViewSite';
import { SearchResults } from '@pnp/sp';
// ListView Columns
const viewFields: IViewField[] = [
{
name: 'icon',
render: ((item: any) => {
const image = <Icon iconName="FileASPX" />;
return image;
}),
maxWidth: 40,
},
{
name: 'title',
displayName: strings.WebSiteTitleLabel,
sorting: true,
isResizable: true,
maxWidth: 200
},
{
name: 'url',
render: ((item: IViewSite) =>{
const link = <Link href={item.url} target='blank'>{item.url}</Link>;
return link;
}),
displayName:strings.WebSiteUrl ,
sorting: true,
isResizable: true,
maxWidth: 200
}
];
export default class SelectSite extends React.Component<ISelectSiteProps, ISelectSiteState> {
private spService: spservice;
private items : IViewSite[];
public constructor(props) {
super(props);
// Initialize state
this.state = ({
isLoading: false,
showError: false,
errorMessage: '',
selectedItems: [],
items: [],
hasError:false,
showList: false
});
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this.onSearch = this.onSearch.bind(this);
this.onClear = this.onClear.bind(this);
}
/**
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof SelectSite
*/
private async onClear(ev: React.MouseEvent<HTMLButtonElement>){
ev.preventDefault();
this.items=[];
this.setState({showList:false, items: this.items, hasError: false, errorMessage:''});
}
/**
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof SelectSite
*/
private async onSearch(newValue:string) {
let results: SearchResults = null;
this.items=[];
this.setState({isLoading:true, errorMessage:''});
try{
results = await this.spService.getSites(newValue);
if (results && results.PrimarySearchResults ){
for ( const result of results.PrimarySearchResults){
this.items.push({key: result.UniqueId, title: result.Title, url: result.Path, webtemplate: result.WebTemplate});
}
}
this.setState({items: this.items, isLoading:false, errorMessage:'', showList:true});
}catch(error){
this.setState({hasError:true,isLoading:false, errorMessage: error.message, showList:false});
}
}
// Component Did Mount
/**
*
* @memberof SelectSite
*/
public async componentDidMount() {
//
}
/**
* On Render
*
* @returns {React.ReactElement<ISelectSiteProps>}
* @memberof SelectSite
*/
public render(): React.ReactElement<ISelectSiteProps> {
return (
<div className={styles.selectSite}>
<SearchBox
placeholder={strings.SearchBoxPlaceholderText}
onClear={this.onClear}
onSearch={this.onSearch}
/>
<br/>
{
this.state.isLoading ?
<Spinner size={SpinnerSize.large} label={strings.LoadingLabel} ariaLive="assertive" />
:
this.state.hasError ?
<MessageBar
messageBarType={MessageBarType.error}>
<span>{this.state.errorMessage}</span>
</MessageBar>
:
this.state.showList &&
<ListView
items={this.state.items}
viewFields={viewFields}
compact={false}
selectionMode={SelectionMode.multiple}
selection={this.props.onSelectItem}
/>
}
</div>
);
}
}

View File

@ -0,0 +1,6 @@
export interface IListViewItems {
'key': string;
DisplayName: string;
PrincipalName: string;
Rights?: number;
}

View File

@ -0,0 +1,20 @@
import { IListViewItems } from './IListViewItems';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
export interface ISiteDesignRightsState{
items: IListViewItems[];
isLoading:boolean;
disableCommandOption:boolean;
showPanel:boolean;
selectItem: IListViewItems[];
panelMode: panelMode;
hasError: boolean;
errorMessage: string;
showPanelAddScript: boolean;
showDialogDelete:boolean;
deleting:boolean;
disableDeleteButton: boolean;
showError: boolean;
}

View File

@ -0,0 +1,29 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
import { SiteScriptInfo, SiteDesignInfo } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
export interface ISiteDesignRightsProps {
SiteDesignSelectedItem: ISiteDesignSelectedItem;
context: WebPartContext;
mode: panelMode;
onDismiss(refresh?: boolean): void;
showPanel: boolean;
}
interface ISiteDesignSelectedItem {
key: string;
Description: string;
PreviewImageUrl: string;
SiteScriptIds: string;
Title: string;
WebTemplate: string;
Id: string;
numberSiteScripts:number;
IsDefault: boolean;
PreviewImageAltText:string;
Version: string;
runStatus: boolean;
}

View File

@ -0,0 +1,35 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.title {
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
vertical-align: top;
margin-right: 15px;
display: inline-block;
@include ms-font-m;
}
.label {
vertical-align: top;
margin-right: 15px;
display: inline-block;
@include ms-font-m;
width: 100px;
}
.listItem {
@include ms-font-l;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.image {
width: 36px;
height: 36px;
overflow: hidden;
border-radius: 50%;
}

View File

@ -0,0 +1,432 @@
// João Mendes
// Mar 2019
//
import * as React from 'react';
//import styles from '../../webparts/siteDesigns/components/SiteDesigns.module.scss';
import { ISiteDesignRightsProps } from './ISiteRightsProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from "@pnp/spfx-controls-react/lib/ListView";
import { IListViewItems } from './IListViewItems';
import spservice from '../../services/spservices';
import {
Icon,
IconType,
CommandBar,
Panel,
PanelType,
MessageBar,
MessageBarType,
Label,
Spinner,
SpinnerSize,
Dialog,
DialogType,
DialogFooter,
PrimaryButton,
DefaultButton,
ImageFit,
} from 'office-ui-fabric-react';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import * as strings from 'SiteDesignsWebPartStrings';
import { ISiteDesignRightsState } from './ISiteDesignRightsState';
import { SiteDesignInfo, SiteScriptInfo, SiteScriptUpdateInfo, SiteDesignUpdateInfo, SiteDesignPrincipals } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import styles from './SiteDesignRights.module.scss';
import { PeoplePicker, PrincipalType } from "@pnp/spfx-controls-react/lib/PeoplePicker";
// "https://outlook.office365.com/owa/service.svc/s/Ge…?email=anamendes@sitenanuvem.pt&UA=0&size=HR96x96"
// ListView Columns
const viewFields: IViewField[] = [
{
name: 'Icon',
render: ((item: IListViewItems) => {
// const image = <Icon iconName="UserFollowed" />;
const email = item.PrincipalName.replace('i:0#.f|membership|', "");
const image = <Icon iconType={IconType.image} imageProps={{ imageFit: ImageFit.cover, src: `/_layouts/15/userphoto.aspx?size=s&accountname=${email}`, className: styles.image }} />;
return image;
}),
maxWidth: 70,
},
{
name: 'DisplayName',
displayName: strings.ListViewColumnIdPrincipalNameLabel,
sorting: true,
isResizable: true,
maxWidth: 300,
minWidth: 300
}
];
export default class SiteDesignRights extends React.Component<ISiteDesignRightsProps, ISiteDesignRightsState> {
private spService: spservice;
private items: IListViewItems[] = [];
private refreshParent: boolean = false;
private siteDesignPrincipals: SiteDesignPrincipals[];
private AddPrincipal = React.lazy(() => import('../AddPrincipal/AddPrincipal' /* webpackChunkName: "addprincipal" */));
public constructor(props) {
super(props);
// Initialize state
this.state = ({
items: [],
isLoading: false,
disableCommandOption: true,
showPanel: false,
selectItem: [],
panelMode: panelMode.New,
hasError: false,
errorMessage: '',
showPanelAddScript: false,
showDialogDelete: false,
deleting: false,
disableDeleteButton: false,
showError: false
});
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this.getSelection = this.getSelection.bind(this);
this.onNewItem = this.onNewItem.bind(this);
this.onDeleteItem = this.onDeleteItem.bind(this);
this.onDismissPanel = this.onDismissPanel.bind(this);
this.onRefresh = this.onRefresh.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onDismissAddPrincipalPane = this.onDismissAddPrincipalPane.bind(this);
this.onCloseDialog = this.onCloseDialog.bind(this);
this.onDeleteConfirm = this.onDeleteConfirm.bind(this);
}
/**
*
* @private
* @param {boolean} refresh
* @memberof SiteDesignRights
*/
private onDismissAddPrincipalPane(refresh: boolean) {
this.setState({ showPanel: false });
if (refresh) {
this.refreshParent = true;
this.loadPrincipals();
}
}
// Get Selection Item from List
/**
*
* @private
* @param {IListViewItems[]} items
* @memberof SiteDesignRights
*/
private getSelection(items: IListViewItems[]) {
if (items.length > 0) {
this.setState({
disableCommandOption: false,
selectItem: items
});
} else {
this.setState({
disableCommandOption: true,
selectItem: [],
showPanel: false,
});
}
}
/**
* cancel event option SiteScrips
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof SiteDesignRights
*/
private onCancel(ev: React.MouseEvent<HTMLButtonElement>) {
this.props.onDismiss(this.refreshParent);
}
private onCloseDialog(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
this.setState({
showDialogDelete: false
});
}
/**
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof SiteDesignRights
*/
private async onDeleteConfirm(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
try {
let updateSiteDesignPrincipals: IListViewItems[] = this.items;
for (const item of this.state.selectItem) {
const idx = updateSiteDesignPrincipals.indexOf(item);
if (idx !== -1) {
updateSiteDesignPrincipals.splice(idx, 1);
}
}
this.setState({ deleting: true, disableDeleteButton: true });
const principals: string[] = [];
for (const item of this.state.selectItem) {
principals.push(item.PrincipalName.replace('i:0#.f|membership|', ""));
}
await this.spService.revokeSiteDesignRights(this.props.SiteDesignSelectedItem.Id, principals);
this.refreshParent = true;
this.setState({ deleting: false, disableDeleteButton: false, showDialogDelete: false, showError: false });
this.loadPrincipals();
} catch (error) {
console.log(error.message);
this.setState({ deleting: false, disableDeleteButton: true, showError: true, errorMessage: error.message });
}
}
/**
* Panel Dismiss CallBack
*
* @param {boolean} [refresh]
* @returns
* @memberof SiteDesignRights
*/
public async onDismissPanel(refresh?: boolean) {
this.setState({
showPanel: false
});
if (refresh) {
await this.loadPrincipals();
}
return;
}
// On New Item
/**
*
*
* @private
* @param {React.MouseEvent<HTMLElement>} e
* @memberof SiteDesignRights
*/
private onNewItem(e: React.MouseEvent<HTMLElement>) {
e.preventDefault();
this.setState({
panelMode: panelMode.New,
showPanel: true,
});
}
/**
* On Delete
*
* @private
* @param {React.MouseEvent<HTMLElement>} e
* @memberof SiteDesignRights
*/
private onDeleteItem(e: React.MouseEvent<HTMLElement>) {
e.preventDefault();
this.setState({
panelMode: panelMode.Delete,
showDialogDelete: true,
});
}
/**
* Load SiteDesignRights
*
* @private
* @memberof SiteDesignRights
*/
private async loadPrincipals() {
this.items = [];
this.setState({ isLoading: true });
try {
// check if user is Teanant Global Admin
const isGlobalAdmin = await this.spService.checkUserIsGlobalAdmin();
if (isGlobalAdmin) {
// get SiteDesignRights for SiteDesign
this.siteDesignPrincipals = await this.spService.getSiteDesignRights(this.props.SiteDesignSelectedItem.Id);
if (this.siteDesignPrincipals.length > 0) {
for (const siteDesignPrincipal of this.siteDesignPrincipals) {
this.items.push(
{
key: siteDesignPrincipal.PrincipalName,
DisplayName: siteDesignPrincipal.DisplayName,
PrincipalName: siteDesignPrincipal.PrincipalName
}
);
}
}
this.setState({ items: this.items, isLoading: false, disableCommandOption: true });
} else {
this.setState({
items: this.items,
hasError: true,
errorMessage: strings.ErrorMessageUserNotAdmin,
isLoading: false
});
}
}
catch (error) {
this.setState({
items: this.items,
hasError: true,
errorMessage: error.message,
isLoading: false
});
}
}
/** Refresh
*
* @param {React.MouseEvent<HTMLElement>} ev
* @memberof SiteDesignRights
*/
public onRefresh(ev: React.MouseEvent<HTMLElement>) {
ev.preventDefault();
// loadPrincipals
this.loadPrincipals();
}
/**
* Component Did Mount
*
* @memberof SiteDesignRights
*/
public async componentDidMount() {
// loadPrincipals
await this.loadPrincipals();
}
// On Render
public render(): React.ReactElement<ISiteDesignRightsProps> {
return (
<div>
<Panel isOpen={this.props.showPanel} onDismiss={this.onCancel} type={PanelType.medium} headerText={strings.SiteDesignRightsPanelTitle}>
<div>
<span className={styles.label}>{strings.SiteDesignIdLabel}</span>
<span className={styles.title}>{this.props.SiteDesignSelectedItem.Id}</span>
</div>
<div>
<span className={styles.label}>{strings.SiteDesignRightsPanelTitle}</span>
<span className={styles.title}>{this.props.SiteDesignSelectedItem.Title}</span>
</div>
<div>
<span className={styles.label}>{strings.WebTemplateLabel}</span>
<span className={styles.title}>{this.props.SiteDesignSelectedItem.WebTemplate === '64' ? strings.WebTemplateTeamSite : strings.WebTemplateCommunicationSite}</span>
</div>
<br />
{
this.state.isLoading ?
<Spinner size={SpinnerSize.large} label={strings.LoadingLabel} ariaLive="assertive" />
:
this.state.hasError ?
<MessageBar
messageBarType={MessageBarType.error}>
<span>{this.state.errorMessage}</span>
</MessageBar>
:
<div style={{ marginBottom: 10 }}>
<CommandBar
items={[
{
key: 'newItem',
name: strings.CommandbarNewLabel,
iconProps: {
iconName: 'Add'
},
onClick: this.onNewItem,
},
{
key: 'delete',
name: strings.CommandbarDeleteLabel,
iconProps: {
iconName: 'Delete'
},
onClick: this.onDeleteItem,
disabled: this.state.disableCommandOption,
}
]}
farItems={[
{
key: 'refresh',
name: strings.CommandbarRefreshLabel,
iconProps: {
iconName: 'Refresh'
},
onClick: this.onRefresh,
}
]}
/>
</div>
}
{
!this.state.hasError && !this.state.isLoading &&
<ListView
items={this.state.items}
viewFields={viewFields}
compact={false}
selectionMode={SelectionMode.multiple}
selection={this.getSelection}
showFilter={true}
filterPlaceHolder={strings.SearchPlaceholder}
/>
}
{
this.state.showPanel && this.state.panelMode == panelMode.New &&
<React.Suspense fallback={<div>Loading...</div>}>
<this.AddPrincipal
showPanel={this.state.showPanel}
onDismiss={this.onDismissAddPrincipalPane}
context={this.props.context}
siteDesignInfo={this.props.SiteDesignSelectedItem}
/>
</React.Suspense>
}
<Dialog
hidden={!this.state.showDialogDelete}
onDismiss={this.onCloseDialog}
dialogContentProps={{
type: DialogType.normal,
title: strings.DialogConfirmDeleteTitle,
}}
modalProps={{
isBlocking: true,
}}
>
<p>{strings.DialogConfirmDeleteText}</p>
<br />
{
this.state.showError &&
<div style={{ marginTop: '15px' }}>
<MessageBar messageBarType={MessageBarType.error} >
<span>{this.state.errorMessage}</span>
</MessageBar>
</div>
}
<br />
<DialogFooter>
{
this.state.deleting &&
<div style={{ display: "inline-block", marginRight: '10px', verticalAlign: 'middle' }}>
<Spinner size={SpinnerSize.small} ariaLive="assertive" />
</div>
}
<DefaultButton onClick={this.onDeleteConfirm} text={strings.ButtonDeleteLabel} disabled={this.state.disableDeleteButton} />
<PrimaryButton onClick={this.onCloseDialog} text={strings.ButtonCancelLabel} />
</DialogFooter>
</Dialog>
</Panel>
</div >
);
}
}

View File

@ -0,0 +1,134 @@
import * as React from 'react';
import JSONEditor from "jsoneditor";
var schema = require("./../json-editor-wrapper/site-design-script-actions.schema.json");
import "jsoneditor/dist/jsoneditor.css";
export interface JsonEditorProps {
onChange: (json: any) => void;
onDirty: () => void;
value: object;
options: any;
width: string;
height: string;
className?: string;
config?: object;
onValidate?: (error:boolean) => void;
}
export default class JsonEditorReact extends React.PureComponent<JsonEditorProps,
any> {
private timeout: any = undefined;
private editor: JSONEditor;
private div: any;
public static defaultProps: Partial<JsonEditorProps> = {
config: {},
onDirty: () => { }
};
constructor(props: JsonEditorProps) {
super(props);
this.state = {
currentValue: props.value,
dirty: false
};
this.onValidate = this.onValidate.bind(this);
}
private handleChange = () => {
this.setState({
currentValue: this
.editor
.get(),
dirty: true
});
}
private onValidate(json) {
const vl = this.props.options.ajv.compile(schema);
if (vl(json) && json.actions && json.actions.length > 0 && json['$schema'] && json.bindata && json.version){
this.props.onValidate(true);
}else{
this.props.onValidate(false);
}
}
private handleFocus = () => {
this.setState({ controllingFocus: true });
}
private handleBlur = () => {
this.setState({ controllingFocus: false });
}
public componentDidMount() {
const { value, options } = this.props;
const mergedOptions = {
...options,
onChange: this.handleChange,
onValidate: this.onValidate,
};
this.editor = new JSONEditor(this.div, mergedOptions);
this
.editor
.set(value);
}
public componentDidUpdate(prevProps: JsonEditorProps, prevState: JsonEditorProps) {
const { onChange, onDirty } = this.props;
if (this.state.dirty === true && this.state.controllingFocus) {
onDirty();
}
//if (this.state.dirty === true && !this.state.controllingFocus) {
if (this.state.dirty === true) {
this.timeout = setTimeout(() => {
onChange(this.state.currentValue);
this.setState({ dirty: false });
}, 200);
}
//if (this.state.controllingFocus) {
// clearTimeout(this.timeout);
//}
if (prevProps.value !== this.props.value && !this.state.controllingFocus) {
this
.editor
.set(this.props.value);
this.setState({ currentValue: this.props.value, dirty: false });
}
}
public componentWillUnmount() {
this
.editor
.destroy();
delete this.editor;
clearTimeout(this.timeout);
}
public render() {
const { className } = this.props;
return (<div
onFocus={this.handleFocus}
onBlur={this.handleBlur}
className={className}
style={{ height: this.props.height , width: this.props.width}}
ref={div => {
this.div = div;
}} />);
}
}

View File

@ -0,0 +1,8 @@
div.jsoneditor-menu {
color: white;
background-color: #414141;
border-bottom: 1px solid #aeaeae;
}
div.jsoneditor-menu .jsoneditor-poweredBy{
display: none;
}

View File

@ -0,0 +1,41 @@
import * as React from "react";
import JsonEditorReact from "../../controls/json-editor-react";
import { ISiteScript } from "../../types/ISiteScript";
var schema = require("./site-design-script-actions.schema.json");
import "./index.css";
import * as Ajv from 'ajv';
interface IJsonEditorWrapperProps {
currentSiteScript: ISiteScript | null | undefined;
setSiteScript: (siteScript: ISiteScript) => void;
onValidate: (error:boolean) => void;
}
const JsonEditorWrapper: React.SFC<IJsonEditorWrapperProps> = props => {
// Editor configuration. See jsoneditor's API.
var ajv = new Ajv();
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
ajv.addSchema(schema, 'JSON Schema for SiteScript Extensions');
const options = {
mode: "code",
schema,
ajv,
};
return (
<JsonEditorReact
value={props.currentSiteScript ? props.currentSiteScript : {}}
onChange={props.setSiteScript}
onDirty={() => {}}
options={options}
height={"300px"}
width={"100%"}
onValidate={props.onValidate}
/>
);
};
export default JsonEditorWrapper;

View File

@ -0,0 +1,907 @@
{
"$schema": "http://json-schema.org/draft-04/schema",
"title": "JSON Schema for SiteScript Extensions",
"definitions": {
"baseAction": {
"type": "object",
"properties": {
"verb": { "type": "string" }
},
"required": [ "verb" ]
},
"activateSPFeature": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^activateSPFeature$"
},
"featureId": {
"type": "string",
"description": "The Guid of the feature to activate."
}
},
"required": [ "featureId", "verb" ]
}
]
},
"associateExtension": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^associateExtension$"
},
"title": {
"type": "string",
"description": "Title of the extension."
},
"location": {
"type": "string",
"pattern": "^ClientSideExtension\\..*$",
"description": "If this extension creates commands, where the commands are display. Otherwise, this should be set to ClientSideExtension.ApplicationCustomizer."
},
"clientSideComponentId": {
"type": "string",
"description": "GUID of the extension."
},
"clientSideComponentProperties": {
"type": "string",
"description": "This is an optional parameter, which can be used to provide properties for the instance."
},
"registrationId": {
"type": "string",
"description": "This is an optional parameter, which indicates the type of the list to which extension is associated, if the extension is associated with lists."
},
"registrationType": {
"type": "string",
"pattern": "^$|^List$",
"description": "This is an optional parameter, which should be specified if the extension is associated with a list."
},
"scope": {
"type": "string",
"pattern": "^(Web|Site)$",
"description": "Indicates whether the extension is associated with a Web or a Site."
}
},
"required": [ "clientSideComponentId", "title", "location", "verb", "scope" ]
},
"createSPList": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^createSPList$"
},
"listName": {
"type": "string",
"description": "An identifying name for the list."
},
"templateType": {
"type": "number",
"description": "Specifies the type of a list definition or a list template."
},
"subactions": {
"description": "Sub-actions to run on the list.",
"type": "array",
"items": {
"anyOf": [
{ "$ref": "#/definitions/SPListSubactions/setTitle" },
{ "$ref": "#/definitions/SPListSubactions/setDescription" },
{ "$ref": "#/definitions/SPListSubactions/addSPField" },
{ "$ref": "#/definitions/SPListSubactions/deleteSPField" },
{ "$ref": "#/definitions/SPListSubactions/addSPFieldXml" },
{ "$ref": "#/definitions/SPListSubactions/addSPLookupFieldXml" },
{ "$ref": "#/definitions/SPListSubactions/addSiteColumn" },
{ "$ref": "#/definitions/SPListSubactions/addSPView" },
{ "$ref": "#/definitions/SPListSubactions/removeSPView" },
{ "$ref": "#/definitions/SPListSubactions/addContentType" },
{ "$ref": "#/definitions/SPListSubactions/removeContentType" },
{ "$ref": "#/definitions/SPListSubactions/setSPFieldCustomFormatter" },
{ "$ref": "#/definitions/SPListSubactions/associateFieldCustomizer" },
{ "$ref": "#/definitions/SPListSubactions/associateListViewCommandSet" }
]
},
"additionalItems": false
}
},
"required": [ "listName", "templateType", "verb" ]
}
]
},
"SPListSubactions": {
"addContentType": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^addContentType$"
},
"name": {
"type": "string",
"description": "The name of the ContentType to add."
}
},
"required": [ "name", "verb" ]
},
"removeContentType": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^removeContentType$"
},
"name": {
"type": "string",
"description": "The name of the ContentType to remove."
}
},
"required": [ "name", "verb" ]
},
"setSPFieldCustomFormatter": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^setSPFieldCustomFormatter$"
},
"fieldDisplayName": {
"type": "string",
"description": "The DisplayName of the SPField to operate on."
},
"formatterJSON": {
"type": "object",
"description": "A JSON object to use as the field CustomFormatter."
}
},
"required": [ "fieldDisplayName", "verb", "formatterJSON" ]
},
"setTitle": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^setTitle$"
},
"title": {
"type": "string",
"description": "The new title of the list."
}
},
"required": [ "title", "verb" ]
},
"setDescription": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^setDescription$"
},
"description": {
"type": "string",
"description": "The new description of the list."
}
},
"required": [ "description", "verb" ]
},
"addSiteColumn": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^addSiteColumn$"
},
"internalName": {
"type": "string",
"description": "The internal name of the site column to add."
},
"addToDefaultView": {
"type": "boolean",
"description": "Optional attribute that defaults to false. If true, the newly added field will also be added to the default view."
}
},
"required": [ "internalName", "verb" ]
},
"addSPField": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^addSPField$"
},
"fieldType": {
"type": "string",
"pattern": "^(Text|Note|Number|Boolean|User|DateTime)$",
"description": "The type of field to add."
},
"displayName": {
"type": "string",
"description": "The display name of the field."
},
"internalName": {
"type": "string",
"description": "Optional attribute. If provided, specifies the internal name of the field. If not provided, the internal name will be based on the display name"
},
"isRequired": {
"type": "boolean",
"description": "Whether this field is required to contain information."
},
"addToDefaultView": {
"type": "boolean",
"description": "Optional attribute that defaults to false. If true, the newly added field will also be added to the default view."
},
"enforceUnique": {
"type": "boolean",
"description": "Optional attribute that defaults to false. If true, then all values for this field must be unique."
}
},
"required": [ "displayName", "fieldType", "isRequired", "verb" ]
},
"deleteSPField": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^deleteSPField$"
},
"displayName": {
"type": "string",
"description": "The display name of the field to delete."
}
},
"required": [ "displayName" ]
},
"addSPFieldXml": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^addSPFieldXml$"
},
"schemaXml": {
"type": "string",
"description": "The schemaXml specifying the field. See https://msdn.microsoft.com/en-us/library/office/aa979575.aspx"
},
"addToDefaultView": {
"type": "boolean",
"description": "Optional attribute that defaults to false. If true, the newly added field will also be added to the default view."
}
},
"required": [ "schemaXml", "verb" ]
},
"addSPLookupFieldXml": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^addSPLookupFieldXml$"
},
"schemaXml": {
"type": "string",
"description": "The schemaXml specifying the field. See https://msdn.microsoft.com/en-us/library/office/aa979575.aspx"
},
"targetListName": {
"type": "string",
"description": "The name that identifies the list this lookup field is targeted against. Provide either this or targetListUrl."
},
"targetListUrl": {
"type": "string",
"description": "A web-relative url that identifies the list this lookup field is targeted against. Provide either this or targetListName."
},
"addToDefaultView": {
"type": "boolean",
"description": "Optional attribute that defaults to false. If true, the newly added field will also be added to the default view."
}
},
"required": [ "schemaXml", "verb" ]
},
"associateFieldCustomizer": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^associateFieldCustomizer$"
},
"internalName": {
"type": "string",
"description": "The internalName of the field to customize."
},
"clientSideComponentId": {
"type": "string",
"description": "GUID of the extension to use to customize the field."
},
"clientSideComponentProperties": {
"type": "string",
"description": "This is an optional parameter, which can be used to provide properties for the Field Customizer instance."
}
},
"required": [ "clientSideComponentId", "internalName", "verb" ]
},
"associateListViewCommandSet": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^associateListViewCommandSet$"
},
"title": {
"type": "string",
"description": "Title of the extension."
},
"location": {
"type": "string",
"pattern": "^(ClientSideExtension\\.ListViewCommandSet\\.ContextMenu|ClientSideExtension\\.ListViewCommandSet\\.CommandBar|ClientSideExtension\\.ListViewCommandSet)$",
"description": "Where the commands are displayed."
},
"clientSideComponentId": {
"type": "string",
"description": "GUID of the extension to use to customize the field."
},
"clientSideComponentProperties": {
"type": "string",
"description": "This is an optional parameter, which can be used to provide properties for the Field Customizer instance."
}
},
"required": [ "clientSideComponentId", "title", "location", "verb" ]
},
"addSPView": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^addSPView$"
},
"name": {
"type": "string",
"description": "The name of the view."
},
"viewFields": {
"description": "An array of the internal names of the fields of the view.",
"type": "array",
"items": {
"type": "string"
}
},
"query": {
"type": "string",
"description": "A CAML query string that contains the where clause for the view's query. See https://msdn.microsoft.com/en-us/library/ms462365"
},
"rowLimit": {
"type": "integer",
"description": "The row limit of the view."
},
"isPaged": {
"type": "boolean",
"description": "Whether the view is paged."
},
"makeDefault": {
"type": "boolean",
"description": "If true, the view will be made the default view of the list."
},
"scope": {
"type": "string",
"pattern": "^(Default|Recursive|RecursiveAll|FilesOnly)$",
"description": "Optional. The scope of the view. See https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spviewscope.aspx"
},
"formatterJSON": {
"type": "object",
"description": "A JSON object to use as the view CustomFormatter."
}
},
"required": [ "name", "viewFields", "query", "rowLimit", "isPaged", "makeDefault" ]
},
"removeSPView": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^removeSPView$"
},
"name": {
"type": "string",
"description": "The name of the view to remove."
}
},
"required": [ "name" ]
}
},
"createPage": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^createPage$"
},
"fileName": {
"type": "string",
"pattern": "^.+\\.aspx&",
"description": "The file name of the page."
},
"pageData": {
"type": "object",
"properties": {
"Title": {
"type": "string",
"description": "The title of the page."
},
"BannerImageUrl": {
"type": "string",
"description": "The URL of the image to be displayed in the banner."
},
"CanvasContent1": {
"type": "string",
"description": "The content to be displayed in the canvas, as a string of XML."
},
"LayoutWebpartsContent": {
"type": "string",
"description": "Information about the page layout, as a string of JSON."
}
},
"required": [ "Title", "BannerImageUrl", "CanvasContent1", "LayoutWebpartsContent" ]
},
"setAsHomePage": {
"type": "boolean",
"description": "Whether or not to set this page as the homepage. The default is false."
}
},
"required": [ "fileName", "pageData", "verb" ]
}
]
},
"addPrincipalToSPGroup": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^addPrincipalToSPGroup$"
},
"principal": {
"type": "string",
"description": "The name of principal to add to the SPGroup."
},
"group": {
"type": "string",
"description": "The SPGroup to add the principal to.",
"pattern": "^(Members|Owners|Visitors)$"
}
},
"required": [ "principal", "group", "verb" ]
}
]
},
"applyTheme": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^applyTheme$"
},
"themeName": {
"type": "string",
"description": "The name of the theme to apply."
}
},
"required": [ "themeName", "verb" ]
}
]
},
"setSiteLogo": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^setSiteLogo$"
},
"url": {
"type": "string",
"description": "The URL of the new SiteLogo."
}
},
"required": [ "url", "verb" ]
}
]
},
"triggerFlow": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^triggerFlow$"
},
"url": {
"type": "string",
"description": "The trigger URL of the Flow."
},
"name": {
"type": "string",
"description": "The name of the Flow."
},
"waitForReply": {
"type": "boolean",
"description": "If true, the script will expect that the Flow will call Microsoft.SharePoint.Utilities.SiteScriptUtility.SetSiteScriptStageOutcome() when it has finished executing. Note that this will not cause the script to block. The script will keep executing, but for record-keeping purposes, the run will be considered incomplete until the reply is made."
},
"parameters": {
"type": "object",
"description": "An option set of parameters to pass into the Flow."
}
},
"required": [ "name", "url", "verb" ]
}
]
},
"installSolution": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^installSolution$"
},
"id": {
"type": "string",
"description": "The ID of the Solution."
},
"name": {
"type": "string",
"description": "The name of the Solution."
}
},
"required": [ "id", "verb", "name" ]
}
]
},
"addNavLink": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^addNavLink$"
},
"url": {
"type": "string",
"description": "The url of the link to add."
},
"displayName": {
"type": "string",
"description": "The display name of the link"
},
"isWebRelative": {
"type": "boolean",
"description": "Optionally indicates whether the link is web-relative. The default is false."
},
"parentDisplayName": {
"type": "string",
"description": "Optional parameter. If provided, makes this nav link a child of the nav link with this displayName. If both this and parentUrl are provided, it searches for a link that matches both to be the parent."
},
"parentUrl": {
"type": "string",
"description": "Optional parameter. If provided, makes this nav link a child of the nav link with this url. If both this and parentDisplayName are provided, it searches for a link that matches both to be the parent."
},
"isParentUrlWebRelative": {
"type": "boolean",
"description": "Optional parameter. Indicates whether parentUrl is web-relative. The default is false."
}
},
"required": [ "displayName", "url", "verb" ]
}
]
},
"removeNavLink": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^removeNavLink$"
},
"url": {
"type": "string",
"description": "Optionally indicates the url of the link to remove. if this parameter is not provided, the link to be removed will be found based on the displayName only."
},
"displayName": {
"type": "string",
"description": "The display name of the link to remove."
},
"isWebRelative": {
"type": "boolean",
"description": "Optionally indicates whether the link is web-relative. The default is false."
}
},
"required": [ "displayName", "verb" ]
}
]
},
"joinHubSite": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^joinHubSite$"
},
"hubSiteId": {
"type": "string",
"description": "A Guid specifying the hub site."
},
"name": {
"type": "string",
"description": "An optional string specifying the name of the hub site."
}
},
"required": [ "hubSiteId", "verb" ]
}
]
},
"createContentType": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^createContentType$"
},
"name": {
"type": "string",
"description": "Name of the content type."
},
"description": {
"type": "string",
"description": "Optional description of the content type."
},
"parentName": {
"type": "string",
"description": "Name of the parent content type. Provide either this, parentId, or id."
},
"parentId": {
"type": "string",
"description": "Id of the parent content type. Provide either this, parentName, or id."
},
"id": {
"type": "string",
"description": "Id of the content type. Provide either this, parentName, or parentId."
},
"hidden": {
"type": "boolean",
"description": "Whether the content type is hidden."
},
"subactions": {
"description": "Sub-actions to run on the content type.",
"type": "array",
"items": {
"anyOf": [
{ "$ref": "#/definitions/SPContentTypeSubactions/addSiteColumn" },
{ "$ref": "#/definitions/SPContentTypeSubactions/removeSiteColumn" }
]
},
"additionalItems": false
}
},
"required": [ "verb", "name", "hidden" ]
}
]
},
"SPContentTypeSubactions": {
"addSiteColumn": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^addSiteColumn$"
},
"internalName": {
"type": "string",
"description": "The internal name of the site column to add."
}
},
"required": [ "internalName", "verb" ]
},
"removeSiteColumn": {
"type": "object",
"properties": {
"verb": {
"type": "string",
"pattern": "^removeSiteColumn$"
},
"internalName": {
"type": "string",
"description": "The internal name of the site column to remove."
}
},
"required": [ "internalName", "verb" ]
}
},
"createSiteColumn": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^createSiteColumn$"
},
"fieldType": {
"type": "string",
"pattern": "^(Text|Note|Number|Boolean|User|DateTime)$",
"description": "The type of column to add."
},
"internalName": {
"type": "string",
"description": "Internal name of the column"
},
"displayName": {
"type": "string",
"description": "Optional display name of the column."
},
"isRequired": {
"type": "boolean",
"description": "Whether this column is required to contain information."
},
"group": {
"type": "string",
"description": "Optional attribute. If provided, sets the column group to which this column belongs."
},
"enforceUnique": {
"type": "boolean",
"description": "Optional attribute that defaults to false. If true, then all values for this column must be unique."
}
},
"required": [ "fieldType", "verb", "internalName", "isRequired" ]
}
]
},
"createSiteColumnXml": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^createSiteColumnXml$"
},
"schemaXml": {
"type": "string",
"description": "The schemaXml specifying the field. See https://msdn.microsoft.com/en-us/library/office/aa979575.aspx"
},
"pushChanges": {
"type": "boolean",
"description": "Indicates whether this change should be pushed to lists that already reference this field. Defaults to true."
}
},
"required": [ "verb", "schemaXml" ]
}
]
},
"setSiteExternalSharingCapability": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^setSiteExternalSharingCapability$"
},
"capability": {
"type": "string",
"pattern": "^(Disabled|ExistingExternalUserSharingOnly|ExternalUserSharingOnly|ExternalUserAndGuestSharing)$",
"description": "A string specifying the sharing capability for the site."
}
},
"required": [ "capability", "verb" ]
}
]
},
"setRegionalSettings": {
"type": "object",
"allOf": [
{ "$ref": "#/definitions/baseAction" },
{
"properties": {
"verb": {
"type": "string",
"pattern": "^setRegionalSettings$"
},
"timeZone": {
"type": "integer",
"description": "A number specifying the time zone. See https://msdn.microsoft.com/library/microsoft.sharepoint.spregionalsettings.timezones.aspx"
},
"locale": {
"type": "integer",
"description": "A number specifying the culture by lcid. See https://msdn.microsoft.com/en-us/library/ms912047(v=winembedded.10).aspx"
},
"sortOrder": {
"type": "integer",
"description": "A number specifying the sort order. See https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spregionalsettings.collation.aspx"
},
"hourFormat": {
"type": "string",
"pattern": "^(12|24)$",
"description": "Specifies whether the site should use 12-hour or 24-hour time."
}
},
"required": [ "sortOrder", "verb" ]
}
]
}
},
"type": "object",
"properties": {
"actions": {
"title": "array of supported actions",
"type": "array",
"items": {
"anyOf": [
{ "$ref": "#/definitions/activateSPFeature" },
{ "$ref": "#/definitions/associateExtension" },
{ "$ref": "#/definitions/addPrincipalToSPGroup" },
{ "$ref": "#/definitions/addNavLink" },
{ "$ref": "#/definitions/applyTheme" },
{ "$ref": "#/definitions/createContentType" },
{ "$ref": "#/definitions/createPage" },
{ "$ref": "#/definitions/createSiteColumn" },
{ "$ref": "#/definitions/createSiteColumnXml" },
{ "$ref": "#/definitions/createSPList" },
{ "$ref": "#/definitions/installSolution" },
{ "$ref": "#/definitions/joinHubSite" },
{ "$ref": "#/definitions/removeNavLink" },
{ "$ref": "#/definitions/setRegionalSettings" },
{ "$ref": "#/definitions/setSiteLogo" },
{ "$ref": "#/definitions/setSiteExternalSharingCapability" },
{ "$ref": "#/definitions/triggerFlow" }
]
},
"additionalItems": false
},
"$schema": {
"type": "string"
},
"bindata": {
"type": "object",
"title": "Binary Data",
"description": "guid<->base64encodedstrings key-value pairs",
"patternProperties": {
"^[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}$": {
"type": "string"
}
}
},
"version": { "type": "number" }
},
"additionalProperties": false
}

View File

@ -0,0 +1,8 @@
export interface IListViewItems {
'key': string;
Description: string;
Title: string;
Id: string;
Version?: string;
SiteScriptsId?: string[];
}

View File

@ -0,0 +1,28 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
import { SiteScriptInfo, SiteDesignInfo } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
export interface ISiteScriptsProps {
SiteDesignSelectedItem: ISiteDesignSelectedItem;
context: WebPartContext;
mode: panelMode;
onDismiss(refresh?: boolean): void;
showPanel: boolean;
}
interface ISiteDesignSelectedItem {
key: string;
Description: string;
Id: string;
Title: string;
WebTemplate:string;
SiteScriptIds: string;
numberSiteScripts: number;
IsDefault: boolean;
PreviewImageAltText: string;
PreviewImageUrl: string;
Version: string;
}

View File

@ -0,0 +1,21 @@
import { IListViewItems } from './IListViewItems';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
export interface ISiteScriptsState{
items: IListViewItems[];
isLoading:boolean;
disableCommandOption:boolean;
showPanel:boolean;
selectItem: IListViewItems[];
panelMode: panelMode;
hasError: boolean;
errorMessage: string;
showPanelAddScript: boolean;
showDialogDelete:boolean;
deleting:boolean;
disableDeleteButton: boolean;
showError: boolean;
showCommmandEdit: string;
}

View File

@ -0,0 +1,486 @@
// João Mendes
// Mar 2019
//
import * as React from 'react';
//import styles from '../../webparts/siteDesigns/components/SiteDesigns.module.scss';
import { ISiteScriptsProps } from './ISiteScriptsProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from "@pnp/spfx-controls-react/lib/ListView";
import { IListViewItems } from './IListViewItems';
import spservice from '../../services/spservices';
import {
Icon,
IconType,
CommandBar,
Panel,
PanelType,
MessageBar,
MessageBarType,
Label,
Spinner,
SpinnerSize,
Dialog,
DialogType,
DialogFooter,
PrimaryButton,
DefaultButton,
} from 'office-ui-fabric-react';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import * as strings from 'SiteDesignsWebPartStrings';
import { ISiteScriptsState } from './ISiteScriptsState';
import { SiteDesignInfo, SiteScriptInfo, SiteScriptUpdateInfo, SiteDesignUpdateInfo } from '@pnp/sp';
import { panelMode } from '../../webparts/siteDesigns/components/IEnumPanel';
import styles from './siteScript.module.scss';
// ListView Columns
const viewFields: IViewField[] = [
{
name: 'Image',
render: ((item: IListViewItems) => {
const image = <Icon iconName="FileCode" />;
return image;
}),
maxWidth: 70,
},
{
name: 'Id',
displayName: strings.ListViewColumnIdLabel,
sorting: true,
isResizable: true,
maxWidth: 200
},
{
name: 'Title',
displayName: strings.TitleFieldLabel,
sorting: true,
isResizable: true,
maxWidth: 250
},
{
name: 'Description',
displayName: strings.ListViewColumnDescriptionLabel,
sorting: true,
isResizable: true,
maxWidth: 250
},
{
name: 'Version',
displayName: "Version",
sorting: true,
isResizable: true,
maxWidth: 65
}
];
export default class SiteScripts extends React.Component<ISiteScriptsProps, ISiteScriptsState> {
private spService: spservice;
private items: IListViewItems[] = [];
private refreshParent: boolean = false;
private siteScripts: string[];
private AddSiteScriptToSiteDesignDialog = React.lazy(() => import('../AddSiteScriptToSiteDesign/AddSiteScriptToSiteDesign' /* webpackChunkName: "addscriptdialog" */));
private EditScriptDialog = React.lazy(() => import('../../controls/EditSiteScript/EditSiteScript' /* webpackChunkName: "editscriptdialog" */));
public constructor(props) {
super(props);
// Initialize state
this.state = ({
items: [],
isLoading: false,
disableCommandOption: true,
showPanel: false,
selectItem: [],
panelMode: panelMode.New,
hasError: false,
errorMessage: '',
showPanelAddScript: false,
showDialogDelete: false,
deleting: false,
disableDeleteButton: false,
showError: false,
showCommmandEdit: ''
});
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this.getSelection = this.getSelection.bind(this);
this.onNewItem = this.onNewItem.bind(this);
this.onEditItem = this.onEditItem.bind(this);
this.onDeleteItem = this.onDeleteItem.bind(this);
this.onDismissPanel = this.onDismissPanel.bind(this);
this.onRefresh = this.onRefresh.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onDismissAddScriptPanel = this.onDismissAddScriptPanel.bind(this);
this.onCloseDialog = this.onCloseDialog.bind(this);
this.onDeleteConfirm = this.onDeleteConfirm.bind(this);
}
/**
*
*
* @private
* @param {boolean} refresh
* @memberof SiteScripts
*/
private onDismissAddScriptPanel(refresh: boolean) {
this.setState({ showPanel: false });
if (refresh) {
this.refreshParent = true;
this.loadSiteScripts();
}
}
// Get Selection Item from List
/**
*
*
* @private
* @param {IListViewItems[]} items
* @memberof SiteScripts
*/
private getSelection(items: IListViewItems[]) {
if (items.length > 0) {
this.setState({
disableCommandOption: false,
selectItem: items,
});
} else {
this.setState({
disableCommandOption: true,
selectItem: [],
showPanel: false,
});
}
}
/**
* cancel event option SiteScrips
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof SiteScripts
*/
private onCancel(ev: React.MouseEvent<HTMLButtonElement>) {
this.props.onDismiss(this.refreshParent);
}
private onCloseDialog(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
this.setState({
showDialogDelete: false
});
}
/**
*
*
* @private
* @param {React.MouseEvent<HTMLButtonElement>} ev
* @memberof SiteScripts
*/
private async onDeleteConfirm(ev: React.MouseEvent<HTMLButtonElement>) {
ev.preventDefault();
try {
let updateSiteScripts: string[] = this.siteScripts;
for (const item of this.state.selectItem) {
const idx = updateSiteScripts.indexOf(item.Id);
if (idx !== -1) {
updateSiteScripts.splice(idx, 1);
}
}
this.setState({ deleting: true, disableDeleteButton: true });
const siteDesignUpdateInfo: SiteDesignUpdateInfo = { Id: this.props.SiteDesignSelectedItem.Id, SiteScriptIds: updateSiteScripts };
const result = await this.spService.updateSiteDesign(siteDesignUpdateInfo);
this.refreshParent = true;
this.setState({ deleting: false, disableDeleteButton: false, showDialogDelete: false, showError: false });
this.loadSiteScripts();
} catch (error) {
console.log(error.message);
this.setState({ deleting: false, disableDeleteButton: true, showError: true, errorMessage: error.message });
}
}
/**
* Panel Dismiss CallBack
*
* @param {boolean} [refresh]
* @returns
* @memberof SiteScripts
*/
public async onDismissPanel(refresh?: boolean) {
this.setState({
showPanel: false
});
if (refresh) {
await this.loadSiteScripts();
}
return;
}
// On New Item
/**
*
*
* @private
* @param {React.MouseEvent<HTMLElement>} e
* @memberof SiteScripts
*/
private onNewItem(e: React.MouseEvent<HTMLElement>) {
e.preventDefault();
this.setState({
panelMode: panelMode.New,
showPanel: true,
});
}
/**
* On Delete
*
* @private
* @param {React.MouseEvent<HTMLElement>} e
* @memberof SiteScripts
*/
private onDeleteItem(e: React.MouseEvent<HTMLElement>) {
e.preventDefault();
this.setState({
panelMode: panelMode.Delete,
showDialogDelete: true,
});
}
/**
* On Edit item
*
* @private
* @param {React.MouseEvent<HTMLElement>} e
* @memberof SiteScripts
*/
private onEditItem(e: React.MouseEvent<HTMLElement>) {
e.preventDefault();
this.setState({
panelMode: panelMode.edit,
showPanel: true
});
}
/**
* Load SiteScripts
*
* @private
* @memberof SiteScripts
*/
private async loadSiteScripts() {
this.items = [];
this.setState({ isLoading: true });
try {
// check if user is Teanant Global Admin
const isGlobalAdmin = await this.spService.checkUserIsGlobalAdmin();
if (isGlobalAdmin) {
// get SiteScripts for SiteDesign
const siteDesignInfo: SiteDesignInfo = await this.spService.getSiteDesignMetadata(this.props.SiteDesignSelectedItem.Id);
this.siteScripts = siteDesignInfo.SiteScriptIds;
if (this.siteScripts.length > 0) {
for (const siteScriptId of this.siteScripts) {
if (siteScriptId === "") continue;
const siteScript: SiteScriptInfo = await this.spService.getSiteScriptMetadata(siteScriptId);
this.items.push(
{
key: siteScript.Id,
Description: siteScript.Description,
Id: siteScript.Id,
Title: siteScript.Title,
Version: siteScript.Version
}
);
}
}
this.setState({ items: this.items, isLoading: false, disableCommandOption: true });
} else {
this.setState({
items: this.items,
hasError: true,
errorMessage: strings.ErrorMessageUserNotAdmin,
isLoading: false
});
}
}
catch (error) {
this.setState({
items: this.items,
hasError: true,
errorMessage: error.message,
isLoading: false
});
}
}
/** Refresh
*
* @param {React.MouseEvent<HTMLElement>} ev
* @memberof SiteScripts
*/
public onRefresh(ev: React.MouseEvent<HTMLElement>) {
ev.preventDefault();
// loadSiteScripts
this.loadSiteScripts();
}
/**
* Component Did Mount
*
* @memberof SiteScripts
*/
public async componentDidMount() {
// loadSiteScripts
await this.loadSiteScripts();
}
// On Render
public render(): React.ReactElement<ISiteScriptsProps> {
return (
<div>
<Panel isOpen={this.props.showPanel} onDismiss={this.onCancel} type={PanelType.large} headerText="Site Scripts">
<div>
<span className={styles.label}>SiteDesign Id:</span>
<span className={styles.title}>{this.props.SiteDesignSelectedItem.Id}</span>
</div>
<div>
<span className={styles.label}>Title:</span>
<span className={styles.title}>{this.props.SiteDesignSelectedItem.Title}</span>
</div>
<div>
<span className={styles.label}>WebTemplate:</span>
<span className={styles.title}>{this.props.SiteDesignSelectedItem.WebTemplate === '64' ? "Team Site" : "Communication Site"}</span>
</div>
<br />
{
this.state.isLoading ?
<Spinner size={SpinnerSize.large} label={strings.LoadingLabel} ariaLive="assertive" />
:
this.state.hasError ?
<MessageBar
messageBarType={MessageBarType.error}>
<span>{this.state.errorMessage}</span>
</MessageBar>
:
<div style={{ marginBottom: 10 }}>
<CommandBar
items={[
{
key: 'newItem',
name: strings.CommandbarNewLabel,
iconProps: {
iconName: 'Add'
},
onClick: this.onNewItem,
},
{
key: 'edit',
name: strings.CommandbarEditLabel,
iconProps: {
iconName: 'Edit'
},
onClick: this.onEditItem,
disabled: this.state.selectItem.length ===0 ? true : this.state.selectItem.length > 1 ? true : false,
},
{
key: 'delete',
name: strings.CommandbarDeleteLabel,
iconProps: {
iconName: 'Delete'
},
onClick: this.onDeleteItem,
disabled: this.state.disableCommandOption,
}
]}
farItems={[
{
key: 'refresh',
name: strings.CommandbarRefreshLabel,
iconProps: {
iconName: 'Refresh'
},
onClick: this.onRefresh,
}
]}
/>
</div>
}
{
!this.state.hasError && !this.state.isLoading &&
<ListView
items={this.state.items}
viewFields={viewFields}
compact={false}
selectionMode={SelectionMode.multiple}
selection={this.getSelection}
showFilter={true}
filterPlaceHolder={strings.SearchPlaceholder}
/>
}
{
this.state.showPanel && this.state.panelMode == panelMode.New &&
<React.Suspense fallback={<div>Loading...</div>}>
<this.AddSiteScriptToSiteDesignDialog
showPanel={this.state.showPanel}
onDismiss={this.onDismissAddScriptPanel}
context={this.props.context}
siteDesignInfo={this.props.SiteDesignSelectedItem}
/>
</React.Suspense>
}
{
this.state.showPanel && this.state.panelMode == panelMode.edit &&
<React.Suspense fallback={<div>Loading...</div>}>
<this.EditScriptDialog
hideDialog={!this.state.showPanel}
onDismiss={this.onDismissAddScriptPanel}
context={this.props.context}
siteScriptId={this.state.selectItem[0].Id}
/>
</React.Suspense>
}
<Dialog
hidden={!this.state.showDialogDelete}
onDismiss={this.onCloseDialog}
dialogContentProps={{
type: DialogType.normal,
title: strings.DeleteSiteScriptDialogConfirmTitle,
}}
modalProps={{
isBlocking: true,
}}
>
<p>{strings.DeleteSiteScriptDialogConfirmText}</p>
<br />
{
this.state.showError &&
<div style={{ marginTop: '15px' }}>
<MessageBar messageBarType={MessageBarType.error} >
<span>{this.state.errorMessage}</span>
</MessageBar>
</div>
}
<br />
<DialogFooter>
{
this.state.deleting &&
<div style={{ display: "inline-block", marginRight: '10px', verticalAlign: 'middle' }}>
<Spinner size={SpinnerSize.small} ariaLive="assertive" />
</div>
}
<DefaultButton onClick={this.onDeleteConfirm} text={strings.ButtonDeleteLabel} disabled={this.state.disableDeleteButton} />
<PrimaryButton onClick={this.onCloseDialog} text={strings.ButtonCancelLabel} />
</DialogFooter>
</Dialog>
</Panel>
</div >
);
}
}

View File

@ -0,0 +1,28 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.title {
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
vertical-align: top;
margin-right: 15px;
display: inline-block;
@include ms-font-m;
}
.label {
vertical-align: top;
margin-right: 15px;
display: inline-block;
@include ms-font-m;
width: 100px;
}
.listItem {
@include ms-font-l;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}

View File

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

View File

@ -0,0 +1,7 @@
export interface IAddSiteDesignTaskToCurrentWebResult {
ID: string;
LogonName: string;
SiteDesignID: string;
SiteID: string;
WebID:string;
}

View File

@ -0,0 +1,6 @@
export interface ISiteDesignTaskResult {
"@odata.null"?: boolean;
ID: string;
LogonName: string;
SiteDesignID: string;
}

View File

@ -0,0 +1,386 @@
// João Mendes
// March 2019
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { sp, Web, SiteDesignInfo, SiteScriptInfo, SiteScripts, SiteDesignCreationInfo, SiteDesignUpdateInfo, SiteDesignPrincipals, SiteScriptUpdateInfo, SearchResults, } from '@pnp/sp';
import { graph, } from "@pnp/graph";
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions, HttpClient } from '@microsoft/sp-http';
import { ISiteScript } from '../types/ISiteScript';
import SiteDesignRights from "../controls/SiteDesignRights/SiteDesignRights";
import { IAddSiteDesignTaskToCurrentWebResult } from './IAddSiteDesignTaskToCurrentWebResult';
import { ISiteDesignTaskResult } from './ISiteDesignTaskResult';
import * as $ from 'jquery';
const ADMIN_ROLETEMPLATE_ID = "62e90394-69f5-4237-9190-012177145e10"; // Global Admin TemplateRoleId
// Class Services
export default class spservices {
private appCatalogUrl: string = '';
constructor(private context: WebPartContext) {
// Setuo Context to PnPjs and MSGraph
sp.setup({
spfxContext: this.context
});
graph.setup({
spfxContext: this.context
});
this.onInit();
}
// OnInit Function
private async onInit() {
this.appCatalogUrl = await this.getAppCatalogUrl();
}
// Get App Catalog
private async getAppCatalogUrl() {
try {
const webAbsoluteUrl = this.context.pageContext.web.absoluteUrl;
const apiUrl = `${webAbsoluteUrl}/_api/SP_TenantSettings_Current`;
const data: SPHttpClientResponse = await this.context.spHttpClient.get(apiUrl, SPHttpClient.configurations.v1);
if (data.ok) {
const results = await data.json();
if (results) {
return results.CorporateCatalogUrl;
}
}
return null;
} catch (error) {
console.dir(error);
return Promise.reject(error.message);
}
}
// Check if user is Global Admin
/**
*
*/
public async checkUserIsGlobalAdmin() {
const myDirRolesAndGroups: any[] = await graph.me.memberOf.get();
for (const myDirRolesAndGroup of myDirRolesAndGroups) {
if (myDirRolesAndGroup.roleTemplateId && myDirRolesAndGroup.roleTemplateId === ADMIN_ROLETEMPLATE_ID) { // roleTemplateId for glabal Admin
return true;
}
}
return false;
}
/**
* Get List of Site Designs
*
* @returns results[]
* @memberof spservices
*/
public async getSiteDesigns() {
let result: SiteDesignInfo[] = [];
try {
result = await sp.siteDesigns.getSiteDesigns();
} catch (error) {
return Promise.reject(error);
}
return result;
}
/**
* Return Site Scripts
*
* @returns array os siteScripts
* @memberof spservices
*/
public async getSiteScripts() {
let results: SiteScriptInfo[] = [];
try {
results = await sp.siteScripts.getSiteScripts();
} catch (error) {
return Promise.reject(error);
}
return results;
}
/**
* Get Site Script by Id
*
* @param {string} id
* @returns {SiteScriptInfo}
* @memberof spservices
*/
public async getSiteScriptMetadata(id: string) {
let results: SiteScriptInfo = null;
try {
results = await sp.siteScripts.getSiteScriptMetadata(id);
} catch (error) {
return Promise.reject(error);
}
return results;
}
/**
* GetSiteDesignMetaData
*
* @param {string} id
* @returns
* @memberof spservices
*/
public async getSiteDesignMetadata(id: string) {
let results: SiteDesignInfo = null;
try {
results = await sp.siteDesigns.getSiteDesignMetadata(id);
} catch (error) {
return Promise.reject(error);
}
return results;
}
/**
* Create a SiteScript
*
* @param {string} title
* @param {*} description
* @param {string} sitescript
* @returns
* @memberof spservices
*/
public async createSiteScript(title: string, description, siteScript: ISiteScript) {
let results: SiteScriptInfo = null;
try {
results = await sp.siteScripts.createSiteScript(title, description, siteScript);
} catch (error) {
return Promise.reject(error);
}
return results;
}
/**
*
*
* @private
* @param {SiteDesignInfo} siteDesignInfo
* @returns
* @memberof spservices
*/
public async createSiteDesign(siteDesignInfo: SiteDesignCreationInfo) {
let results: SiteDesignCreationInfo = null;
try {
results = await sp.siteDesigns.createSiteDesign(siteDesignInfo);
} catch (error) {
return Promise.reject(error);
}
return results;
}
/**
*
*
* @param {SiteDesignUpdateInfo} siteDesignInfo
* @returns
* @memberof spservices
*/
public async updateSiteDesign(siteDesignInfo: SiteDesignUpdateInfo) {
let results: SiteDesignInfo = null;
try {
results = await sp.siteDesigns.updateSiteDesign(siteDesignInfo);
} catch (error) {
return Promise.reject(error);
}
return results;
}
/**
*
*
* @param {SiteDesignUpdateInfo} siteDesignInfo
* @returns
* @memberof spservices
*/
public async deleteSiteDesign(siteDesignInfo: SiteDesignUpdateInfo) {
try {
await sp.siteDesigns.deleteSiteDesign(siteDesignInfo.Id);
} catch (error) {
return Promise.reject(error);
}
return;
}
/**
*
*
* @private
* @param {string} siteDesignId
* @returns
* @memberof spservices
*/
public async getSiteDesignRights(siteDesignId: string) {
let results: SiteDesignPrincipals[] = null;
try {
results = await sp.siteDesigns.getSiteDesignRights(siteDesignId);
} catch (error) {
return Promise.reject(error);
}
return results;
}
public async updateSiteScript(siteScriptInfo: SiteScriptUpdateInfo) {
let results: SiteScriptInfo = null;
try {
results = await sp.siteScripts.updateSiteScript(siteScriptInfo);
} catch (error) {
return Promise.reject(error);
}
return results;
}
/**
*
*
* @param {string} siteDesignId
* @param {string[]} principals
* @returns
* @memberof spservices
*/
public async grantSiteDesignRights(siteDesignId: string, principals: string[]) {
try {
await sp.siteDesigns.grantSiteDesignRights(siteDesignId, principals);
} catch (error) {
return Promise.reject(error);
}
return;
}
/**
*
*
* @param {string} siteDesignId
* @param {string[]} principals
* @returns
* @memberof spservices
*/
public async revokeSiteDesignRights(siteDesignId: string, principals: string[]) {
try {
await sp.siteDesigns.revokeSiteDesignRights(siteDesignId, principals);
} catch (error) {
return Promise.reject(error);
}
return;
}
/**
*
*
* @param {string} value
* @returns
* @memberof spservices
*/
public async getSites(value: string) {
let results: SearchResults = null;
try {
results = await sp.search(`${value} AND contentclass:STS_Site`);
} catch (error) {
return Promise.reject(error);
}
return results;
}
/**
*
*
* @param {string} webUrl
* @param {string} siteDesignId
* @returns
* @memberof spservices
*/
public async AddSiteDesignTask(webUrl: string, siteDesignId: string) {
const webAbsoluteUrl = this.context.pageContext.web.absoluteUrl;
try {
const spOpts: ISPHttpClientOptions = {
body: `{"siteDesignId":"${siteDesignId}","webUrl":${webUrl}}`
};
const formDigest = await this.getRequestDigest();
const apiUrl = `${webAbsoluteUrl}/_api/microsoft.Sharepoint.Utilities.WebTemplateExtensions.SiteScriptUtility.AddSiteDesignTask`;
const results = await $.ajax({
url: apiUrl,
type: 'POST',
dataType: 'json',
data: `{"siteDesignId":"${siteDesignId}","webUrl":"${webUrl}"}`,
headers: {
'content-type': 'application/json;charset=utf-8',
'accept': 'application/json;odata=nometadata',
'X-RequestDigest': formDigest
}
});
// const data: SPHttpClientResponse = await this.context.spHttpClient.post(apiUrl, SPHttpClient.configurations.v1,spOpts);
if (results && results) {
return results;
}
} catch (error) {
console.dir(error);
return Promise.reject(error);
}
}
/**
*
* @param {string} webUrl
* @param {string} taskId
* @returns
* @memberof spservices
*/
public async getSiteDesignTask(webUrl: string, runTaskId: string): Promise<ISiteDesignTaskResult> {
try {
const spOpts: ISPHttpClientOptions = {
body: `{ "taskId": "${runTaskId}"}`
};
const apiUrl = `${webUrl}/_api/microsoft.Sharepoint.Utilities.WebTemplateExtensions.SiteScriptUtility.GetSiteDesignTask`;
const data: SPHttpClientResponse = await this.context.spHttpClient.post(apiUrl, SPHttpClient.configurations.v1, spOpts);
if (data.ok) { // Testar sucesso diferente 200 e lançar erro
const results: ISiteDesignTaskResult = await data.json();
if (results) {
return results;
}
}
return null;
} catch (error) {
console.dir(error);
return Promise.reject(error.message);
}
}
private async getRequestDigest(){
try {
const webAbsoluteUrl = this.context.pageContext.web.absoluteUrl;
const spOpts: ISPHttpClientOptions = {
};
const apiUrl = `${webAbsoluteUrl}/_api/contextinfo`;
const data: SPHttpClientResponse = await this.context.spHttpClient.post(apiUrl, SPHttpClient.configurations.v1, null);
if (data.ok) {
const results = await data.json();
if (results) {
return results.FormDigestValue;
}
}
return null;
} catch (error) {
console.dir(error);
return Promise.reject(error);
}
}
}

View File

@ -0,0 +1,17 @@
export interface ISiteScript {
$schema: string;
actions: IAction[];
bindata: object;
version: number;
}
export interface IAction {
verb: string;
[key: string]: any;
subactions?: ISubaction[];
}
export interface ISubaction {
verb: string;
[key: string]: any;
}

View File

@ -0,0 +1,13 @@
declare module '@dr-kobros/react-jsoneditor' {
import * as react from 'react';
export default class JsonEditor extends React.PureComponent<JsonEditorProps, any> {
}
export interface JsonEditorProps {
onChange:(json:any)=>void;
onDirty:(json:any)=>void;
value:object;
options:any;
width:string;
height:string;
}
}

View File

@ -0,0 +1,12 @@
declare module 'jsoneditor' {
import * as react from 'react';
export default class JSONEditor {
constructor(container: HTMLElement, options:JsonEditorOptions)
get():any;
set(value:object):any;
destroy():any;
}
export interface JsonEditorOptions {
onChange:()=>void;
}
}

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "08bc7d85-ef3a-492d-89c5-a7f0048fc2ec",
"alias": "SiteDesignsWebPart",
"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": "Site Designs" },
"description": { "default": "Manage Site Designs" },
"officeFabricIconFontName": "Design",
"properties": {
"title": "Manage Site Designs"
}
}]
}

View File

@ -0,0 +1,65 @@
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 'SiteDesignsWebPartStrings';
import SiteDesigns from './components/SiteDesigns';
import { ISiteDesignsProps } from './components/ISiteDesignsProps';
export interface ISiteDesignsWebPartProps {
title: string;
}
export default class SiteDesignsWebPart extends BaseClientSideWebPart<ISiteDesignsWebPartProps> {
public render(): void {
const element: React.ReactElement<ISiteDesignsProps> = React.createElement(
SiteDesigns,
{
title: this.properties.title,
context: this.context,
displayMode: this.displayMode,
updateProperty: (value: string) => {
this.properties.title = value;
}
}
);
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: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('title', {
label: 'Title'
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,8 @@
export enum panelMode {
"New",
"edit",
"Delete",
"Apply",
"Rights",
"SiteScripts"
}

View File

@ -0,0 +1,15 @@
export interface IListViewItems {
key: string;
Description: string;
PreviewImageUrl: string;
SiteScriptIds: string;
Title: string;
WebTemplate: string;
Id: string;
numberSiteScripts:number;
IsDefault: boolean;
PreviewImageAltText:string;
Version: string;
runStatus: boolean;
}

View File

@ -0,0 +1,17 @@
import { IListViewItems } from '../components/IListViewItems';
import { panelMode } from './IEnumPanel';
import { SiteDesignInfo } from '@pnp/sp';
export interface ISiteDesignState{
items: IListViewItems[];
isLoading:boolean;
disableCommandOption:boolean;
showPanel:boolean;
selectItem: IListViewItems;
panelMode: panelMode;
hasError: boolean;
errorMessage: string;
siteDesignRunning:boolean;
siteDesignRunningMessage: string[];
}

View File

@ -0,0 +1,10 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { DisplayMode } from '@microsoft/sp-core-library';
export interface ISiteDesignsProps {
title: string;
context: WebPartContext;
displayMode:DisplayMode;
updateProperty(value:string):void;
}

View File

@ -0,0 +1,88 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.siteDesigns {
margin: 0 auto;
width: calc(100% - 50px) !important;
}
.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;
}
.webPartTitle{
margin-top: 25px;
margin-bottom: 25px;
}
.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-size: 20px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
.siteDesignRunningMessage {
display: inline-block;
vertical-align: top;
margin-right: 20px;
}
.siteDesignRunningMessageLink {
display: inline-block;
vertical-align: top;
}
.commandBar {
margin-top: 15px;
margin-bottom: 15px;
}

View File

@ -0,0 +1,561 @@
// João Mendes
// Mar 2019
//
import * as React from 'react';
import styles from './SiteDesigns.module.scss';
import { ISiteDesignsProps } from './ISiteDesignsProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from "@pnp/spfx-controls-react/lib/ListView";
import { IListViewItems } from '../components/IListViewItems';
import spservice from '../../../services/spservices';
import { Icon, IconType } from 'office-ui-fabric-react/lib/Icon';
import { CommandBar } from 'office-ui-fabric-react/lib/CommandBar';
import { panelMode } from './IEnumPanel';
import {
MessageBar,
MessageBarType,
Spinner,
SpinnerSize,
Dialog,
DialogType,
DialogFooter,
ImageFit,
PrimaryButton,
Link
} from 'office-ui-fabric-react';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import * as strings from 'SiteDesignsWebPartStrings';
import { ISiteDesignState } from './ISiteDesignState';
import { SiteDesignInfo, Item } from '@pnp/sp';
import { IAddSiteDesignTaskToCurrentWebResult } from '../../../services/IAddSiteDesignTaskToCurrentWebResult';
//import { SiteScripts } from './../../../controls/sitescripts/SiteScripts';
// ListView Columns
const viewFields: IViewField[] = [
{
name: 'Image',
render: ((item: IListViewItems) => {
let image;
item.PreviewImageUrl ?
image = <Icon iconType={IconType.image} imageProps={{ src: item.PreviewImageUrl, imageFit: ImageFit.cover }} /> :
image = <Icon iconName="FileImage" />;
return image;
}),
maxWidth: 70,
},
{
name: 'Id',
displayName: strings.ListViewColumnIdLabel,
sorting: true,
isResizable: true,
maxWidth: 200
},
{
name: 'Title',
displayName: strings.TitleFieldLabel,
sorting: true,
isResizable: true,
maxWidth: 250
},
{
name: 'Description',
displayName: strings.ListViewColumnDescriptionLabel,
sorting: true,
isResizable: true,
maxWidth: 250
},
{
name: 'WebTemplate',
displayName: strings.ListViewColumnWebTemplateLabel,
sorting: true,
isResizable: true,
maxWidth: 65
},
{
name: 'numberSiteScripts',
displayName: strings.ListViewColumnNumberSiteScriptsLabel,
sorting: true,
isResizable: true,
maxWidth: 60
}
];
export default class SiteDesigns extends React.Component<ISiteDesignsProps, ISiteDesignState> {
private spService: spservice;
private items: IListViewItems[];
private siteDesignsRunStatus: { addSiteDesignTaskResult: IAddSiteDesignTaskToCurrentWebResult, siteUrl: string }[] = [];
private SiteScriptsList = React.lazy(() => import('../../../controls/siteScripts/SiteScripts' /* webpackChunkName: "sitescriptslist" */));
private AddSiteDesign = React.lazy(() => import('./../../../controls/AddSiteDesign/AddSiteDesign' /* webpackChunkName: "addsitedesign" */));
private EditSiteDesign = React.lazy(() => import('./../../../controls/EditSiteDesign/EditSiteDesign' /* webpackChunkName: "editsitedesign" */));
private DeleteSiteDesign = React.lazy(() => import('./../../../controls/DeleteSiteDesign/DeleteSiteDesign' /* webpackChunkName: "deletesitedesign" */));
private SiteDesignRights = React.lazy(() => import('./../../../controls/SiteDesignRights/SiteDesignRights' /* webpackChunkName: "sitedesignrights" */));
private ApplySiteDesign = React.lazy(() => import('./../../../controls/ApplySiteDesign/ApplySiteDesign' /* webpackChunkName: "applysitedesign" */));
public constructor(props) {
super(props);
// Initialize state
this.state = ({
items: [],
isLoading: false,
disableCommandOption: true,
showPanel: false,
selectItem: null,
panelMode: panelMode.New,
hasError: false,
errorMessage: '',
siteDesignRunning: false,
siteDesignRunningMessage: []
});
// Init class services
this.spService = new spservice(this.props.context);
// Register event handlers
this._getSelection = this._getSelection.bind(this);
this.onNewItem = this.onNewItem.bind(this);
this.onEditItem = this.onEditItem.bind(this);
this.onDeleteItem = this.onDeleteItem.bind(this);
this.onApplyItem = this.onApplyItem.bind(this);
this.onRights = this.onRights.bind(this);
this.onSiteScripts = this.onSiteScripts.bind(this);
this.onDismissPanel = this.onDismissPanel.bind(this);
this.onRefresh = this.onRefresh.bind(this);
this.onDismissApplyPanel = this.onDismissApplyPanel.bind(this);
}
// Get Selection Item from List
private _getSelection(items: IListViewItems[]) {
if (items.length > 0) {
this.setState({
disableCommandOption: false,
selectItem: items[0]
});
} else {
this.setState({
disableCommandOption: true,
selectItem: null,
showPanel: false,
});
}
}
/**
*
*
* @param {IAddSiteDesignTaskToCurrentWebResult[]} siteDesignsRunning
* @param {boolean} [refresh]
* @returns
* @memberof SiteDesigns
*/
public async onDismissApplyPanel(siteDesignsRunning: { addSiteDesignTaskResult: IAddSiteDesignTaskToCurrentWebResult, siteUrl: string }[], refresh?: boolean) {
let isRunning: boolean = false;
let totalRunningSiteDesigns: number = 0;
let siteDesignRunningMessage: string[] = [`The Site Design ${siteDesignsRunning[0].addSiteDesignTaskResult.SiteDesignID} is beeing applyed ...`];
if (siteDesignsRunning && siteDesignsRunning.length > 0) {
totalRunningSiteDesigns = siteDesignsRunning.length;
this.siteDesignsRunStatus = siteDesignsRunning;
this.setState({
siteDesignRunning: true,
siteDesignRunningMessage: siteDesignRunningMessage
});
const runTimer = setInterval(async () => {
siteDesignRunningMessage = [];
siteDesignRunningMessage = [`The Site Design :${siteDesignsRunning[0].addSiteDesignTaskResult.SiteDesignID} was applyed.`];
for (const siteDesignApplyed of siteDesignsRunning) {
isRunning = true;
const result = await this.spService.getSiteDesignTask(siteDesignApplyed.siteUrl, siteDesignApplyed.addSiteDesignTaskResult.ID);
isRunning = !result['@odata.null'] ? true : false;
if (!isRunning) {
totalRunningSiteDesigns = totalRunningSiteDesigns - 1;
siteDesignRunningMessage.push(` site: ${siteDesignApplyed.siteUrl} : Applyed`);
} else {
siteDesignRunningMessage.push(`site: ${siteDesignApplyed.siteUrl} : Applying...`);
}
}
//
if (totalRunningSiteDesigns <= 0) {
clearInterval(runTimer);
this.setState({
siteDesignRunning: true,
siteDesignRunningMessage: siteDesignRunningMessage
});
}
}
, 5000);
}
this.setState({
showPanel: false
});
if (refresh) {
await this.loadSiteDesignsProperties();
}
return;
}
// Panel Dismiss CallBack
// @param refresh refresh list?
public async onDismissPanel(refresh?: boolean) {
this.setState({
showPanel: false
});
if (refresh) {
await this.loadSiteDesignsProperties();
}
return;
}
// On New Item
private onNewItem(e: React.MouseEvent<HTMLElement>) {
e.preventDefault();
this.setState({
panelMode: panelMode.New,
showPanel: true,
});
}
// On Delete
private onDeleteItem(e: React.MouseEvent<HTMLElement>) {
e.preventDefault();
this.setState({
panelMode: panelMode.Delete,
showPanel: true,
});
}
// On Edit item
private onEditItem(e: React.MouseEvent<HTMLElement>) {
e.preventDefault();
this.setState({
panelMode: panelMode.edit,
showPanel: true
});
}
/**
* On Appply item
* @private
* @param {React.MouseEvent<HTMLElement>} e
* @memberof SiteDesigns
*/
private onApplyItem(e: React.MouseEvent<HTMLElement>) {
e.preventDefault();
this.setState({
panelMode: panelMode.Apply,
showPanel: true
});
}
/**
* On Rights Event
*
* @private
* @param {React.MouseEvent<HTMLElement>} e
* @memberof SiteDesigns
*/
private onRights(e: React.MouseEvent<HTMLElement>) {
e.preventDefault();
this.setState({
panelMode: panelMode.Rights,
showPanel: true
});
}
/**
* On SiteScripts
*
* @private
* @param {React.MouseEvent<HTMLElement>} e
* @memberof SiteDesigns
*/
private onSiteScripts(e: React.MouseEvent<HTMLElement>) {
e.preventDefault();
this.setState({
panelMode: panelMode.SiteScripts,
showPanel: true
});
}
private checkSiteDesignRunTask(siteDesignApplyedInfo: IAddSiteDesignTaskToCurrentWebResult) {
try {
} catch (error) {
}
}
//
/**
*
*
* @private
* @memberof SiteDesigns
*/
private async loadSiteDesignsProperties() {
this.items = [];
this.setState({ isLoading: true });
try {
// check if user is Teanant Global Admin
const isGlobalAdmin = await this.spService.checkUserIsGlobalAdmin();
if (isGlobalAdmin) {
// Get Tenant Properties
const siteDesigns: SiteDesignInfo[] = await this.spService.getSiteDesigns();
for (const siteDesign of siteDesigns) {
this.items.push(
{
key: siteDesign.Id,
Description: siteDesign.Description,
Id: siteDesign.Id,
Title: siteDesign.Title,
WebTemplate: siteDesign.WebTemplate,
SiteScriptIds: siteDesign.SiteScriptIds.toString(),
numberSiteScripts: siteDesign.SiteScriptIds.length,
IsDefault: siteDesign.IsDefault,
PreviewImageAltText: siteDesign.PreviewImageAltText,
PreviewImageUrl: siteDesign.PreviewImageUrl,
Version: siteDesign.Version,
runStatus: false
}
);
}
this.setState({ items: this.items, isLoading: false, disableCommandOption: true });
} else {
this.setState({
items: this.items,
hasError: true,
errorMessage: strings.ErrorMessageUserNotAdmin,
isLoading: false
});
}
}
catch (error) {
this.setState({
items: this.items,
hasError: true,
errorMessage: error.message,
isLoading: false
});
}
}
// Refresh
public onRefresh(ev: React.MouseEvent<HTMLElement>) {
ev.preventDefault();
// LoadTenantProperties
this.loadSiteDesignsProperties();
}
// Component Did Mount
public async componentDidMount() {
// LoadTenantProperties
await this.loadSiteDesignsProperties();
}
// On Render
public render(): React.ReactElement<ISiteDesignsProps> {
return (
<div className={styles.siteDesigns}>
<WebPartTitle displayMode={this.props.displayMode}
title={this.props.title}
className={styles.webPartTitle}
updateProperty={this.props.updateProperty} />
{
this.state.isLoading ?
<Spinner size={SpinnerSize.large} label={strings.LoadingLabel} ariaLive="assertive" />
:
this.state.hasError ?
<MessageBar
messageBarType={MessageBarType.error}>
<span>{this.state.errorMessage}</span>
</MessageBar >
:
this.state.siteDesignRunning ?
<MessageBar
truncated={true}
isMultiline={false}
onDismiss={ (ev) => {this.setState({siteDesignRunning: false});}}
messageBarType={MessageBarType.info}
styles={{
root: {
background: 'rgba(113, 175, 229, 0.2)',
color: '#00188f'
},
icon: {
color: '#00188f'
}
}}
>
{
this.state.siteDesignRunningMessage.map((message) =>{
return (
<span>{message}<br/></span>
);
})
}
</MessageBar>
:
null
}
{
!this.state.hasError && !this.state.isLoading &&
< div className={styles.commandBar}>
<CommandBar
items={[
{
key: 'newItem',
name: strings.CommandbarNewLabel,
iconProps: {
iconName: 'Add'
},
onClick: this.onNewItem,
},
{
key: 'edit',
name: strings.CommandbarEditLabel,
iconProps: {
iconName: 'Edit'
},
onClick: this.onEditItem,
disabled: this.state.disableCommandOption,
},
{
key: 'delete',
name: strings.CommandbarDeleteLabel,
iconProps: {
iconName: 'Delete'
},
onClick: this.onDeleteItem,
disabled: this.state.disableCommandOption,
},
{
key: 'apply',
name: strings.CommandbarApplyLabel,
iconProps: {
iconName: 'Play'
},
onClick: this.onApplyItem,
disabled: this.state.disableCommandOption,
},
{
key: 'rights',
name: strings.CommandbarRightsLabel,
iconProps: {
iconName: 'Permissions'
},
onClick: this.onRights,
disabled: this.state.disableCommandOption,
},
{
key: 'sitescripts',
name: strings.CommandbarSiteScriptsLabel,
iconProps: {
iconName: 'FileCode'
},
onClick: this.onSiteScripts,
disabled: this.state.disableCommandOption,
}
]}
farItems={[
{
key: 'refresh',
name: strings.CommandbarRefreshLabel,
iconProps: {
iconName: 'Refresh'
},
onClick: this.onRefresh,
}
]}
/>
</div>
}
{
!this.state.hasError && !this.state.isLoading &&
<ListView
items={this.state.items}
viewFields={viewFields}
compact={false}
selectionMode={SelectionMode.single}
selection={this._getSelection}
showFilter={true}
filterPlaceHolder={strings.SearchPlaceholder}
/>
}
{
this.state.showPanel && this.state.panelMode == panelMode.SiteScripts &&
<React.Suspense fallback={<div>Loading...</div>}>
<this.SiteScriptsList
mode={this.state.panelMode}
SiteDesignSelectedItem={this.state.selectItem}
onDismiss={this.onDismissPanel}
showPanel={this.state.showPanel}
context={this.props.context}
/>
</React.Suspense>
}
{
this.state.showPanel && this.state.panelMode == panelMode.New &&
<React.Suspense fallback={<div>Loading...</div>}>
<this.AddSiteDesign
mode={this.state.panelMode}
onDismiss={this.onDismissPanel}
showPanel={this.state.showPanel}
context={this.props.context}
/>
</React.Suspense>
}
{
this.state.showPanel && this.state.panelMode == panelMode.edit &&
<React.Suspense fallback={<div>Loading...</div>}>
<this.EditSiteDesign
mode={this.state.panelMode}
onDismiss={this.onDismissPanel}
showPanel={this.state.showPanel}
context={this.props.context}
siteDesignInfo={this.state.selectItem}
/>
</React.Suspense>
}
{
this.state.showPanel && this.state.panelMode == panelMode.Delete &&
<React.Suspense fallback={<div>Loading...</div>}>
<this.DeleteSiteDesign
mode={this.state.panelMode}
onDismiss={this.onDismissPanel}
showPanel={this.state.showPanel}
context={this.props.context}
siteDesignInfo={this.state.selectItem}
/>
</React.Suspense>
}
{
this.state.showPanel && this.state.panelMode == panelMode.Rights &&
<React.Suspense fallback={<div>Loading...</div>}>
<this.SiteDesignRights
mode={this.state.panelMode}
onDismiss={this.onDismissPanel}
showPanel={this.state.showPanel}
context={this.props.context}
SiteDesignSelectedItem={this.state.selectItem}
/>
</React.Suspense>
}
{
this.state.showPanel && this.state.panelMode == panelMode.Apply &&
<React.Suspense fallback={<div>Loading...</div>}>
<this.ApplySiteDesign
onDismiss={this.onDismissApplyPanel}
showPanel={this.state.showPanel}
context={this.props.context}
siteDesignInfo={this.state.selectItem}
/>
</React.Suspense>
}
</div >
);
}
}

View File

@ -0,0 +1,83 @@
define([], function() {
return {
DeleteSiteScriptDialogConfirmText: "Please confirm delete of Site Scripts",
DeleteSiteScriptDialogConfirmTitle: "Delete Site Scripts?",
JSONSchemaNotValidMessage: "JSON Schema has errors or not valid",
EditSiteDesignPanelButtonCancelText: "Cancel",
EditSiteDesignPanelButtonSaveText: "Save",
DeleteSiteDesignPanelTitle: "Delete Site Design",
DeleteSiteDesignPanelButtonCanceltext: "Cancel",
DeleteSiteDesignPanelButtonDeleteText: "Delete",
AddSiteScriptToSiteDesignPanelButtonCancelText: "Cancel",
AddSiteScriptToSiteDesignPanelButtonSaveText: "Save",
AddSiteScriptPanelButtonCancelText: "Cancel",
AddSiteScriptPanelButtonSave: "Save",
AddPrincipalPanelButtonCancelText: "Cancel",
AddPrincipalPanelButtonSaveText: "Save",
AddSiteDesignPanelDropDownLabel: "Site Scripts",
AddSiteDesignPanelDropDownPlaceholderText: "Select Site Script",
AddSiteDesignPanelButtonCancelText: "Cancel",
AddSiteDesignPanelButtonSaveText: "Save",
AddSiteDesignPanelScriptOrderInfo: "SiteScripts will run in the order they are selected",
AddSiteDesignPanelActionButtonText: " Add SiteScript",
AddSiteDesignPanelTitleErrorMessage: "Site Design title is required",
SearchBoxPlaceholderText: "Please search for sites to apply",
EditSiteScriptPanelButtonCancel: "Cancel",
EditSiteScriptSaveButtonLabel: "Save",
ApplyPanelTitle: "Apply Site Design",
ApplyPanelButtonCancelLabel: "Cancel",
ApplyPanelApplyButtonLabel: "Apply",
siteDesignRunningMessage: "There are Site Designs been applyed...",
WebSiteUrl: "Web Url",
WebSiteTitleLabel: "Web Title",
actionButtonTitle: "Add Site Script",
ButtonCancelLabel: "Cancel",
ButtonDeleteLabel: "Delete",
DropDownSelectSiteScriptLabel: "Site Scripts",
DropDownSelectSiteScriptPlaceHolder: "Select Site Script",
AddSiteScriptToSiteDesignPanelTitle: "Add SiteScript to SiteDesign",
AddSiteScriptDialogSubText: "Please specify JSON Definitions",
JSONSchemaErrorMessage: "JSON Schema has errors or not valid",
DialogConfirmDeleteText: "Please confirm revoke of Site Design Rights ",
DialogConfirmDeleteTitle: "Revoke Site Design Rights",
WebTemplateCommunicationSite: "Communication Sit",
WebTemplateTeamSite: "Team Site",
SiteDesignRightsPanelTitle: "Site Design Rights",
SiteScriptIdLabel: "Site Script Id",
EditSiteScriptDialogTitle: "Edit Site Script",
AddSiteScriptDialogTitle: "Create a Site Script",
AddSiteScriptDescriptionLabel: "Description",
AddSiteScriptTitleLabel: "Title",
"PropertyPaneDescription": "Properties",
"BasicGroupName": "Group Name",
"TitleFieldLabel": "Title",
"ListViewColumnIdLabel": "Id",
"ListViewColumnWebTemplateLabel": "WebTemplate",
"ListViewColumnDescriptionLabel": "Description",
"ListViewColumnNumberSiteScriptsLabel": "Nr Site Scripts",
"ErrorMessageUserNotAdmin": "User is not Tenant Admin to managed Tenant Properties",
"LoadingLabel":"Loading...",
"CommandbarNewLabel":"New",
"CommandbarEditLabel": "Edit",
"CommandbarDeleteLabel": "Delete",
"CommandbarRefreshLabel": "Refresh",
"CommandbarRightsLabel": "Rights",
"CommandbarApplyLabel": "Apply",
"SearchPlaceholder": "Search...",
"PrimaryButtonLabelSave":"Save",
"PrimaryButtonLabelDelete":"Delete",
"DefaultButtonLabel":"Cancel",
"PanelHeaderTextEdit":"Add / Edit Site Design",
"PanelHeaderTextDelete":"Delete Site Design",
"messageTenantExist": "A Site Design with this key already exists",
"CommandbarSiteScriptsLabel": "Site Scripts",
"AddSiteDesignTitleLabel" : "Site Design Title",
"AddSiteDesignDescriptionLabel": "Site Design Description",
"AddSiteDesignImageUrlLabel": "Preview Image Url",
"AddSiteDesignIsDefaultLabel" : "Is Default ?",
"SiteDesignIdLabel": "Site Design Id",
"ListViewColumnIdPrincipalNameLabel" : "PrincipalName",
SiteDesignTitleLabel: "Site Design Title:",
WebTemplateLabel: "Web Template"
}
});

View File

@ -0,0 +1,87 @@
declare interface ISiteDesignsWebPartStrings {
DeleteSiteScriptDialogConfirmText: string;
DeleteSiteScriptDialogConfirmTitle: string;
JSONSchemaNotValidMessage: string;
EditSiteDesignPanelButtonCancelText: string;
EditSiteDesignPanelButtonSaveText: string;
DeleteSiteDesignPanelTitle: string;
DeleteSiteDesignPanelButtonCanceltext: string;
DeleteSiteDesignPanelButtonDeleteText: string;
AddSiteScriptToSiteDesignPanelButtonCancelText: string;
AddSiteScriptToSiteDesignPanelButtonSaveText: string;
AddSiteScriptPanelButtonCancelText: string;
AddSiteScriptPanelButtonSave: string;
AddPrincipalPanelButtonCancelText: string;
AddPrincipalPanelButtonSaveText: string;
AddSiteDesignPanelDropDownLabel: string;
AddSiteDesignPanelDropDownPlaceholderText: string;
AddSiteDesignPanelButtonCancelText: string;
AddSiteDesignPanelButtonSaveText: string;
AddSiteDesignPanelScriptOrderInfo: string;
AddSiteDesignPanelActionButtonText: string;
AddSiteDesignPanelTitleErrorMessage: string;
SearchBoxPlaceholderText: string;
EditSiteScriptPanelButtonCancel: string;
EditSiteScriptSaveButtonLabel: string;
ApplyPanelTitle: string;
ApplyPanelButtonCancelLabel: string;
ApplyPanelApplyButtonLabel: string;
siteDesignRunningMessage: string;
WebSiteUrl: string;
WebSiteTitleLabel: string;
actionButtonTitle: string;
ButtonCancelLabel: string;
ButtonDeleteLabel: string;
DropDownSelectSiteScriptLabel: string;
DropDownSelectSiteScriptPlaceHolder: string;
AddSiteScriptToSiteDesignPanelTitle: string;
AddSiteScriptDialogSubText: string;
JSONSchemaErrorMessage: string;
DialogConfirmDeleteText: string;
DialogConfirmDeleteTitle: string;
WebTemplateCommunicationSite: string;
WebTemplateTeamSite: string;
SiteDesignRightsPanelTitle: string;
SiteScriptIdLabel: string;
EditSiteScriptDialogTitle: string;
AddSiteScriptDialogTitle: string;
AddSiteScriptDescriptionLabel: string;
AddSiteScriptTitleLabel: string;
PropertyPaneDescription: string;
BasicGroupName: string;
TitleFieldLabel: string;
ListViewColumnIdLabel:string;
ListViewColumnWebTemplateLabel:string;
ListViewColumnDescriptionLabel:string;
ListViewColumnNumberSiteScriptsLabel:string;
ErrorMessageUserNotAdmin:string;
LoadingLabel:string;
CommandbarNewLabel:string;
CommandbarEditLabel:string;
CommandbarDeleteLabel:string;
CommandbarRefreshLabel:string;
CommandbarRightsLabel:string;
CommandbarSiteScriptsLabel:string;
CommandbarApplyLabel:string;
SearchPlaceholder:string;
PrimaryButtonLabelSave:string;
PrimaryButtonLabelDelete:string;
DefaultButtonLabel:string;
PanelHeaderTextEdit:string;
PanelHeaderTextDelete:string;
messageTenantExist:string;
AddSiteDesignTitleLabel:string,
AddSiteDesignDescriptionLabel:string;
AddSiteDesignImageUrlLabel:string;
AddSiteDesignIsDefaultLabel:string;
SiteDesignIdLabel:string;
ListViewColumnIdPrincipalNameLabel:string;
SiteDesignTitleLabel:string;
WebTemplateLabel:string;
}
declare module 'SiteDesignsWebPartStrings' {
const strings: ISiteDesignsWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,64 @@
/**
* This script updates the package-solution version analogue to the
* the package.json file.
*/
if (process.env.npm_package_version === undefined) {
throw 'Package version cannot be evaluated';
}
// define path to package-solution file
const solution = './config/package-solution.json',
teams = './teams/manifest.json';
// require filesystem instanc
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');
}

Some files were not shown because too many files have changed in this diff Show More