This commit is contained in:
Ryan Schouten 2020-08-31 20:10:04 -07:00
commit 37a0a4b646
79 changed files with 46155 additions and 1214 deletions

View File

@ -74,10 +74,16 @@ Once the npm packages are installed, run the following command to preview your w
gulp serve gulp serve
``` ```
## Authors
This repository's contributors are all community members who volunteered their time to share code samples. Work is done as an open source community project, which each sample contained in their own solution.
## Contributions ## Contributions
These samples are direct from the feature teams, SharePoint PnP core team (http://aka.ms/m365pnp) or shared by the community. We welcome your input on issues and suggestions for new samples. We do also welcome community contributions around the client-side web parts. If there's any questions around that, just let us know. These samples are direct from the feature teams, SharePoint PnP core team (http://aka.ms/m365pnp) or shared by the community. We welcome your input on issues and suggestions for new samples. We do also welcome community contributions around the client-side web parts. If you have any questions, just let us know.
Please have a look on our [Contribution Guidance](./.github/CONTRIBUTING.md) before submitting your pull requests, so that we can get your contribution processed as fast as possible. Thx. Please have a look on our [Contribution Guidance](./.github/CONTRIBUTING.md) before submitting your pull requests, so that we can get your contribution processed as fast as possible.
## Code of Conduct
This repository has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
> Sharing is caring! > Sharing is caring!

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,33 @@
# 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
*.scss.d.ts

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.9.1",
"libraryName": "react-graph-cascading-managed-metadata",
"libraryId": "cdc626ca-e9a7-4d1d-bded-a574bc5e61d0",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,72 @@
# Cascading managed metadata using Graph API (beta)
## Summary
This web part shows how to use the Microsoft Graph APIs (beta) for Taxonomy to get the data.
### Functionality
![Cascading managed metadata](./assets/cmmd.gif)
### Termstore
![Term store](./assets/termstore.png)
## Used SharePoint Framework Version
![SPFx v1.11.0](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
## Applies to
* [SharePoint Framework Developer](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [Office 365 developer tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-developer-tenant)
## Pre-requisites
* Set up the termset structure as shown in the image above.
* For the cities, get the required latitude and longitude.
* Set the description of the city term as `latitude;longitude` (as highlighted for the term `London` in the image above).
## Solution
Solution|Author(s)
--------|---------
react-graph-cascading-managed-metadata| Anoop Tatti ([@anooptells](https://twitter.com/anooptells))
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|Aug 24, 2020|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
* Clone this repository
* in the command line run:
* `npm install`
* `gulp serve`
## Features
This sample illustrates the following concepts on top of the SharePoint Framework:
* Get data terms using Microsoft Graph API (beta).
* React Hooks
* Using async / await for the async calls
* Caching the data in session storage
* Usage of PnP SPFx controls (Maps and Placeholder)
* Office UI fabric components
### Enhancements
* Currently, this web part supports 2-level cascading. So there is scope to enhance this such that it supports more levels of cascading dynamically.
* Currently, this web part reads latitude and longitude from description of the city terms. If there is a way of getting these from the custom properties of the city terms, then that needs to be implemented.
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-graph-cascading-managed-metadata" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,19 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"cascading-managed-metadata-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/cascadingManagedMetadata/CascadingManagedMetadataWebPart.js",
"manifest": "./src/webparts/cascadingManagedMetadata/CascadingManagedMetadataWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"CascadingManagedMetadataWebPartStrings": "lib/webparts/cascadingManagedMetadata/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/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-graph-cascading-managed-metadata",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,14 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-graph-cascading-managed-metadata-client-side-solution",
"id": "cdc626ca-e9a7-4d1d-bded-a574bc5e61d0",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/react-graph-cascading-managed-metadata.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,36 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
const argv = build.rig.getYargs().argv;
const useCustomServe = argv['custom-serve'];
const fs = require("fs");
const workbenchApi = require("@microsoft/sp-webpart-workbench/lib/api");
if (useCustomServe) {
build.tslintCmd.enabled = false;
const ensureWorkbenchSubtask = build.subTask('ensure-workbench-task', function (gulp, buildOptions, done) {
this.log('Creating workbench.html file...');
try {
workbenchApi.default["/workbench"]();
} catch (e) { }
done();
});
build.rig.addPostBuildTask(build.task('ensure-workbench', ensureWorkbenchSubtask));
build.configureWebpack.mergeConfig({
additionalConfiguration: (generatedConfiguration) => {
fs.writeFileSync("./temp/_webpack_config.json", JSON.stringify(generatedConfiguration, null, 2));
return generatedConfiguration;
}
});
}
build.initialize(require('gulp'));

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
{
"name": "react-graph-cascading-managed-metadata",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test",
"serve": "cross-env NODE_OPTIONS=--max_old_space_size=4096 gulp bundle --custom-serve && cross-env NODE_OPTIONS=--max_old_space_size=4096 webpack-dev-server --mode development --config ./webpack.js --env.env=dev"
},
"dependencies": {
"@microsoft/sp-core-library": "1.11.0",
"@microsoft/sp-lodash-subset": "1.11.0",
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
"@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.11.0",
"@pnp/spfx-controls-react": "1.19.0",
"office-ui-fabric-react": "6.214.0",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"resolutions": {
"@types/react": "16.8.8"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.11.0",
"@microsoft/sp-module-interfaces": "1.11.0",
"@microsoft/sp-tslint-rules": "1.11.0",
"@microsoft/sp-webpart-workbench": "1.11.0",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",
"cross-env": "7.0.2",
"css-loader": "3.4.2",
"css-modules-typescript-loader": "4.0.0",
"del": "5.1.0",
"fork-ts-checker-webpack-plugin": "4.1.0",
"gulp": "~3.9.1",
"node-sass": "4.13.1",
"sass-loader": "8.0.2",
"style-loader": "1.1.3",
"ts-loader": "6.2.1",
"webpack": "4.42.0",
"webpack-cli": "3.3.11",
"webpack-dev-server": "3.10.3"
}
}

View File

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

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "d3ff339d-201a-4f48-860c-0a3e435c0ad6",
"alias": "CascadingManagedMetadataWebPart",
"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": "Cascading Managed Metadata" },
"description": { "default": "Gets the data from termstore using Microsoft Graph" },
"officeFabricIconFontName": "DependencyAdd",
"properties": {
"termSetId": ""
}
}]
}

View File

@ -0,0 +1,93 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version, DisplayMode } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import * as strings from 'CascadingManagedMetadataWebPartStrings';
import CascadingManagedMetadata from './components/CascadingManagedMetadata';
import { MSGraph } from './services/MSGraph';
export interface ICascadingManagedMetadataWebPartProps {
termSetId: string;
}
export default class CascadingManagedMetadataWebPart extends BaseClientSideWebPart<ICascadingManagedMetadataWebPartProps> {
private _placeholder = null;
public async render(): Promise<void> {
await MSGraph.Init(this.context);
let renderElement = null;
//TODO: Use function to check if GUID?
if (this.properties.termSetId && this.properties.termSetId.length == 36) {
renderElement = React.createElement(
CascadingManagedMetadata,
{
termSetId: this.properties.termSetId
}
);
} else {
if (this.displayMode === DisplayMode.Edit) {
const { Placeholder } = await import(
/* webpackChunkName: 'cascadingManagedMetadata-property-pane' */
'@pnp/spfx-controls-react/lib/Placeholder'
);
this._placeholder = Placeholder;
const placeholder: React.ReactElement<any> = React.createElement(
this._placeholder,
{
iconName: 'Edit',
iconText: 'Configure your webpart',
description: 'Please configure the web part.',
buttonLabel: 'Configure',
onConfigure: this._onConfigure.bind(this)
}
);
renderElement = placeholder;
} else {
renderElement = React.createElement('div', null);
}
}
ReactDom.render(renderElement, this.domElement);
}
private _onConfigure = () => {
this.context.propertyPane.open();
}
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('termSetId', {
label: strings.TermSetIdFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,5 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.cascadingManagedMetadata {
}

View File

@ -0,0 +1,16 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'button': string;
'cascadingManagedMetadata': string;
'column': string;
'container': string;
'description': string;
'label': string;
'ms-Grid': string;
'row': string;
'subTitle': string;
'title': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@ -0,0 +1,99 @@
import * as React from 'react';
import styles from './CascadingManagedMetadata.module.scss';
import { ICascadingManagedMetadataProps } from './ICascadingManagedMetadataProps';
import { MSGraph } from '../services/MSGraph';
import { ITerms } from '../../interfaces';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import { Map, ICoordinates } from "@pnp/spfx-controls-react/lib/Map";
import { MMDService } from '../services/MMDService';
const CascadingManagedMetadata: React.SFC<ICascadingManagedMetadataProps> = (props) => {
const [countriesList, setCountriesList] = React.useState<IDropdownOption[]>([]);
const [citiesList, setCitiesList] = React.useState<IDropdownOption[]>([]);
const [selectedCityCoordinates, setSelectedCityCoordinates] = React.useState<string>(null);
const [selectedCity, setSelectedCity] = React.useState<string>(null);
const [coordinates, setCoordinates] = React.useState<ICoordinates>({ latitude: null, longitude: null });
React.useEffect(() => {
_getCountries().then(countries => {
const options: IDropdownOption[] = countries.value.map(c => ({ key: c.id, text: c.labels[0].name }));
setCountriesList(options);
});
}, []);
//* Get the country terms i.e. level 1 children using Graph
const _getCountries = async (): Promise<ITerms> => {
let countries: ITerms = await MSGraph.Get(`/termStore/sets/${props.termSetId}/children`, "beta");
return (countries);
};
//* Get the city terms under a country i.e. level 2 children using Graph
const _onCountryChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
setCoordinates({ latitude: null, longitude: null });
setSelectedCityCoordinates(null);
setSelectedCity(null);
let countryTermId: string = item.key.toString();
MMDService.GetTermsAsDropdownOptions(`/termStore/sets/${props.termSetId}/terms/${countryTermId}/children`, countryTermId, true).then(options => {
setCitiesList(options);
});
};
//* Extract co-ordinates from key of the dropdown option
//* The key will contain the description of the term
//* The description of the term will be of the format latitude;longitude
const _onCityChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
setSelectedCity(item.text);
setSelectedCityCoordinates(item.key.toString());
const [lat, long] = item.key.toString().split(';');
const coordinates: ICoordinates = {
latitude: isNaN(Number(lat)) ? null : Number(lat),
longitude: isNaN(Number(long)) ? null : Number(long)
};
setCoordinates(coordinates);
};
return (
<div>
<Dropdown
label="Country"
placeHolder="Select a country"
options={countriesList}
onChange={_onCountryChange} />
<Dropdown
label="City"
selectedKey={selectedCityCoordinates}
placeHolder="Select a city"
options={citiesList}
onChange={_onCityChange} />
{
coordinates.latitude && coordinates.longitude ?
(
<React.Fragment>
<Map
titleText={`Map of our office in ${selectedCity}`}
coordinates={coordinates}
zoom={15}
enableSearch={false} />
</React.Fragment>
) :
(
<div style={{ marginTop: "15px" }}>
<MessageBar messageBarType={MessageBarType.warning}>
{selectedCity ? "To see the map, please check if the coordinates have been configured correctly."
: "To see the map, please select a city."}
</MessageBar>
</div>
)
}
</div>
);
};
export default CascadingManagedMetadata;

View File

@ -0,0 +1,3 @@
export interface ICascadingManagedMetadataProps {
termSetId: string;
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Properties",
"BasicGroupName": "Basic",
"TermSetIdFieldLabel": "Termset Id"
}
});

