Merge pull request #1980 from giuleon/main

This commit is contained in:
Hugo Bernier 2021-08-02 00:37:07 -04:00 committed by GitHub
commit f4a1f0c0b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 22042 additions and 0 deletions

View File

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

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.12.1",
"libraryName": "react-cross-device-data",
"libraryId": "e542e0b9-3afa-4e6f-aeab-8ea701e5eeca",
"environment": "spo",
"packageManager": "npm",
"isCreatingSolution": true,
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,77 @@
# Cross-Device Data
## Summary
This solution demonstrates how to use the OneDrive special folder **Apps** in order to save user's preferencies cross-device.
![Preview](./assets/Preview.jpg)
## Compatibility
![SPFx 1.12.1](https://img.shields.io/badge/SPFx-1.12.1-green.svg)
![Node.js LTS v14 | LTS v12 | LTS v10](https://img.shields.io/badge/Node.js-LTS%20v14%20%7C%20LTS%20v12%20%7C%20LTS%20v10-green.svg)
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
![Workbench Local | Hosted](https://img.shields.io/badge/Workbench-Local%20%7C%20Hosted-green.svg)
## Applies to
- [SharePoint Framework](https://aka.ms/spfx)
- [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram)
## Prerequisites
In order to use this solution is necessary to approve the usage of Microsoft Graph API `Files.ReadWrite` in the SharePoint Admin Center
## Solution
Solution|Author(s)
--------|---------
react-cross-device-data | [Giuliano De Luca](https://github.com/giuleon) ([@delucagiulian](https://twitter.com/delucagiulian))
## Version history
Version|Date|Comments
-------|----|--------
1.0|July 26, 2021|Initial release
## Minimal Path to Awesome
- Clone this repository
- Ensure that you are at the solution folder
- in the command-line run:
- **npm install**
- **gulp serve**
## Features
This web part illustrates the following concepts:
- How to store user data in the OneDrive special folder
- How to leverage the capabilities of Microsoft Graph API
## References
- [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
- [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview)
- [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis)
- [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview)
- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development
## 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.**
## Help
We do not support samples, but we this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.
If you encounter any issues while using this sample, [create a new issue](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=bug-report.yml&sample=react-cross-device-data&authors=@giuleon&title=react-cross-device-data%20-%20).
For questions regarding this sample, [create a new question](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=question.yml&sample=react-cross-device-data&authors=@giuleon&title=react-cross-device-data%20-%20).
Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=suggestion.yml&sample=react-cross-device-data&authors=@giuleon&title=react-cross-device-data%20-%20).
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-cross-device-data" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

View File

@ -0,0 +1,50 @@
[
{
"name": "pnp-sp-dev-spfx-web-parts-react-cross-device-data",
"source": "pnp",
"title": "Cross-Device Data",
"shortDescription": "This solution demonstrates how to use the OneDrive special folder Apps in order to save user's preferencies cross-device.",
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-cross-device-data",
"longDescription": [
"This solution demonstrates how to use the OneDrive special folder Apps in order to save user's preferencies cross-device."
],
"creationDateTime": "2021-07-26",
"updateDateTime": "2021-07-26",
"products": [
"SharePoint",
"Office"
],
"metadata": [
{
"key": "CLIENT-SIDE-DEV",
"value": "React"
},
{
"key": "SPFX-VERSION",
"value": "1.12.1"
}
],
"thumbnails": [
{
"type": "image",
"order": 100,
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-cross-device-data/assets/Preview.jpg",
"alt": "Preview"
}
],
"authors": [
{
"gitHubAccount": "giuleon",
"pictureUrl": "https://github.com/giuleon.png",
"name": "Giuliano De Luca"
}
],
"references": [
{
"name": "Build your first SharePoint client-side web part",
"description": "Client-side web parts are client-side components that run in the context of a SharePoint page. Client-side web parts can be deployed to SharePoint environments that support the SharePoint Framework. You can also use modern JavaScript web frameworks, tools, and libraries to build them.",
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part"
}
]
}
]

View File

@ -0,0 +1,18 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"hello-world-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/helloWorld/HelloWorldWebPart.js",
"manifest": "./src/webparts/helloWorld/HelloWorldWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"HelloWorldWebPartStrings": "lib/webparts/helloWorld/loc/{locale}.js"
}
}

View File

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

View File

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

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-cross-device-data-client-side-solution",
"id": "e542e0b9-3afa-4e6f-aeab-8ea701e5eeca",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false,
"developer": {
"name": "",
"websiteUrl": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"mpnId": ""
},
"webApiPermissionRequests": [
{
"resource": "Microsoft Graph",
"scope": "Files.ReadWrite"
}
]
},
"paths": {
"zippedPackage": "solution/react-cross-device-data.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,16 @@
'use strict';
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.`);
var getTasks = build.rig.getTasks;
build.rig.getTasks = function () {
var result = getTasks.call(build.rig);
result.set('serve', result.get('serve-deprecated'));
return result;
};
build.initialize(require('gulp'));

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
{
"name": "react-cross-device-data",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/microsoft-graph-types": "^1.41.0",
"@microsoft/sp-core-library": "1.12.1",
"@microsoft/sp-lodash-subset": "1.12.1",
"@microsoft/sp-office-ui-fabric-core": "1.12.1",
"@microsoft/sp-property-pane": "1.12.1",
"@microsoft/sp-webpart-base": "1.12.1",
"office-ui-fabric-react": "7.156.0",
"react": "16.9.0",
"react-dom": "16.9.0"
},
"devDependencies": {
"@types/react": "16.9.36",
"@types/react-dom": "16.9.8",
"@microsoft/sp-build-web": "1.12.1",
"@microsoft/sp-tslint-rules": "1.12.1",
"@microsoft/sp-module-interfaces": "1.12.1",
"@microsoft/sp-webpart-workbench": "1.12.1",
"@microsoft/rush-stack-compiler-3.7": "0.2.3",
"gulp": "~4.0.2",
"ajv": "~5.2.2",
"@types/webpack-env": "1.13.1"
}
}

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": "a8aaf7ce-6775-479d-be91-5d7c14128001",
"alias": "CrossDeviceApp",
"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","TeamsPersonalApp"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "Cross Device User Data" },
"description": { "default": "HelloWorld description" },
"officeFabricIconFontName": "Devices2",
"properties": {
"description": "HelloWorld"
}
}]
}

View File

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

View File

@ -0,0 +1,92 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.helloWorld {
.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);
}
.rowLight {
@include ms-Grid-row;
@include ms-fontColor-themeDark;
background-color: $ms-color-themeLight;
padding: 20px;
}
.rowDark {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
.textFieldcssStyledLight {
label {
color: $ms-color-themeDark;
}
}
.textFieldcssStyledDark {
label {
color: $ms-color-themeLight;
}
}
}

View File

@ -0,0 +1,142 @@
import * as React from 'react';
import styles from './HelloWorld.module.scss';
import { IHelloWorldProps } from './IHelloWorldProps';
import { escape } from '@microsoft/sp-lodash-subset';
import {
PrimaryButton,
TextField,
Toggle,
Spinner,
SpinnerSize
} from 'office-ui-fabric-react';
import { MSGraphClient } from '@microsoft/sp-http';
interface IUserData {
Theme: string;
Token: string;
Preference1: string;
}
let _Theme: string = '';
let _Token: string = '';
let _Preference1: string = '';
const Dashboard: React.FunctionComponent<IHelloWorldProps> = (props) => {
const [userSettings, setUserSettings] = React.useState<IUserData | null>(null);
const [currentTheme, setTheme] = React.useState<string>(null);
const [isLoading, setIsLoading] = React.useState<boolean>(true);
const setUserData = async () => {
props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient) => {
client
.api("me/drive/special/approot:/CrossDeviceApp/settings.json:/content")
.version("v1.0")
.put(`{"Theme": "${_Theme}", "Token": "${_Token}", "Preference1": "${_Preference1}"}`, (err, res) => {
console.log(err, res);
});
});
};
const getUserData = async () => {
try {
const msGraphClient = await props.context.msGraphClientFactory.getClient();
const result = await msGraphClient
.api("me/drive/special/approot:/CrossDeviceApp/settings.json?select=@microsoft.graph.downloadUrl")
.version("v1.0")
.get();
console.log(result);
const response = await fetch(`${result['@microsoft.graph.downloadUrl']}`);
console.log('response', response);
const userData: IUserData = await response.json();
_Theme = userData.Theme;
_Token = userData.Token;
_Preference1 = userData.Preference1;
console.log('userData', userData);
return userData;
} catch (error) {
const userData: IUserData = JSON.parse(`{"Theme": "", "Token": "", "Preference1": ""}`);
return userData;
}
};
const loadUserData = async () => {
return await getUserData();
};
const onChangeTheme = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
if (checked) {
_Theme = 'Dark';
} else {
_Theme = 'Light';
}
setTheme(_Theme);
};
const onChangeToken = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newText: string): void => {
_Token = newText;
};
const onChangePreference1 = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newText: string): void => {
_Preference1 = newText;
};
React.useEffect(() => {
console.log('useEffect');
(async () => {
let data = await loadUserData();
console.log('loadUserData', data);
setUserSettings(data);
setIsLoading(false);
})();
}, []);
let view: any = <Spinner size={SpinnerSize.large} />;
if (isLoading !== true) {
let theme: any =
userSettings.Theme == 'Dark'
? <Toggle label="Which Theme do you prefer?" defaultChecked onText="Dark" offText="Light" onChange={onChangeTheme} className={(_Theme === 'Dark') ? styles.textFieldcssStyledDark : styles.textFieldcssStyledLight} />
: <Toggle label="Which Theme do you prefer?" onText="Dark" offText="Light" onChange={onChangeTheme} className={(_Theme === 'Dark') ? styles.textFieldcssStyledDark : styles.textFieldcssStyledLight} />;
if (userSettings !== null) {
view =
<div>
<div className={(_Theme === 'Dark') ? styles.rowDark : styles.rowLight}>
<div className={styles.column}>
{theme}
{/* <TextField label="Theme:" underlined placeholder="Enter text here" defaultValue={userSettings.Theme} onChange={onChangeTheme} className={styles.textFieldcssStyled} /> */}
</div>
</div>
<div className={(_Theme === 'Dark') ? styles.rowDark : styles.rowLight}>
<div className={styles.column}>
<TextField label="Token:" underlined placeholder="Enter text here" defaultValue={userSettings.Token} onChange={onChangeToken} className={(_Theme === 'Dark') ? styles.textFieldcssStyledDark : styles.textFieldcssStyledLight} />
</div>
</div>
<div className={(_Theme === 'Dark') ? styles.rowDark : styles.rowLight}>
<div className={styles.column}>
<TextField label="Preference1:" underlined placeholder="Enter text here" defaultValue={userSettings.Preference1} onChange={onChangePreference1} className={(_Theme === 'Dark') ? styles.textFieldcssStyledDark : styles.textFieldcssStyledLight} />
</div>
</div>
<div className={(_Theme === 'Dark') ? styles.rowDark : styles.rowLight}>
<div className={styles.column}>
<PrimaryButton text='Save current user data' onClick={setUserData} ></PrimaryButton>
</div>
</div>
<div className={(_Theme === 'Dark') ? styles.rowDark : styles.rowLight}>
<div className={styles.column}>
<PrimaryButton text='Get current user data' onClick={getUserData} ></PrimaryButton>
</div>
</div>
</div>;
}
}
return (
<div className={styles.helloWorld}>
<div className={styles.container}>
{view}
</div>
</div>
);
};
export default Dashboard;

View File

@ -0,0 +1,6 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
export interface IHelloWorldProps {
description: string;
context: WebPartContext;
}

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

View File

@ -0,0 +1,35 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.7/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": [
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection",
"es2015.promise"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "./node_modules/@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
}
}