View File

@ -0,0 +1,10 @@
declare interface ICascadingManagedMetadataWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
TermSetIdFieldLabel: string;
}
declare module 'CascadingManagedMetadataWebPartStrings' {
const strings: ICascadingManagedMetadataWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,65 @@
import { ITerms, IOption } from "../../interfaces";
import { MSGraph } from "./MSGraph";
import { IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
export class MMDService {
private static _sessionStorageKey: string = "CMMD_Options";
public static async GetTermsAsDropdownOptions(apiUrl: string, parent: string, tryFromCache: boolean): Promise<IDropdownOption[]> {
let options: IDropdownOption[] = [];
if (tryFromCache) {
let optionsFromCache: IOption[] = this._fetchFromSessionStorge();
if (optionsFromCache.length) {
let requiredOptionsFromCache = optionsFromCache.filter(o => o.parent == parent);
if (requiredOptionsFromCache.length) {
options = requiredOptionsFromCache.map(r => ({ key: r.key, text: r.text }));
return options;
}
}
}
//Get data using Graph
return await this._getTermsAsDropdownOptionsUsingGraph(apiUrl, parent);
}
private static async _getTermsAsDropdownOptionsUsingGraph(apiUrl: string, parent: string): Promise<IDropdownOption[]> {
let terms: ITerms = await MSGraph.Get(apiUrl, "beta");
if (terms.value) {
//* Set key as description of the term
//* Description will be of the format latitude;longitude
//* This will be used to render maps
const options: IDropdownOption[] = terms.value.map(t => ({
key: t.descriptions[0] ? t.descriptions[0].description : t.id,
text: t.labels[0].name }));
let optionsToStoreInCache: IOption[] = options.map(o => ({
key: o.key.toString(),
text: o.text,
parent
}));
let optionsFromCache: IOption[] = this._fetchFromSessionStorge();
optionsToStoreInCache = [...optionsFromCache, ...optionsToStoreInCache];
window.sessionStorage.setItem(this._sessionStorageKey, JSON.stringify(optionsToStoreInCache));
console.log("Options added in cache");
return options;
} else {
return [];
}
}
private static _fetchFromSessionStorge(): IOption[] {
let result: IOption[] = [];
let stringResult: string = window.sessionStorage.getItem(this._sessionStorageKey);
if (stringResult) {
try {
result = JSON.parse(stringResult);
if(result.length) {
console.log("Fetched options from cache");
}
} catch (error) {
console.error(error);
}
}
return result;
}
}

View File

@ -0,0 +1,90 @@
//* Thank you Mikael Svenson
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { GraphError } from '@microsoft/microsoft-graph-client';
import { MSGraphClient } from '@microsoft/sp-http';
export class MSGraph {
private static _graphClient: MSGraphClient;
public static async Init(context: WebPartContext) {
this._graphClient = await context.msGraphClientFactory.getClient();
}
public static async Get(apiUrl: string, version: string = "v1.0", selectProperties?: string[], expandProperties?: string[], filter?: string): Promise<any> {
var p = new Promise<string>(async (resolve, reject) => {
let query = this._graphClient.api(apiUrl).version(version);
if (selectProperties && selectProperties.length > 0) {
query = query.select(selectProperties);
}
if (filter && filter.length > 0) {
query = query.filter(filter);
}
if (expandProperties && expandProperties.length > 0) {
query = query.expand(expandProperties);
}
let callback = (error: GraphError, response: any, rawResponse?: any) => {
if (error) {
reject(error);
} else {
resolve(response);
}
};
await query.get(callback);
});
return p;
}
public static async Patch(apiUrl: string, version: string = "v1.0", content: any): Promise<any> {
var p = new Promise<string>(async (resolve, reject) => {
if (typeof (content) === "object") {
content = JSON.stringify(content);
}
let query = this._graphClient.api(apiUrl).version(version);
let callback = (error: GraphError, _response: any, rawResponse?: any) => {
if (error) {
reject(error);
} else {
resolve();
}
};
await query.update(content, callback);
});
return p;
}
public static async Post(apiUrl: string, version: string = "v1.0", content: any): Promise<any> {
var p = new Promise<string>(async (resolve, reject) => {
if (typeof (content) === "object") {
content = JSON.stringify(content);
}
let query = this._graphClient.api(apiUrl).version(version);
let callback = (error: GraphError, response: any, rawResponse?: any) => {
if (error) {
reject(error);
} else {
resolve(response);
}
};
await query.post(content, callback);
});
return p;
}
public static async Delete(apiUrl: string, version: string = "v1.0"): Promise<any> {
var p = new Promise<string>(async (resolve, reject) => {
let query = this._graphClient.api(apiUrl).version(version);
let callback = (error: GraphError, response: any, rawResponse?: any) => {
if (error) {
reject(error);
} else {
resolve(response);
}
};
await query.delete(callback);
});
return p;
}
}

View File

@ -0,0 +1,4 @@
export interface IDescription {
description: string;
languageTag: string;
}

View File

@ -0,0 +1,5 @@
export interface ILabel {
name: string;
isDefault: boolean;
languageTag: string;
}

View File

@ -0,0 +1,5 @@
export interface IOption {
key: string;
text: string;
parent: string;
}

View File

@ -0,0 +1,10 @@
import { IDescription } from "./IDescription";
import { ILabel } from "./ILabel";
export interface ITerm {
id: string;
createdDateTime: Date;
lastModifiedDateTime: Date;
labels: ILabel[];
descriptions: IDescription[];
}

View File

@ -0,0 +1,5 @@
import { ITerm } from "./ITerm";
export interface ITerms {
value: ITerm[];
}

View File

@ -0,0 +1,5 @@
export { ILabel } from './ILabel';
export { IDescription } from './IDescription';
export { ITerm } from './ITerm';
export { ITerms } from './ITerms';
export { IOption } from './IOption';

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

View File

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

View File

@ -0,0 +1,259 @@
const path = require("path");
const fs = require("fs");
const webpack = require("webpack");
const resolve = require("path").resolve;
const CertStore = require("@microsoft/gulp-core-build-serve/lib/CertificateStore");
const CertificateStore = CertStore.CertificateStore || CertStore.default;
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const del = require("del");
const port = 4321;
const host = "https://localhost:" + port;
///
// Transforms define("<guid>", ...) to web part specific define("<web part id_version", ...)
// the same approach is used inside copyAssets SPFx build step
///
class DynamicLibraryPlugin {
constructor(options) {
this.opitons = options;
}
apply(compiler) {
compiler.hooks.emit.tap("DynamicLibraryPlugin", compilation => {
for (const assetId in this.opitons.modulesMap) {
const moduleMap = this.opitons.modulesMap[assetId];
if (compilation.assets[assetId]) {
const rawValue = compilation.assets[assetId].children[0]._value;
compilation.assets[assetId].children[0]._value = rawValue.replace(this.opitons.libraryName, moduleMap.id + "_" + moduleMap.version);
}
}
});
}
}
///
// Removes *.module.scss.ts on the first execution in order prevent conflicts with *.module.scss.d.ts
// generated by css-modules-typescript-loader
///
class ClearCssModuleDefinitionsPlugin {
constructor(options) {
this.options = options || {};
}
apply(compiler) {
compiler.hooks.done.tap("FixStylesPlugin", stats => {
if (!this.options.deleted) {
setTimeout(() => {
del.sync(["src/**/*.module.scss.ts"]);
}, 3000);
this.options.deleted = true;
}
});
}
}
let baseConfig = {
target: "web",
mode: "development",
devtool: "source-map",
resolve: {
extensions: [".ts", ".tsx", ".js"],
modules: ["node_modules"]
},
context: path.resolve(__dirname),
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader",
options: {
transpileOnly: true,
compilerOptions: {
declarationMap: false
}
},
exclude: /node_modules/
},
{
use: [{
loader: "@microsoft/loader-cased-file",
options: {
name: "[name:lower]_[hash].[ext]"
}
}],
test: /\.(jpe?g|png|woff|eot|ttf|svg|gif|dds)$/i
},
{
use: [{
loader: "html-loader"
}],
test: /\.html$/
},
{
test: /\.css$/,
use: [
{
loader: "@microsoft/loader-load-themed-styles",
options: {
async: true
}
},
{
loader: "css-loader"
}
]
},
{
test: function (fileName) {
return fileName.endsWith(".module.scss"); // scss modules support
},
use: [
{
loader: "@microsoft/loader-load-themed-styles",
options: {
async: true
}
},
"css-modules-typescript-loader",
{
loader: "css-loader",
options: {
modules: {
localIdentName: "[local]_[hash:base64:8]"
}
}
}, // translates CSS into CommonJS
"sass-loader" // compiles Sass to CSS, using Node Sass by default
]
},
{
test: function (fileName) {
return !fileName.endsWith(".module.scss") && fileName.endsWith(".scss"); // just regular .scss
},
use: [
{
loader: "@microsoft/loader-load-themed-styles",
options: {
async: true
}
},
"css-loader", // translates CSS into CommonJS
"sass-loader" // compiles Sass to CSS, using Node Sass by default
]
}
]
},
plugins: [
new ForkTsCheckerWebpackPlugin({
tslint: true
}),
new ClearCssModuleDefinitionsPlugin(),
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
"process.env.DEBUG": JSON.stringify(true),
"DEBUG": JSON.stringify(true)
})],
devServer: {
hot: false,
contentBase: resolve(__dirname),
publicPath: host + "/dist/",
host: "localhost",
port: port,
disableHostCheck: true,
historyApiFallback: true,
open: true,
writeToDisk: false,
openPage: host + "/temp/workbench.html",
stats: {
preset: "errors-only",
colors: true,
chunks: false,
modules: false,
assets: false
},
proxy: { // url re-write for resources to be served directly from src folder
"/lib/**/loc/*.js": {
target: host,
pathRewrite: { "^/lib": "/src" },
secure: false
}
},
headers: {
"Access-Control-Allow-Origin": "*",
},
https: {
cert: CertificateStore.instance.certificateData,
key: CertificateStore.instance.keyData
}
},
}
const createConfig = function () {
// remove old css module TypeScript definitions
del.sync(["dist/*.js", "dist/*.map"]);
// we need only "externals", "output" and "entry" from the original webpack config
let originalWebpackConfig = require("./temp/_webpack_config.json");
baseConfig.externals = originalWebpackConfig.externals;
baseConfig.output = originalWebpackConfig.output;
baseConfig.entry = getEntryPoints(originalWebpackConfig.entry);
baseConfig.output.publicPath = host + "/dist/";
const manifest = require("./temp/manifests.json");
const modulesMap = {};
const originalEntries = Object.keys(originalWebpackConfig.entry);
for (const jsModule of manifest) {
if (jsModule.loaderConfig
&& jsModule.loaderConfig.entryModuleId
&& originalEntries.indexOf(jsModule.loaderConfig.entryModuleId) !== -1) {
modulesMap[jsModule.loaderConfig.entryModuleId + ".js"] = {
id: jsModule.id,
version: jsModule.version
}
}
}
baseConfig.plugins.push(new DynamicLibraryPlugin({
modulesMap: modulesMap,
libraryName: originalWebpackConfig.output.library
}));
return baseConfig;
}
function getEntryPoints(entry) {
// fix: ".js" entry needs to be ".ts"
// also replaces the path form /lib/* to /src/*
let newEntry = {};
let libSearchRegexp;
if (path.sep === "/") {
libSearchRegexp = /\/lib\//gi;
} else {
libSearchRegexp = /\\lib\\/gi;
}
const srcPathToReplace = path.sep + "src" + path.sep;
for (const key in entry) {
let entryPath = entry[key];
if (entryPath.indexOf("bundle-entries") === -1) {
entryPath = entryPath.replace(libSearchRegexp, srcPathToReplace).slice(0, -3) + ".ts";
} else {
// replace paths and extensions in bundle file
let bundleContent = fs.readFileSync(entryPath).toString();
bundleContent = bundleContent.replace(libSearchRegexp, srcPathToReplace).replace(/\.js/gi, ".ts");
fs.writeFileSync(entryPath, bundleContent);
}
newEntry[key] = entryPath;
}
return newEntry;
}
module.exports = createConfig();

View File

@ -25,6 +25,8 @@ Each Site has a symbol indicate if it is SharePoint Site, Group, Group and OnDri
If the Group has a microsoft team associated and user has permissions to access the symbol of teams will be displayed. If the Group has a microsoft team associated and user has permissions to access the symbol of teams will be displayed.
     
![MySites](./assets/MySites.gif) ![MySites](./assets/MySites.gif)
![MySites](./assets/Screenshot%202020-08-06%20at%2013.50.51.png) ![MySites](./assets/Screenshot%202020-08-06%20at%2013.50.51.png)
@ -56,6 +58,7 @@ react-my-sites|João Mendes
Version|Date|Comments Version|Date|Comments
-------|----|-------- -------|----|--------
1.0.0|August 6, 2020|Initial release 1.0.0|August 6, 2020|Initial release
1.0.1|August 29, 2020|Additional updates
## Disclaimer ## Disclaimer

View File

@ -3,7 +3,7 @@
"solution": { "solution": {
"name": "react-my-sites-client-side-solution", "name": "react-my-sites-client-side-solution",
"id": "ad28b382-886b-4b2a-9646-92de8a0b1d13", "id": "ad28b382-886b-4b2a-9646-92de8a0b1d13",
"version": "1.0.0.0", "version": "1.0.1.0",
"includeClientSideAssets": true, "includeClientSideAssets": true,
"skipFeatureDeployment": true, "skipFeatureDeployment": true,
"isDomainIsolated": false, "isDomainIsolated": false,

View File

@ -1,6 +1,6 @@
{ {
"name": "react-my-sites", "name": "react-my-sites",
"version": "0.0.1", "version": "1.0.1",
"private": true, "private": true,
"main": "lib/index.js", "main": "lib/index.js",
"engines": { "engines": {

View File

@ -1,6 +1,7 @@
export enum Filters { export enum Filters {
"SharePoint", "All",
"Group", "Group",
"OneDrive", "OneDrive",
"All", "SharePoint",
"Site"
} }

View File

@ -49,7 +49,8 @@ export const useUserSites = () => {
const getUserSites = async ( const getUserSites = async (
searchString?: string, searchString?: string,
itemsPerPage?: number, itemsPerPage?: number,
filter?: Filters filter?: Filters,
site?:string
): Promise<SearchResults> => { ): Promise<SearchResults> => {
let searchResults: SearchResults = null; let searchResults: SearchResults = null;
let _filter: string = ""; let _filter: string = "";
@ -62,12 +63,17 @@ export const useUserSites = () => {
_filter = ` GroupId:a* OR GroupId:b* OR GroupId:c* OR GroupId:d* OR GroupId:e* OR GroupId:f* OR GroupId:g* OR GroupId:h* OR GroupId:i* OR GroupId:j* OR GroupId:k* OR GroupId:l* OR GroupId:m* OR GroupId:n* OR GroupId:o* OR GroupId:p* OR GroupId:q* OR GroupId:r* OR GroupId:s* OR GroupId:t* OR GroupId:u* OR GroupId:v* OR GroupId:w* OR GroupId:x* OR GroupId:y* OR GroupId:z* OR GroupId:1* OR GroupId:2* OR GroupId:3* OR GroupId:4* OR GroupId:5* OR GroupId:6* OR GroupId:7* OR GroupId:8* OR GroupId:9* OR GroupId:0*`; _filter = ` GroupId:a* OR GroupId:b* OR GroupId:c* OR GroupId:d* OR GroupId:e* OR GroupId:f* OR GroupId:g* OR GroupId:h* OR GroupId:i* OR GroupId:j* OR GroupId:k* OR GroupId:l* OR GroupId:m* OR GroupId:n* OR GroupId:o* OR GroupId:p* OR GroupId:q* OR GroupId:r* OR GroupId:s* OR GroupId:t* OR GroupId:u* OR GroupId:v* OR GroupId:w* OR GroupId:x* OR GroupId:y* OR GroupId:z* OR GroupId:1* OR GroupId:2* OR GroupId:3* OR GroupId:4* OR GroupId:5* OR GroupId:6* OR GroupId:7* OR GroupId:8* OR GroupId:9* OR GroupId:0*`;
break; break;
case Filters.OneDrive: case Filters.OneDrive:
_filter = " SiteGroup:Onedrive"; _filter = " WebTemplate:SPSPERS"; // OneDrive
// _filter = " SiteGroup:Onedrive";
break; break;
case Filters.SharePoint: case Filters.SharePoint:
_filter = _filter =
" SiteGroup:SharePoint AND NOT(GroupId:b* OR GroupId:c* OR GroupId:d* OR GroupId:e* OR GroupId:f* OR GroupId:g* OR GroupId:h* OR GroupId:i* OR GroupId:j* OR GroupId:k* OR GroupId:l* OR GroupId:m* OR GroupId:n* OR GroupId:o* OR GroupId:p* OR GroupId:q* OR GroupId:r* OR GroupId:s* OR GroupId:t* OR GroupId:u* OR GroupId:v* OR GroupId:w* OR GroupId:x* OR GroupId:y* OR GroupId:z* OR GroupId:1* OR GroupId:2* OR GroupId:3* OR GroupId:4* OR GroupId:5* OR GroupId:6* OR GroupId:7* OR GroupId:8* OR GroupId:9* OR GroupId:0*)"; " SiteGroup:SharePoint AND NOT(GroupId:b* OR GroupId:c* OR GroupId:d* OR GroupId:e* OR GroupId:f* OR GroupId:g* OR GroupId:h* OR GroupId:i* OR GroupId:j* OR GroupId:k* OR GroupId:l* OR GroupId:m* OR GroupId:n* OR GroupId:o* OR GroupId:p* OR GroupId:q* OR GroupId:r* OR GroupId:s* OR GroupId:t* OR GroupId:u* OR GroupId:v* OR GroupId:w* OR GroupId:x* OR GroupId:y* OR GroupId:z* OR GroupId:1* OR GroupId:2* OR GroupId:3* OR GroupId:4* OR GroupId:5* OR GroupId:6* OR GroupId:7* OR GroupId:8* OR GroupId:9* OR GroupId:0*)";
break; break;
case Filters.Site:
_filter = `Path:${site}`;
break;
} }
const q = SearchQueryBuilder( const q = SearchQueryBuilder(
@ -79,6 +85,11 @@ export const useUserSites = () => {
Direction: SortDirection.Descending, Direction: SortDirection.Descending,
}) })
.selectProperties( .selectProperties(
"ParentLink",
"SPSiteURL",
"SiteID",
"SPWebUrl",
"WebId",
"SiteLogo", "SiteLogo",
"SiteClosed", "SiteClosed",
"RelatedHubSites", "RelatedHubSites",
@ -94,8 +105,10 @@ export const useUserSites = () => {
"ModifiedById", "ModifiedById",
"LastModifiedTime", "LastModifiedTime",
"OriginalPath", "OriginalPath",
"Path",
"Title", "Title",
"Created" "Created",
"WebTemplate"
); );
const results = await sp.search(q); const results = await sp.search(q);
searchResults = results; // set the current results searchResults = results; // set the current results
@ -103,5 +116,47 @@ export const useUserSites = () => {
return searchResults; return searchResults;
}; };
return { getUserSites, checkGroupHasTeam }; // Get User Sites
const getUserWebs = async (
): Promise<SearchResults> => {
let searchResults: SearchResults = null;
const q = SearchQueryBuilder(
`(contentclass:STS_Web)`
)
.rowLimit(100000)
.selectProperties(
"ParentLink",
"SPSiteURL",
"SiteID",
"SPWebUrl",
"WebId",
"SiteLogo",
"SiteClosed",
"RelatedHubSites",
"IsHubSite",
"GroupId",
"RelatedGroupId",
"SiteGroup",
"Author",
"CreatedBy",
"CreatedById",
"AccountName",
"ModifiedBy",
"ModifiedById",
"LastModifiedTime",
"OriginalPath",
"Path",
"Title",
"Created",
"WebTemplate"
);
const results = await sp.search(q);
searchResults = results; // set the current results
console.log("webs",searchResults);
return searchResults;
};
return { getUserSites, checkGroupHasTeam, getUserWebs };
}; };

View File

@ -1,3 +1,4 @@
import { IContextualMenuProps } from "office-ui-fabric-react";
export interface IMySitesState { export interface IMySitesState {
sites: any[]; sites: any[];
@ -9,4 +10,7 @@ export interface IMySitesState {
totalPages:number; totalPages:number;
searchValue:string; searchValue:string;
currentFilter?:number; currentFilter?:number;
currentFilterName?:string;
currentSelectedSite?:string;
filterMenuProps: IContextualMenuProps;
} }

View File

@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import { Filters} from '../../../../Entities/EnumFilters'; import { Filters } from "../../../../Entities/EnumFilters";
import "./paginationOverride.module.scss"; import "./paginationOverride.module.scss";
import { IMySitesProps } from "./IMySitesProps"; import { IMySitesProps } from "./IMySitesProps";
import { escape } from "@microsoft/sp-lodash-subset"; import { escape } from "@microsoft/sp-lodash-subset";
@ -18,6 +18,7 @@ import {
IContextualMenuItem, IContextualMenuItem,
FontIcon, FontIcon,
Label, Label,
ContextualMenuItemType,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import { WebPartTitle } from "@pnp/spfx-controls-react"; import { WebPartTitle } from "@pnp/spfx-controls-react";
import { useUserSites } from "../../../../Hooks/useUserSites"; import { useUserSites } from "../../../../Hooks/useUserSites";
@ -31,7 +32,11 @@ import _ from "lodash";
import { MSGraphClient } from "@microsoft/sp-http"; import { MSGraphClient } from "@microsoft/sp-http";
let _searchResults: SearchResults = null; let _searchResults: SearchResults = null;
let _msGraphClient:MSGraphClient = undefined; let _msGraphClient: MSGraphClient = undefined;
let _filterMenuProps: IContextualMenuProps = undefined;
// Get Hook functions
const { getUserSites, getUserWebs } = useUserSites();
export const MySites: React.FunctionComponent<IMySitesProps> = ( export const MySites: React.FunctionComponent<IMySitesProps> = (
props: IMySitesProps props: IMySitesProps
@ -47,12 +52,11 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
}, },
webPartTile: { webPartTile: {
fontWeight: 500, fontWeight: 500,
marginBottom: 20 marginBottom: 20,
}, },
}); });
// Document Card Styles // Document Card Styles
// state // state
const [state, setState] = React.useState<IMySitesState>({ const [state, setState] = React.useState<IMySitesState>({
errorMessage: "", errorMessage: "",
@ -61,32 +65,40 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
hasError: false, hasError: false,
title: props.title, title: props.title,
currentPage: 1, currentPage: 1,
totalPages: 50, totalPages: 0,
searchValue: "", searchValue: "",
currentFilter: Filters.All, currentFilter: Filters.All,
currentFilterName: "All",
currentSelectedSite: undefined,
filterMenuProps: undefined,
}); });
const filterIcon: IIconProps = { iconName: "Filter" }; const filterIcon: IIconProps = { iconName: "Filter" };
// Get Hook functions
const { getUserSites } = useUserSites();
// get User Sites // get User Sites
const _getUserSites = async ( const _getUserSites = async (
searchString?: string, searchString?: string,
currentFilter?: Filters currentFilter?: Filters,
currentFilterName?:string,
site?:string
) => { ) => {
try { try {
console.log('tiles var', props.themeVariant);
setState({ ...state, isLoading: true }); setState({ ...state, isLoading: true });
const { itemsPerPage } = props; const { itemsPerPage } = props;
const searchResults = await getUserSites(searchString, itemsPerPage, currentFilter); const searchResults = await getUserSites(
searchString,
itemsPerPage,
currentFilter,
site
);
_searchResults = searchResults; _searchResults = searchResults;
let _totalPages: number = searchResults.TotalRows / itemsPerPage; let _totalPages: number = searchResults.TotalRows / itemsPerPage;
const _modulus: number = searchResults.TotalRows % itemsPerPage; const _modulus: number = searchResults.TotalRows % itemsPerPage;
_totalPages = _totalPages =
_modulus > 0 ? toInteger(_totalPages) + 1 : toInteger(_totalPages); _modulus > 0 ? toInteger(_totalPages) + 1 : toInteger(_totalPages);
setState({ setState({
...state,
searchValue: "", searchValue: "",
currentPage: 1, currentPage: 1,
totalPages: _totalPages, totalPages: _totalPages,
@ -95,7 +107,11 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
hasError: false, hasError: false,
errorMessage: "", errorMessage: "",
sites: _searchResults.PrimarySearchResults, sites: _searchResults.PrimarySearchResults,
currentFilter: currentFilter currentFilter: currentFilter,
currentSelectedSite: site,
currentFilterName: currentFilterName,
// tslint:disable-next-line: no-use-before-declare
filterMenuProps: _filterMenuProps,
}); });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@ -108,110 +124,154 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
} }
}; };
const _Filtersites = async (filter: string) => { const _Filtersites = async (filter: string, site?:string) => {
setState({ ...state, isLoading: true });
let _filteredSites: any[] = [];
switch (filter) { switch (filter) {
case "All": case "All":
setState({...state,currentFilter: Filters.All}); await _getUserSites("", Filters.All, "All");
await _getUserSites('', Filters.All);
break; break;
case "Groups": case "Groups":
setState({...state,currentFilter: Filters.Group}); await _getUserSites("", Filters.Group, "Groups");
await _getUserSites('', Filters.Group);
break; break;
case "OneDrive": case "OneDrive":
setState({...state,currentFilter: Filters.OneDrive}); await _getUserSites("", Filters.OneDrive, "OneDrive");
await _getUserSites('', Filters.OneDrive);
break; break;
case "SharePoint": case "SharePoint":
setState({...state,currentFilter: Filters.SharePoint}); await _getUserSites("", Filters.SharePoint, "SharePoint");
await _getUserSites('', Filters.SharePoint);
break; break;
default: default:
setState({ ...state, isLoading: false }); await _getUserSites("", Filters.Site, filter, site);
break;
} }
}; };
const filterMenuProps: IContextualMenuProps = {
items: [
{
key: "0",
text: "All",
iconProps: { iconName: "ThumbnailView" },
onClick: (
ev:
| React.MouseEvent<HTMLElement, MouseEvent>
| React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => {
_Filtersites(item.text);
},
},
{
key: "1",
text: "SharePoint",
iconProps: { iconName: "SharepointAppIcon16" },
onClick: (
ev:
| React.MouseEvent<HTMLElement, MouseEvent>
| React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => {
_Filtersites(item.text);
},
},
{
key: "2",
text: "Groups",
iconProps: { iconName: "Group" },
onClick: (
ev:
| React.MouseEvent<HTMLElement, MouseEvent>
| React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => {
_Filtersites(item.text);
},
},
{
key: "3",
text: "OneDrive",
iconProps: { iconName: "onedrive" },
onClick: (
ev:
| React.MouseEvent<HTMLElement, MouseEvent>
| React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => {
_Filtersites(item.text);
},
},
],
};
// useEffect component did mount or modified // useEffect component did mount or modified
React.useEffect(() => { React.useEffect(() => {
(async () => { (async () => {
_msGraphClient = await props.context.msGraphClientFactory.getClient(); _msGraphClient = await props.context.msGraphClientFactory.getClient();
await _getUserSites("",state.currentFilter);
const _sitesWithSubSties = await getUserWebs();
console.log("subsites", _sitesWithSubSties);
const _uniqweb = _.uniqBy(
_sitesWithSubSties.PrimarySearchResults,
"ParentLink"
);
_filterMenuProps = {
items: [
{
key: "0",
text: "All",
iconProps: { iconName: "ThumbnailView" },
onClick: (
ev:
| React.MouseEvent<HTMLElement, MouseEvent>
| React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => {
_Filtersites(item.text);
},
},
{
key: "1",
text: "SharePoint",
iconProps: { iconName: "SharepointAppIcon16" },
onClick: (
ev:
| React.MouseEvent<HTMLElement, MouseEvent>
| React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => {
_Filtersites(item.text);
},
},
{
key: "2",
text: "Groups",
iconProps: { iconName: "Group" },
onClick: (
ev:
| React.MouseEvent<HTMLElement, MouseEvent>
| React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => {
_Filtersites(item.text);
},
},
{
key: "3",
text: "OneDrive",
iconProps: { iconName: "onedrive" },
onClick: (
ev:
| React.MouseEvent<HTMLElement, MouseEvent>
| React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => {
_Filtersites(item.text);
},
},
],
};
if (_sitesWithSubSties.PrimarySearchResults.length > 0){
_filterMenuProps.items.push(
{
key: 'sites',
itemType: ContextualMenuItemType.Header,
text: 'Sites with Sub Sites',
itemProps: {
lang: 'en-us',
},
}
);
}
// Add Site Collections with sub
for (const web of _uniqweb) {
// tslint:disable-next-line: no-use-before-declare
const _lastHasPosition: number = (web.ParentLink as string).lastIndexOf(
"/"
);
const _siteName: string = (web.ParentLink as string).substring(
_lastHasPosition + 1
);
// tslint:disable-next-line: no-use-before-declare
_filterMenuProps.items.push({
key: web.ParentLink,
text: _siteName,
iconProps: { iconName: "DrillExpand" },
onClick: (
ev:
| React.MouseEvent<HTMLElement, MouseEvent>
| React.KeyboardEvent<HTMLElement>,
item: IContextualMenuItem
) => {
// tslint:disable-next-line: no-use-before-declare
_Filtersites(item.text, item.key);
},
});
}
await _getUserSites("", state.currentFilter, state.currentFilterName);
})(); })();
}, [props.title, props.itemsPerPage]); }, [props.title, props.itemsPerPage]);
// On Search Sites // On Search Sites
const _onSearch = async (value: string) => { const _onSearch = async (value: string) => {
await _getUserSites(value,state.currentFilter); await _getUserSites(value, state.currentFilter,state.currentFilterName, state.currentSelectedSite);
}; };
// On Search Sites // On Search Sites
const _onClear = async (ev: any) => { const _onClear = async (ev: any) => {
await _getUserSites("",state.currentFilter); await _getUserSites("", state.currentFilter,state.currentFilterName, state.currentSelectedSite);
}; };
// Render component
// Render component if (state.hasError) {
if (state.hasError) { // render message error // render message error
return ( return (
<MessageBar messageBarType={MessageBarType.error}> <MessageBar messageBarType={MessageBarType.error}>
{state.errorMessage} {state.errorMessage}
@ -229,9 +289,8 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
themeVariant={props.themeVariant} themeVariant={props.themeVariant}
updateProperty={props.updateProperty} updateProperty={props.updateProperty}
className={stylesComponent.webPartTile} className={stylesComponent.webPartTile}
/> />
<Stack horizontal horizontalAlign="end" tokens={{ childrenGap: 10 }}> <Stack horizontal verticalAlign="center" horizontalAlign="end" wrap tokens={{ childrenGap: 5 }}>
<SearchBox <SearchBox
placeholder="Search my sites" placeholder="Search my sites"
underlined={true} underlined={true}
@ -246,10 +305,11 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
/> />
<CommandButton <CommandButton
iconProps={filterIcon} iconProps={filterIcon}
text={Filters[state.currentFilter]} text={state.currentFilterName}
menuProps={filterMenuProps} menuProps={state.filterMenuProps}
disabled={false} disabled={false}
checked={true} checked={true}
title="filter"
/> />
</Stack> </Stack>
{state.isLoading ? ( {state.isLoading ? (
@ -259,29 +319,40 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
></Spinner> ></Spinner>
) : ( ) : (
<> <>
{ // has sites ? {
// has sites ?
state.sites.length > 0 ? state.sites.length > 0 ? (
<div className={stylesComponent.containerTiles}> <div className={stylesComponent.containerTiles}>
{state.sites.map((site:any, i: number) => { {state.sites.map((site: any, i: number) => {
return ( return (
<SiteTile <SiteTile
site={site} site={site}
msGraphClient={_msGraphClient} msGraphClient={_msGraphClient}
themeVariant={props.themeVariant} themeVariant={props.themeVariant}
></SiteTile> ></SiteTile>
); );
})} })}
</div> </div>
: ) : (
<> <>
<Stack horizontal verticalAlign="center" horizontalAlign="center" tokens={{childrenGap: 20}} styles={{root: {marginTop: 50}}}> <Stack
<FontIcon iconName="Tiles" style={{fontSize: 48}}></FontIcon> horizontal
<Label styles={{root:{fontSize: 26}}}>No Sites Found </Label> verticalAlign="center"
</Stack> horizontalAlign="center"
</> tokens={{ childrenGap: 20 }}
styles={{ root: { marginTop: 50 } }}
} >
<FontIcon
iconName="Tiles"
style={{ fontSize: 48 }}
></FontIcon>
<Label styles={{ root: { fontSize: 26 } }}>
No Sites Found{" "}
</Label>
</Stack>
</>
)
}
{state.totalPages > 1 && ( {state.totalPages > 1 && (
<> <>
@ -296,7 +367,7 @@ export const MySites: React.FunctionComponent<IMySitesProps> = (
color="primary" color="primary"
count={state.totalPages} count={state.totalPages}
page={state.currentPage} page={state.currentPage}
onChange={async (event:any, page:number) => { onChange={async (event: any, page: number) => {
const rs = await _searchResults.getPage(page); const rs = await _searchResults.getPage(page);
_searchResults = rs; _searchResults = rs;
setState({ setState({

View File

@ -94,10 +94,9 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
fontSize: 20, fontSize: 20,
color: props.themeVariant ? props.themeVariant.palette.themePrimary: 'white', color: props.themeVariant ? props.themeVariant.palette.themePrimary: 'white',
marginTop: 8, marginTop: 8,
marginRight: 16, marginRight: 7,
}, },
}; };
const DocumentCardActivityStyles: Partial<IDocumentCardActivityStyles> = { const DocumentCardActivityStyles: Partial<IDocumentCardActivityStyles> = {
root: { paddingBottom: 0 }, root: { paddingBottom: 0 },
}; };
@ -107,7 +106,6 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
}; };
let _activityUserEmail: string = "N/A"; let _activityUserEmail: string = "N/A";
let _activityUser: string = "N/A"; let _activityUser: string = "N/A";
let _activityDate: string = "N/A"; let _activityDate: string = "N/A";
@ -124,6 +122,8 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
OriginalPath, OriginalPath,
CreatedBy, CreatedBy,
Created, Created,
IsHubSite,
WebTemplate
} = props.site; } = props.site;
@ -208,7 +208,7 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
previewImages={[ previewImages={[
{ {
previewImageSrc: previewImageSrc:
SiteGroup == "OneDrive" ? _siteLogoOndrive : _siteLogoSP, WebTemplate == "SPSPERS" ? _siteLogoOndrive : _siteLogoSP,
width: 68, width: 68,
height: 68, height: 68,
imageFit: ImageFit.cover, imageFit: ImageFit.cover,
@ -229,15 +229,22 @@ export const SiteTile: React.FunctionComponent<ISiteTileProps> = (
/> />
)} )}
{GroupId && (
{GroupId && GroupId !== "00000000-0000-0000-0000-000000000000" && ( // (is groupId = undefined or 000000-0000-0000-0000000000000 guid) this is showned is some personal drives
<Icon <Icon
styles={groupIconStyles} styles={groupIconStyles}
iconName="Group" iconName="Group"
title="Office 365 Group" title="Office 365 Group"
></Icon> ></Icon>
)} )}
{IsHubSite == "true" && (
{SiteGroup == "OneDrive" && ( <Icon
styles={groupIconStyles}
iconName="DrillExpand"
title="is Hub Site"
></Icon>
)}
{WebTemplate == "SPSPERS" && (
<Icon <Icon
styles={groupIconStyles} styles={groupIconStyles}
iconName="onedrive" iconName="onedrive"

View File

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

View File

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

32
samples/react-upgrade-me/.gitignore vendored Normal file
View File

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

View File

@ -0,0 +1,8 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.4.0",
"libraryName": "react-upgrade-me",
"libraryId": "c32676e6-9536-4db1-b59f-62c7be23f38b",
"environment": "onprem"
}
}

View File

@ -0,0 +1,50 @@
# Upgrade Me
## Summary
This web part does nothing, really. It is intended to be used to test upgrading SPFx web parts and demonstrating incompatibility issues between versions of SPFx and Node.js.
![picture of the web part in action](./assets/react-upgrade-me.png)
## Used SharePoint Framework Version
![1.4.1](https://img.shields.io/badge/version-1.4.1-green.svg)
## Applies to
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
## Prerequisites
You'll need the [CLI for Microsoft 365](https://pnp.github.io/cli-microsoft365/) in order to perform an upgrade.
## Solution
Solution|Author(s)
--------|---------
react-upgrade-me | Hugo Bernier ([Tahoe Ninjas](https://tahoeninjas.blog), [@bernierh](https://twitter.com/bernierh))
## Version history
Version|Date|Comments
-------|----|--------
1.0|August 26, 2020|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
Not applicable.
## Features
This web part really does nothing. It is intended to be used with the CLI for Microsoft 365 [SPFx Project Upgrade](https://pnp.github.io/cli-microsoft365/cmd/spfx/project/project-upgrade/).
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-upgrade-me" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -0,0 +1,18 @@
{
"version": "2.0",
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
"externals": {},
"localizedResources": {
"UpgradeMeWebPartStrings": "lib/webparts/upgradeMe/loc/{locale}.js"
},
"bundles": {
"upgrade-me-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/upgradeMe/UpgradeMeWebPart.js",
"manifest": "./src/webparts/upgradeMe/UpgradeMeWebPart.manifest.json"
}
]
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,12 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-upgrade-me-client-side-solution",
"id": "c32676e6-9536-4db1-b59f-62c7be23f38b",
"version": "1.0.0.0",
"includeClientSideAssets": true
},
"paths": {
"zippedPackage": "solution/react-upgrade-me.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://dev.office.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,45 @@
{
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-case": true,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"valid-typeof": true,
"variable-name": false,
"whitespace": false
}
}
}

View File

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

7
samples/react-upgrade-me/gulpfile.js vendored Normal file
View File

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

22569
samples/react-upgrade-me/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
{
"name": "react-upgrade-me",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.4.1",
"@microsoft/sp-lodash-subset": "1.4.1",
"@microsoft/sp-office-ui-fabric-core": "1.4.1",
"@microsoft/sp-webpart-base": "1.4.1",
"@types/react": "15.6.6",
"@types/react-addons-shallow-compare": "0.14.17",
"@types/react-dom": "15.5.6",
"@types/webpack-env": ">=1.12.1 <1.14.0",
"react": "15.6.2",
"react-dom": "15.4.2"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.4.1",
"@microsoft/sp-module-interfaces": "1.4.1",
"@microsoft/sp-webpart-workbench": "~1.1.0",
"@types/chai": ">=3.4.34 <3.6.0",
"@types/mocha": ">=2.2.33 <2.6.0",
"ajv": "5.2.2",
"gulp": "~3.9.1"
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,57 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import * as strings from 'UpgradeMeWebPartStrings';
import UpgradeMe from './components/UpgradeMe';
import { IUpgradeMeProps } from './components/IUpgradeMeProps';
export interface IUpgradeMeWebPartProps {
description: string;
}
export default class UpgradeMeWebPart extends BaseClientSideWebPart<IUpgradeMeWebPartProps> {
public render(): void {
const element: React.ReactElement<IUpgradeMeProps > = React.createElement(
UpgradeMe,
{
description: this.properties.description
}
);
ReactDom.render(element, this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,3 @@
export interface IUpgradeMeProps {
description: string;
}

View File

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

View File

@ -0,0 +1,22 @@
import * as React from 'react';
import styles from './UpgradeMe.module.scss';
import { IUpgradeMeProps } from './IUpgradeMeProps';
import { escape } from '@microsoft/sp-lodash-subset';
export default class UpgradeMe extends React.Component<IUpgradeMeProps, {}> {
public render(): React.ReactElement<IUpgradeMeProps> {
return (
<div className={ styles.upgradeMe }>
<div className={ styles.container }>
<div className={ styles.row }>
<div className={ styles.column }>
<span className={ styles.title }>Upgrade Me!</span>
<p className={ styles.subTitle }>This web part does nothing. It is intended to be used to demonstrate how to upgrade SPFx solutions and to demonstrate incompatibilities between versions of SPFx/Node.js</p>
<p className={ styles.description }>{escape(this.props.description)}</p>
</div>
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "You should use this web part to test upgrading solutions.",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

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

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
}
}

View File

@ -0,0 +1,30 @@
/**
* @file index.ts
* Type definitions for Microsoft SPFx projects
*/
/**
* Global definition for UNIT_TEST builds.
* Code that is wrapped inside an if (UNIT_TEST) {...}
* block will not be included in the final bundle when the
* --ship flag is specified.
*/
declare const UNIT_TEST: boolean;
/**
* @internal
* Global definition for NPM package builds
*/
declare const NPM_BUILD: boolean;
/**
* @internal
* Global defintion for SharePoint Online builds
*/
declare const DATACENTER: boolean;
/**
* @internal
* Global definition for BUILD_NUMBER
*/
declare const BUILD_NUMBER: string;

View File

@ -0,0 +1 @@
/// <reference path="@ms/odsp.d.ts" />

View File

@ -2,7 +2,7 @@
"@microsoft/generator-sharepoint": { "@microsoft/generator-sharepoint": {
"isCreatingSolution": true, "isCreatingSolution": true,
"environment": "spo", "environment": "spo",
"version": "1.10.0", "version": "1.11.0",
"libraryName": "react-visio", "libraryName": "react-visio",
"libraryId": "0ad401f1-b158-4626-8e71-cf27b1f4848d", "libraryId": "0ad401f1-b158-4626-8e71-cf27b1f4848d",
"packageManager": "npm", "packageManager": "npm",

View File

@ -8,7 +8,7 @@ This sample shows how the [Visio JavaScript APIs](https://docs.microsoft.com/off
## Used SharePoint Framework Version ## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/drop-1.10.0-green.svg) ![SPFx 1.11.0](https://img.shields.io/badge/drop-1.11.0-green.svg)
## Applies to ## Applies to
@ -30,6 +30,7 @@ This sample shows how the [Visio JavaScript APIs](https://docs.microsoft.com/off
| Version | Date | Comments | | Version | Date | Comments |
| ------- | ------------------ | -------------------- | | ------- | ------------------ | -------------------- |
| 1.6 | August 25, 2020 | Update to SPFx 1.11.0 |
| 1.5 | February 20, 2019 | Update to SPFx 1.10.0 | | 1.5 | February 20, 2019 | Update to SPFx 1.10.0 |
| 1.4 | September 20, 2019 | Update to SPFx 1.9.1 | | 1.4 | September 20, 2019 | Update to SPFx 1.9.1 |
| 1.3 | April 4, 2019 | Update readme | | 1.3 | April 4, 2019 | Update readme |

View File

@ -3,10 +3,17 @@
"solution": { "solution": {
"name": "react-visio-client-side-solution", "name": "react-visio-client-side-solution",
"id": "0ad401f1-b158-4626-8e71-cf27b1f4848d", "id": "0ad401f1-b158-4626-8e71-cf27b1f4848d",
"version": "1.0.0.0", "version": "1.6.0.0",
"includeClientSideAssets": true, "includeClientSideAssets": true,
"skipFeatureDeployment": true, "skipFeatureDeployment": true,
"isDomainIsolated": false "isDomainIsolated": false,
"developer": {
"name": "Contoso",
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
}
}, },
"paths": { "paths": {
"zippedPackage": "solution/react-visio.sppkg" "zippedPackage": "solution/react-visio.sppkg"

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "react-visio", "name": "react-visio",
"version": "0.0.1", "version": "1.6.0",
"private": true, "private": true,
"main": "lib/index.js", "main": "lib/index.js",
"engines": { "engines": {
@ -12,29 +12,29 @@
"test": "gulp test" "test": "gulp test"
}, },
"dependencies": { "dependencies": {
"@microsoft/sp-core-library": "1.10.0", "@microsoft/sp-core-library": "1.11.0",
"@microsoft/sp-lodash-subset": "1.10.0", "@microsoft/sp-lodash-subset": "1.11.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0", "@microsoft/sp-office-ui-fabric-core": "1.11.0",
"@microsoft/sp-property-pane": "1.10.0", "@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.10.0", "@microsoft/sp-webpart-base": "1.11.0",
"@types/es6-promise": "0.0.33",
"@types/office-js": "0.0.101", "@types/office-js": "0.0.101",
"@types/react": "16.8.8", "office-ui-fabric-react": "6.214.0",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5", "react": "16.8.5",
"react-dom": "16.8.5" "react-dom": "16.8.5"
}, },
"devDependencies": { "devDependencies": {
"@microsoft/rush-stack-compiler-2.9": "0.7.16", "@microsoft/rush-stack-compiler-2.9": "0.7.16",
"@microsoft/rush-stack-compiler-3.3": "0.3.5", "@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.10.0", "@microsoft/sp-build-web": "1.11.0",
"@microsoft/sp-module-interfaces": "1.10.0", "@microsoft/sp-module-interfaces": "1.11.0",
"@microsoft/sp-tslint-rules": "1.10.0", "@microsoft/sp-tslint-rules": "1.11.0",
"@microsoft/sp-webpart-workbench": "1.10.0", "@microsoft/sp-webpart-workbench": "1.11.0",
"@types/chai": "3.4.34", "@types/chai": "3.4.34",
"@types/es6-promise": "0.0.33",
"@types/mocha": "2.2.38", "@types/mocha": "2.2.38",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"ajv": "~5.2.2", "ajv": "~5.2.2",
"gulp": "~3.9.1" "gulp": "~3.9.1"
}, },

View File

@ -30,7 +30,8 @@
] ]
}, },
"include": [ "include": [
"src/**/*.ts" "src/**/*.ts",
"src/**/*.tsx"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",

View File

@ -1,202 +0,0 @@
# Upgrade project C:\Users\joelf\Dev\GitHub\sp-dev-fx-webparts\samples\react-visio to v1.10.0
Date: 2/29/2020
## Findings
Following is the list of steps required to upgrade your project to SharePoint Framework version 1.10.0. [Summary](#Summary) of the modifications is included at the end of the report.
### FN001001 @microsoft/sp-core-library | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-core-library
Execute the following command:
```sh
npm i -SE @microsoft/sp-core-library@1.10.0
```
File: [./package.json](./package.json)
### FN001002 @microsoft/sp-lodash-subset | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-lodash-subset
Execute the following command:
```sh
npm i -SE @microsoft/sp-lodash-subset@1.10.0
```
File: [./package.json](./package.json)
### FN001003 @microsoft/sp-office-ui-fabric-core | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-office-ui-fabric-core
Execute the following command:
```sh
npm i -SE @microsoft/sp-office-ui-fabric-core@1.10.0
```
File: [./package.json](./package.json)
### FN001004 @microsoft/sp-webpart-base | Required
Upgrade SharePoint Framework dependency package @microsoft/sp-webpart-base
Execute the following command:
```sh
npm i -SE @microsoft/sp-webpart-base@1.10.0
```
File: [./package.json](./package.json)
### FN001021 @microsoft/sp-property-pane | Required
Install SharePoint Framework dependency package @microsoft/sp-property-pane
Execute the following command:
```sh
npm i -SE @microsoft/sp-property-pane@1.10.0
```
File: [./package.json](./package.json)
### FN002001 @microsoft/sp-build-web | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-build-web
Execute the following command:
```sh
npm i -DE @microsoft/sp-build-web@1.10.0
```
File: [./package.json](./package.json)
### FN002002 @microsoft/sp-module-interfaces | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-module-interfaces
Execute the following command:
```sh
npm i -DE @microsoft/sp-module-interfaces@1.10.0
```
File: [./package.json](./package.json)
### FN002003 @microsoft/sp-webpart-workbench | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-webpart-workbench
Execute the following command:
```sh
npm i -DE @microsoft/sp-webpart-workbench@1.10.0
```
File: [./package.json](./package.json)
### FN002009 @microsoft/sp-tslint-rules | Required
Upgrade SharePoint Framework dev dependency package @microsoft/sp-tslint-rules
Execute the following command:
```sh
npm i -DE @microsoft/sp-tslint-rules@1.10.0
```
File: [./package.json](./package.json)
### FN002012 @microsoft/rush-stack-compiler-3.3 | Required
Install SharePoint Framework dev dependency package @microsoft/rush-stack-compiler-3.3
Execute the following command:
```sh
npm i -DE @microsoft/rush-stack-compiler-3.3@0.3.5
```
File: [./package.json](./package.json)
### FN010001 .yo-rc.json version | Recommended
Update version in .yo-rc.json
In file [./.yo-rc.json](./.yo-rc.json) update the code as follows:
```json
{
"@microsoft/generator-sharepoint": {
"version": "1.10.0"
}
}
```
File: [./.yo-rc.json](./.yo-rc.json)
### FN012017 tsconfig.json extends property | Required
Update tsconfig.json extends property
In file [./tsconfig.json](./tsconfig.json) update the code as follows:
```json
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json"
}
```
File: [./tsconfig.json](./tsconfig.json)
### FN017001 Run npm dedupe | Optional
If, after upgrading npm packages, when building the project you have errors similar to: "error TS2345: Argument of type 'SPHttpClientConfiguration' is not assignable to parameter of type 'SPHttpClientConfiguration'", try running 'npm dedupe' to cleanup npm packages.
Execute the following command:
```sh
npm dedupe
```
File: [./package.json](./package.json)
## Summary
### Execute script
```sh
npm i -SE @microsoft/sp-core-library@1.10.0 @microsoft/sp-lodash-subset@1.10.0 @microsoft/sp-office-ui-fabric-core@1.10.0 @microsoft/sp-webpart-base@1.10.0 @microsoft/sp-property-pane@1.10.0
npm i -DE @microsoft/sp-build-web@1.10.0 @microsoft/sp-module-interfaces@1.10.0 @microsoft/sp-webpart-workbench@1.10.0 @microsoft/sp-tslint-rules@1.10.0 @microsoft/rush-stack-compiler-3.3@0.3.5
```
### Modify files
#### [./.yo-rc.json](./.yo-rc.json)
Update version in .yo-rc.json:
```json
{
"@microsoft/generator-sharepoint": {
"version": "1.10.0"
}
}
```
#### [./tsconfig.json](./tsconfig.json)
Update tsconfig.json extends property:
```json
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json"
}
```