|
@ -2,7 +2,7 @@
|
|||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.7.1",
|
||||
"version": "1.9.1",
|
||||
"libraryName": "react-form-webpart",
|
||||
"libraryId": "b092661d-5730-49ea-be27-14ee4a84eb33",
|
||||
"packageManager": "npm",
|
||||
|
|
|
@ -8,7 +8,7 @@ The web part allows configuring which list to use and if a form for adding a new
|
|||
![Demo](./assets/React-ListForm-Overview.gif)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/version-1.7.1-green.svg)
|
||||
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
|
@ -27,6 +27,7 @@ Version|Date|Comments
|
|||
-------|----|--------
|
||||
1.0.0|November 24, 2017|Initial release
|
||||
1.0.1|February 22, 2019|Updated to SPFx 1.7.1 and dependencies, Added Turkish translation, Added RichText Mode and Tinymce Editor
|
||||
1.0.2|October 14, 2019|Updated to SPFx 1.9.1 and dependencies
|
||||
|
||||
## 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.**
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"solution": {
|
||||
"name": "react-form-webpart-client-side-solution",
|
||||
"id": "373a20ef-dfc6-456a-95ec-171de3c94581",
|
||||
"version": "1.0.1.0",
|
||||
"version": "1.0.2.0",
|
||||
"title": "List form",
|
||||
"supportedLocales": [
|
||||
"en-US",
|
||||
|
@ -16,4 +16,4 @@
|
|||
"paths": {
|
||||
"zippedPackage": "solution/react-form-webpart.sppkg"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"name": "react-form-webpart",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"serve": "gulp serve",
|
||||
"build": "gulp bundle",
|
||||
|
@ -12,10 +13,10 @@
|
|||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.7.1",
|
||||
"@microsoft/sp-lodash-subset": "1.7.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.7.1",
|
||||
"@microsoft/sp-webpart-base": "1.7.1",
|
||||
"@microsoft/sp-core-library": "1.9.1",
|
||||
"@microsoft/sp-lodash-subset": "1.9.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
|
||||
"@microsoft/sp-webpart-base": "1.9.1",
|
||||
"@tinymce/tinymce-react": "^3.0.1",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react-dnd": "~2.0.34",
|
||||
|
@ -28,14 +29,15 @@
|
|||
"tinymce": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.7.1",
|
||||
"@microsoft/sp-tslint-rules": "1.7.1",
|
||||
"@microsoft/sp-module-interfaces": "1.7.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.7.1",
|
||||
"@microsoft/rush-stack-compiler-2.9": "^0.8.5",
|
||||
"@microsoft/sp-build-web": "1.9.1",
|
||||
"@microsoft/sp-module-interfaces": "1.9.1",
|
||||
"@microsoft/sp-tslint-rules": "1.9.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1",
|
||||
"tslint-microsoft-contrib": "5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
/* tslint:disable */
|
||||
require('./ConfigureWebPart.module.css');
|
||||
require("./ConfigureWebPart.module.css");
|
||||
const styles = {
|
||||
container: 'container_0bf126c2',
|
||||
title: 'title_0bf126c2',
|
||||
description: 'description_0bf126c2',
|
||||
button: 'button_0bf126c2',
|
||||
container: 'container_f7c71bef',
|
||||
title: 'title_f7c71bef',
|
||||
description: 'description_f7c71bef',
|
||||
button: 'button_f7c71bef'
|
||||
};
|
||||
|
||||
export default styles;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { IPropertyPaneCustomFieldProps } from '@microsoft/sp-webpart-base';
|
||||
|
||||
import { IPropertyPaneCustomFieldProps } from "@microsoft/sp-property-pane";
|
||||
import { IPropertyPaneAsyncDropdownProps } from './IPropertyPaneAsyncDropdownProps';
|
||||
|
||||
export interface IPropertyPaneAsyncDropdownInternalProps extends IPropertyPaneAsyncDropdownProps, IPropertyPaneCustomFieldProps { }
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import {
|
||||
IPropertyPaneField,
|
||||
PropertyPaneFieldType
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
import { IPropertyPaneField, PropertyPaneFieldType } from "@microsoft/sp-property-pane";
|
||||
|
||||
import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
|
||||
import { IPropertyPaneAsyncDropdownProps } from './IPropertyPaneAsyncDropdownProps';
|
||||
import { IPropertyPaneAsyncDropdownInternalProps } from './IPropertyPaneAsyncDropdownInternalProps';
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ControlMode } from '../../common/datatypes/ControlMode';
|
||||
import { IFieldConfiguration } from './components/IFieldConfiguration';
|
||||
|
||||
|
||||
export interface IListFormWebPartProps {
|
||||
title: string;
|
||||
description: string;
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
// 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": "48e2d130-7eb7-4ee9-aa23-5ddbdfd175b1",
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { DisplayMode, Environment, EnvironmentType, Version } from '@microsoft/sp-core-library';
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
PropertyPaneDropdown,
|
||||
PropertyPaneToggle
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
IPropertyPaneConfiguration, PropertyPaneDropdown,
|
||||
PropertyPaneTextField, PropertyPaneToggle, IPropertyPaneField
|
||||
} from "@microsoft/sp-property-pane";
|
||||
|
||||
import * as strings from 'ListFormWebPartStrings';
|
||||
import ListForm from './components/ListForm';
|
||||
|
@ -137,7 +136,7 @@ export default class ListFormWebPart extends BaseClientSideWebPart<IListFormWebP
|
|||
PropertyPaneToggle('showUnsupportedFields', {
|
||||
label: strings.ShowUnsupportedFieldsLabel,
|
||||
disabled: !this.properties.listUrl
|
||||
})
|
||||
}) as IPropertyPaneField<any> // for some reasong the PropertyPaneToggle was not being accepted as IPropertyPaneField<any>
|
||||
);
|
||||
mainGroup.groupFields.push(
|
||||
PropertyPaneTextField('redirectUrl', {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/* tslint:disable */
|
||||
require('./DraggableComponent.module.css');
|
||||
require("./DraggableComponent.module.css");
|
||||
const styles = {
|
||||
draggableComponent: 'draggableComponent_983b2b46',
|
||||
isDragging: 'isDragging_983b2b46',
|
||||
toolbar: 'toolbar_983b2b46',
|
||||
button: 'button_983b2b46',
|
||||
draggableComponent: 'draggableComponent_98a40df4',
|
||||
isDragging: 'isDragging_98a40df4',
|
||||
toolbar: 'toolbar_98a40df4',
|
||||
button: 'button_98a40df4'
|
||||
};
|
||||
|
||||
export default styles;
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
/* tslint:disable */
|
||||
require('./ListForm.module.css');
|
||||
require("./ListForm.module.css");
|
||||
const styles = {
|
||||
listForm: 'listForm_7906cbc9',
|
||||
title: 'title_7906cbc9',
|
||||
description: 'description_7906cbc9',
|
||||
formFieldsContainer: 'formFieldsContainer_7906cbc9',
|
||||
isDataLoading: 'isDataLoading_7906cbc9',
|
||||
formButtonsContainer: 'formButtonsContainer_7906cbc9',
|
||||
addFieldToolbox: 'addFieldToolbox_7906cbc9',
|
||||
addFieldToolboxPlusButton: 'addFieldToolboxPlusButton_7906cbc9',
|
||||
listForm: 'listForm_e2d8b707',
|
||||
title: 'title_e2d8b707',
|
||||
description: 'description_e2d8b707',
|
||||
formFieldsContainer: 'formFieldsContainer_e2d8b707',
|
||||
isDataLoading: 'isDataLoading_e2d8b707',
|
||||
formButtonsContainer: 'formButtonsContainer_e2d8b707',
|
||||
addFieldToolbox: 'addFieldToolbox_e2d8b707',
|
||||
addFieldToolboxPlusButton: 'addFieldToolboxPlusButton_e2d8b707'
|
||||
};
|
||||
|
||||
export default styles;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* tslint:disable */
|
||||
require('./FormField.module.css');
|
||||
require("./FormField.module.css");
|
||||
const styles = {
|
||||
formField: 'formField_d444b146',
|
||||
label: 'label_d444b146',
|
||||
controlContainerDisplay: 'controlContainerDisplay_d444b146',
|
||||
formField: 'formField_3f2aedbe',
|
||||
label: 'label_3f2aedbe',
|
||||
controlContainerDisplay: 'controlContainerDisplay_3f2aedbe'
|
||||
};
|
||||
|
||||
export default styles;
|
||||
|
|
|
@ -10,7 +10,7 @@ import { AnimationClassNames } from '@uifabric/styling';
|
|||
import { ControlMode } from '../../../../common/datatypes/ControlMode';
|
||||
import { IFieldSchema } from '../../../../common/services/datatypes/RenderListData';
|
||||
|
||||
import * as stylesImport from 'office-ui-fabric-react/lib/components/TextField/TextField.scss';
|
||||
import * as stylesImport from 'office-ui-fabric-react/lib/components/TextField/TextField.types';
|
||||
const styles: any = stylesImport;
|
||||
|
||||
import ardStyles from './FormField.module.scss';
|
||||
|
@ -50,7 +50,7 @@ const FormField: React.SFC<IFormFieldProps> = (props) => {
|
|||
return (
|
||||
<div className={css(formFieldClassName, 'od-ClientFormFields-field')}>
|
||||
<div className={css('ard-FormField-wrapper', styles.wrapper)}>
|
||||
{label && <Label className={css(ardStyles.label, { ['is-required']: required })} htmlFor={this._id}>{label}</Label>}
|
||||
{label && <Label className={css(ardStyles.label, { ['is-required']: required })}>{label}</Label>}
|
||||
<div className={css('ard-FormField-fieldGroup', ardStyles.controlContainerDisplay, active
|
||||
&& styles.fieldGroupIsFocused, errorMessage && styles.invalid)}>
|
||||
{children}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/* tslint:disable */
|
||||
require('./SPFormField.module.css');
|
||||
require("./SPFormField.module.css");
|
||||
const styles = {
|
||||
dropDownFormField: 'dropDownFormField_e5e89a2f',
|
||||
dateFormField: 'dateFormField_e5e89a2f',
|
||||
unsupportedFieldMessage: 'unsupportedFieldMessage_e5e89a2f',
|
||||
dropDownFormField: 'dropDownFormField_2427d638',
|
||||
dateFormField: 'dateFormField_2427d638',
|
||||
unsupportedFieldMessage: 'unsupportedFieldMessage_2427d638'
|
||||
};
|
||||
|
||||
export default styles;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
@ -7,8 +8,11 @@
|
|||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"inlineSources": false,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"outDir": "lib",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.9.1",
|
||||
"libraryName": "react-msgraph-extension",
|
||||
"libraryId": "7ecf1b13-cb5b-451d-bfda-b11fa316d6c1",
|
||||
"environment": "spo",
|
||||
"packageManager": "npm",
|
||||
"isCreatingSolution": true,
|
||||
"isDomainIsolated": true,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
## react-msgraph-extension
|
||||
|
||||
## Summary
|
||||
This sample shows how to managed Microsoft Graph Open Extension in SPFX. This application uses **User** Resource to create Open Extension.
|
||||
|
||||
## ScreenShots
|
||||
|
||||
### Create a new Microsoft Graph Open Extension
|
||||
![Create a new Microsoft Graph Open Extension](./assets/create-graph-extension.png)
|
||||
|
||||
### Get an existing Microsoft Graph Open Extension
|
||||
![Get existing Microsoft Graph Open Extension](./assets/get-graph-extension.png)
|
||||
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
> You need following set of permissions in order to manage Microsoft Open Graph Extension.Find out more about consuming the [Microsoft Graph API in the SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/use-aad-tutorial)<br><br>![Microsoft Graph API Permissions](./assets/graph-extension-user-permissions.png)
|
||||
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-msgraph-extension | Ejaz Hussain
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|October 20, 2019|Initial release
|
||||
|
||||
## Disclaimer
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Clone this repository
|
||||
- in the command line run:
|
||||
- `npm install`
|
||||
- `gulp serve`
|
||||
|
||||
If you have not previously granted the required Microsoft Graph permissions, you need to:
|
||||
|
||||
- Run `gulp bundle --ship`
|
||||
- Run `gulp package-solution --ship`
|
||||
- Install the .sppkg file (under .\sharepoint\solution) to the SP app catalog
|
||||
- Approve the API permissions in the new SP admin center
|
||||
|
||||
## Features
|
||||
Here are main features for this application
|
||||
|
||||
- Create a new Open Graph Extension
|
||||
- Get an existing Graph Open Extension
|
||||
- Update an existing Open Graph Extension
|
||||
- Remove an existing Open Graph Extension
|
||||
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-msgraph-extension" />
|
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"graphextension-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/graphextension/GraphextensionWebPart.js",
|
||||
"manifest": "./src/webparts/graphextension/GraphextensionWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"GraphextensionWebPartStrings": "lib/webparts/graphextension/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-msgraph-extension",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-msgraph-extension-client-side-solution",
|
||||
"id": "7ecf1b13-cb5b-451d-bfda-b11fa316d6c1",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.ReadBasic.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.ReadWrite.All"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-msgraph-extension.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "react-msgraph-extension",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.9.1",
|
||||
"@microsoft/sp-lodash-subset": "1.9.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
|
||||
"@microsoft/sp-webpart-base": "1.9.1",
|
||||
"@pnp/common": "^1.3.6",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "16.8.8",
|
||||
"@types/react-dom": "16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"office-ui-fabric-react": "6.189.2",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5",
|
||||
"react-toastify": "^5.4.0",
|
||||
"semantic-ui-react": "^0.88.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.8.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.9.1",
|
||||
"@microsoft/sp-tslint-rules": "1.9.1",
|
||||
"@microsoft/sp-module-interfaces": "1.9.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.9.1",
|
||||
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export default class Constants {
|
||||
public static readonly ExtensionName = "com.ejazhussain.settings";
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,4 @@
|
|||
export interface IFormSchema {
|
||||
Theme?: string;
|
||||
Tags?: string;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
export default class IPersona {
|
||||
public name: string;
|
||||
public email: string;
|
||||
public phone: string;
|
||||
public photo: string;
|
||||
|
||||
constructor(name: string, email: string, phone: string, photo: string) {
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.phone = phone;
|
||||
this.photo = photo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
import { MSGraphClient } from "@microsoft/sp-http";
|
||||
import IPersona from "./../models/IPersona";
|
||||
import Constants from '../common/constants';
|
||||
|
||||
export class MsGraphService {
|
||||
|
||||
private context = null;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(context: any) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public async PatchExtension(userSettings: any): Promise<any> {
|
||||
|
||||
try {
|
||||
|
||||
let result = await this.PATCH(`/me/extensions/${Constants.ExtensionName}`, JSON.stringify(userSettings));
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Error in PatchExtension:", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async CreateExtension(userSettings: any): Promise<any> {
|
||||
|
||||
try {
|
||||
|
||||
let result = await this.POST(`/me/extensions`, JSON.stringify(userSettings));
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Error in CreateExtension:", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async GetExtension(): Promise<any> {
|
||||
|
||||
try {
|
||||
|
||||
let result = await this.GET(`/me/extensions/${Constants.ExtensionName}`);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Error in GetExtension:", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
public async DeleteExtension(): Promise<any> {
|
||||
|
||||
try {
|
||||
|
||||
let result = await this.DELETE(`/me/extensions/${Constants.ExtensionName}`);
|
||||
return result;
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Error in DeleteExtension:", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async GetUserProfile(): Promise<any> {
|
||||
|
||||
try {
|
||||
|
||||
let userResponse: any = await this.GET("/me");
|
||||
let photoResponse: any = await this.GET("/me/photo/$value", "blob");
|
||||
|
||||
let user = {
|
||||
name: userResponse.displayName,
|
||||
email: userResponse.mail,
|
||||
phone: userResponse.businessPhones[0],
|
||||
photo: window.URL.createObjectURL(photoResponse)
|
||||
} as IPersona;
|
||||
|
||||
return user;
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Error in GetUserProfile:", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private GET(query: string, responseType?: string): Promise<any> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.context.msGraphClientFactory.getClient().then((client: MSGraphClient): void => {
|
||||
client.api(query).responseType(responseType)
|
||||
.get((error, response) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private POST(query: string, content: string) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.context.msGraphClientFactory.getClient().then((client: MSGraphClient): void => {
|
||||
client.api(query)
|
||||
.post(content, (error, response) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
private PATCH(query: string, content: string) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.context.msGraphClientFactory.getClient().then((client: MSGraphClient): void => {
|
||||
client.api(query)
|
||||
.patch(content, (error, response, rawResponse) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
resolve(rawResponse);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
private DELETE(query: string) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.context.msGraphClientFactory.getClient().then((client: MSGraphClient): void => {
|
||||
client.api(query)
|
||||
.delete((error, response, rawResponse) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
resolve(rawResponse);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "8cc62b32-de8c-4600-bdee-325dc55f587f",
|
||||
"alias": "GraphextensionWebPart",
|
||||
"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": "SpfxSolutions" },
|
||||
"title": { "default": "Graph Extension" },
|
||||
"description": { "default": "This solution provide working demo to create, get and update microsoft graph extensions" },
|
||||
"officeFabricIconFontName": "Emoji2",
|
||||
"properties": {
|
||||
"description": "graphextension"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
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 'GraphextensionWebPartStrings';
|
||||
import Graphextension from './components/Graphextension';
|
||||
import { IGraphextensionProps } from './components/IGraphextensionProps';
|
||||
import { SPComponentLoader } from '@microsoft/sp-loader';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
|
||||
export interface IGraphextensionWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class GraphextensionWebPart extends BaseClientSideWebPart<IGraphextensionWebPartProps> {
|
||||
|
||||
protected onInit(): Promise<void> {
|
||||
|
||||
//toast.configure()
|
||||
SPComponentLoader.loadCss("https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css");
|
||||
return super.onInit();
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IGraphextensionProps > = React.createElement(
|
||||
Graphextension,
|
||||
{
|
||||
webpartContext: 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
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.graphextension {
|
||||
.container {
|
||||
width: 100%;
|
||||
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-neutralDark;
|
||||
//background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
|
||||
.column12 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg12;
|
||||
// @include ms-xl8;
|
||||
// @include ms-xlPush2;
|
||||
// @include ms-lgPush1;
|
||||
}
|
||||
.column10 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
}
|
||||
.column8 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg8;
|
||||
}
|
||||
.column6 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg6;
|
||||
}
|
||||
.column4 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg4;
|
||||
}
|
||||
.column2 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg2;
|
||||
}
|
||||
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.button {
|
||||
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
margin:20px 0;
|
||||
min-width: 80px;
|
||||
//background-color: $ms-color-themePrimary;
|
||||
//border-color: $ms-color-themePrimary;
|
||||
//color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
// outline: transparent;
|
||||
// position: relative;
|
||||
// font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||
// -webkit-font-smoothing: antialiased;
|
||||
// font-size: $ms-font-size-m;
|
||||
// font-weight: $ms-font-weight-regular;
|
||||
// border-width: 0;
|
||||
// text-align: center;
|
||||
// cursor: pointer;
|
||||
// display: inline-block;
|
||||
// padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
import * as React from 'react';
|
||||
import styles from './Graphextension.module.scss';
|
||||
import { IGraphextensionProps } from './IGraphextensionProps';
|
||||
import { escape, isEmpty } from '@microsoft/sp-lodash-subset';
|
||||
import { MsGraphService } from '../../../services/MSGraphService';
|
||||
import { Tab, Header } from 'semantic-ui-react';
|
||||
import { TextField, MaskedTextField } from 'office-ui-fabric-react/lib/TextField';
|
||||
import { Stack, IStackProps } from 'office-ui-fabric-react/lib/Stack';
|
||||
import { IFormSchema } from '../../../models/IFormSchema';
|
||||
import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react';
|
||||
import Constants from './../../../common/constants';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import { string } from 'prop-types';
|
||||
require('./ReactToastify.css');
|
||||
import { stringIsNullOrEmpty } from "@pnp/common";
|
||||
|
||||
|
||||
export interface IGraphextensionState {
|
||||
schemaForm?: IFormSchema;
|
||||
onSuccess?: Boolean;
|
||||
onFail?: Boolean;
|
||||
response?: any;
|
||||
}
|
||||
|
||||
|
||||
export default class Graphextension extends React.Component<IGraphextensionProps, IGraphextensionState> {
|
||||
|
||||
|
||||
|
||||
//MS Graph service
|
||||
private graphService: MsGraphService;
|
||||
|
||||
constructor(props: IGraphextensionProps, state: IGraphextensionState) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
schemaForm: {} as IFormSchema
|
||||
};
|
||||
this.onChangeValue = this.onChangeValue.bind(this);
|
||||
this.onCreateExtension = this.onCreateExtension.bind(this);
|
||||
this.onGetExtension = this.onGetExtension.bind(this);
|
||||
this.onPatchExtension = this.onPatchExtension.bind(this);
|
||||
this.onDeleteExtension = this.onDeleteExtension.bind(this);
|
||||
this.onTabChange = this.onTabChange.bind(this);
|
||||
this.graphService = new MsGraphService(this.props.webpartContext);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
private onChangeValue(event: any, newValue?: string) {
|
||||
this.setState({
|
||||
schemaForm: {
|
||||
...this.state.schemaForm,
|
||||
[event.target.name]: newValue
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async onPatchExtension() {
|
||||
|
||||
|
||||
if (this.state.schemaForm != null && !isEmpty(this.state.schemaForm)) {
|
||||
|
||||
|
||||
let result = await this.graphService.GetExtension();
|
||||
|
||||
let userSettings = {
|
||||
"@odata.type": "microsoft.graph.openTypeExtension",
|
||||
"extensionName": Constants.ExtensionName,
|
||||
"Theme": !stringIsNullOrEmpty(this.state.schemaForm.Theme) ? this.state.schemaForm.Theme : result.Theme,
|
||||
"Tags": !stringIsNullOrEmpty(this.state.schemaForm.Tags) ? this.state.schemaForm.Tags : result.Tags
|
||||
};
|
||||
|
||||
//Patch Extesion
|
||||
let response = await this.graphService.PatchExtension(userSettings);
|
||||
if (response != null && response.ok) {
|
||||
toast.success("Graph Extension Successfully Updated!", {
|
||||
position: toast.POSITION.BOTTOM_CENTER
|
||||
});
|
||||
}
|
||||
else {
|
||||
toast.error("Error in patching graph extension !", {
|
||||
position: toast.POSITION.BOTTOM_CENTER
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async onCreateExtension() {
|
||||
|
||||
|
||||
if (this.state.schemaForm != null && !isEmpty(this.state.schemaForm)) {
|
||||
|
||||
|
||||
let userSettings = {
|
||||
"@odata.type": "microsoft.graph.openTypeExtension",
|
||||
"extensionName": Constants.ExtensionName,
|
||||
"Theme": this.state.schemaForm.Theme,
|
||||
"Tags": this.state.schemaForm.Tags
|
||||
};
|
||||
|
||||
//Create Extesion
|
||||
let response = await this.graphService.CreateExtension(userSettings);
|
||||
if (response != null) {
|
||||
toast.success("Graph Extension created !", {
|
||||
position: toast.POSITION.BOTTOM_CENTER
|
||||
});
|
||||
}
|
||||
else {
|
||||
toast.error("Error in creating graph extension !", {
|
||||
position: toast.POSITION.BOTTOM_CENTER
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async onDeleteExtension() {
|
||||
|
||||
//Delete Extesion
|
||||
let response = await this.graphService.DeleteExtension();
|
||||
if (response != null && response.ok) {
|
||||
|
||||
toast.success("Graph extension removed !", {
|
||||
position: toast.POSITION.BOTTOM_CENTER
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
toast.error("Error in removing graph extension !", {
|
||||
position: toast.POSITION.BOTTOM_CENTER
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async onGetExtension() {
|
||||
|
||||
//Get Extesion
|
||||
let response = await this.graphService.GetExtension();
|
||||
if (response != null) {
|
||||
|
||||
this.setState({
|
||||
response
|
||||
});
|
||||
toast.success("Graph Extension retrieved !", {
|
||||
position: toast.POSITION.BOTTOM_CENTER
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.setState({
|
||||
response: null
|
||||
});
|
||||
toast.error("Error in retrieving graph extension !", {
|
||||
position: toast.POSITION.BOTTOM_CENTER
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onTabChange(e: any, data: any): void {
|
||||
console.log(data);
|
||||
this.setState({
|
||||
response: null,
|
||||
schemaForm: null
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public render(): React.ReactElement<IGraphextensionProps> {
|
||||
|
||||
|
||||
const columnProps: Partial<IStackProps> = {
|
||||
tokens: { childrenGap: 15 },
|
||||
styles: { root: { width: 300 } }
|
||||
};
|
||||
const panes = [
|
||||
{
|
||||
menuItem: 'POST', render: () =>
|
||||
<Tab.Pane>
|
||||
<Header as='h3'>Create open extension</Header>
|
||||
<Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
|
||||
<Stack {...columnProps}>
|
||||
<TextField
|
||||
name="Theme"
|
||||
label="Theme"
|
||||
value={this.state.schemaForm ? this.state.schemaForm.Theme : ""}
|
||||
onChange={this.onChangeValue} />
|
||||
<TextField
|
||||
name="Tags"
|
||||
label="Tags"
|
||||
value={this.state.schemaForm ? this.state.schemaForm.Tags : ""}
|
||||
onChange={this.onChangeValue} />
|
||||
</Stack>
|
||||
<Stack {...columnProps}>
|
||||
{this.state.schemaForm != null ?
|
||||
<pre>{JSON.stringify(this.state.schemaForm, null, 2)}</pre>
|
||||
: ""}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<PrimaryButton className={styles.button} text="Create Extension" onClick={this.onCreateExtension} allowDisabledFocus />
|
||||
|
||||
</Tab.Pane>
|
||||
},
|
||||
{
|
||||
menuItem: 'GET', render: () =>
|
||||
|
||||
<Tab.Pane>
|
||||
<Header as='h3'>Get open extension</Header>
|
||||
<PrimaryButton className={styles.button} text="Get Extension" onClick={this.onGetExtension} allowDisabledFocus />
|
||||
|
||||
<Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
|
||||
<Stack {...columnProps}>
|
||||
{this.state.response != null ? <pre>{JSON.stringify(this.state.response, null, 2)}</pre> : ""}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Tab.Pane>
|
||||
},
|
||||
{
|
||||
menuItem: 'PATCH', render: () =>
|
||||
|
||||
<Tab.Pane>
|
||||
<Header as='h3'>Patch Extension</Header>
|
||||
<Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
|
||||
<Stack {...columnProps}>
|
||||
<TextField
|
||||
name="Theme"
|
||||
label="Theme"
|
||||
value={this.state.schemaForm ? this.state.schemaForm.Theme : ""}
|
||||
onChange={this.onChangeValue} />
|
||||
<TextField
|
||||
name="Tags"
|
||||
label="Tags"
|
||||
value={this.state.schemaForm ? this.state.schemaForm.Tags : ""}
|
||||
onChange={this.onChangeValue} />
|
||||
</Stack>
|
||||
<Stack {...columnProps}>
|
||||
{this.state.schemaForm != null ?
|
||||
<pre>{JSON.stringify(this.state.schemaForm, null, 2)}</pre>
|
||||
: ""}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<PrimaryButton className={styles.button} text="Patch Extension" onClick={this.onPatchExtension} allowDisabledFocus />
|
||||
|
||||
|
||||
|
||||
</Tab.Pane>
|
||||
},
|
||||
{
|
||||
menuItem: 'DELETE', render: () => <Tab.Pane>
|
||||
<Header as='h3'>Delete Extension</Header>
|
||||
<PrimaryButton className={styles.button} text="Delete Extension" onClick={this.onDeleteExtension} allowDisabledFocus />
|
||||
|
||||
<Stack horizontal tokens={{ childrenGap: 50 }} styles={{ root: { width: 650 } }}>
|
||||
<Stack {...columnProps}>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Tab.Pane>
|
||||
}
|
||||
];
|
||||
return (
|
||||
<div className={styles.graphextension}>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.column12}>
|
||||
<Header as='h1'>Graph Open Extension Demo</Header>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.column12}>
|
||||
<Tab panes={panes} onTabChange={this.onTabChange}></Tab>
|
||||
<ToastContainer></ToastContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
export interface IGraphextensionProps {
|
||||
webpartContext:WebPartContext;
|
||||
}
|
|
@ -0,0 +1,624 @@
|
|||
.Toastify__toast-container {
|
||||
z-index: 9999;
|
||||
-webkit-transform: translate3d(0, 0, 9999px);
|
||||
position: fixed;
|
||||
padding: 4px;
|
||||
width: 320px;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.Toastify__toast-container--top-left {
|
||||
top: 1em;
|
||||
left: 1em;
|
||||
}
|
||||
|
||||
.Toastify__toast-container--top-center {
|
||||
top: 1em;
|
||||
left: 50%;
|
||||
margin-left: -160px;
|
||||
}
|
||||
|
||||
.Toastify__toast-container--top-right {
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
}
|
||||
|
||||
.Toastify__toast-container--bottom-left {
|
||||
bottom: 1em;
|
||||
left: 1em;
|
||||
}
|
||||
|
||||
.Toastify__toast-container--bottom-center {
|
||||
bottom: 1em;
|
||||
left: 50%;
|
||||
margin-left: -160px;
|
||||
}
|
||||
|
||||
.Toastify__toast-container--bottom-right {
|
||||
bottom: 1em;
|
||||
right: 1em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
.Toastify__toast-container {
|
||||
width: 100vw;
|
||||
padding: 0;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.Toastify__toast-container--top-left,
|
||||
.Toastify__toast-container--top-center,
|
||||
.Toastify__toast-container--top-right {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.Toastify__toast-container--bottom-left,
|
||||
.Toastify__toast-container--bottom-center,
|
||||
.Toastify__toast-container--bottom-right {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.Toastify__toast-container--rtl {
|
||||
right: 0;
|
||||
left: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__toast {
|
||||
position: relative;
|
||||
min-height: 64px;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 1rem;
|
||||
padding: 8px;
|
||||
border-radius: 1px;
|
||||
box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.1), 0 2px 15px 0 rgba(0, 0, 0, 0.05);
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
max-height: 800px;
|
||||
overflow: hidden;
|
||||
font-family: sans-serif;
|
||||
cursor: pointer;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.Toastify__toast--rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.Toastify__toast--default {
|
||||
background: #fff;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.Toastify__toast--info {
|
||||
background: #3498db;
|
||||
}
|
||||
|
||||
.Toastify__toast--success {
|
||||
background: #28a745 !important;
|
||||
}
|
||||
|
||||
.Toastify__toast--warning {
|
||||
background: #ffc107!important;
|
||||
}
|
||||
|
||||
.Toastify__toast--error {
|
||||
background: #dc3545 !important;
|
||||
}
|
||||
|
||||
.Toastify__toast-body {
|
||||
margin: auto 0;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
.Toastify__toast {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__close-button {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
transition: 0.3s ease;
|
||||
-ms-flex-item-align: start;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.Toastify__close-button--default {
|
||||
color: #000;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.Toastify__close-button:hover,
|
||||
.Toastify__close-button:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes Toastify__trackProgress {
|
||||
0% {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__progress-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
z-index: 9999;
|
||||
opacity: 0.7;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.Toastify__progress-bar--animated {
|
||||
animation: Toastify__trackProgress linear 1 forwards;
|
||||
}
|
||||
|
||||
.Toastify__progress-bar--controlled {
|
||||
transition: transform .2s;
|
||||
}
|
||||
|
||||
.Toastify__progress-bar--rtl {
|
||||
right: 0;
|
||||
left: initial;
|
||||
transform-origin: right;
|
||||
}
|
||||
|
||||
.Toastify__progress-bar--default {
|
||||
background: linear-gradient(to right, #4cd964, #5ac8fa, #007aff, #34aadc, #5856d6, #ff2d55);
|
||||
}
|
||||
|
||||
@keyframes Toastify__bounceInRight {
|
||||
|
||||
from,
|
||||
60%,
|
||||
75%,
|
||||
90%,
|
||||
to {
|
||||
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(3000px, 0, 0);
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 1;
|
||||
transform: translate3d(-25px, 0, 0);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translate3d(10px, 0, 0);
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: translate3d(-5px, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__bounceOutRight {
|
||||
20% {
|
||||
opacity: 1;
|
||||
transform: translate3d(-20px, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translate3d(2000px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__bounceInLeft {
|
||||
|
||||
from,
|
||||
60%,
|
||||
75%,
|
||||
90%,
|
||||
to {
|
||||
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(-3000px, 0, 0);
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 1;
|
||||
transform: translate3d(25px, 0, 0);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translate3d(-10px, 0, 0);
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: translate3d(5px, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__bounceOutLeft {
|
||||
20% {
|
||||
opacity: 1;
|
||||
transform: translate3d(20px, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translate3d(-2000px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__bounceInUp {
|
||||
|
||||
from,
|
||||
60%,
|
||||
75%,
|
||||
90%,
|
||||
to {
|
||||
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 3000px, 0);
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, -20px, 0);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translate3d(0, 10px, 0);
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: translate3d(0, -5px, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__bounceOutUp {
|
||||
20% {
|
||||
transform: translate3d(0, -10px, 0);
|
||||
}
|
||||
|
||||
40%,
|
||||
45% {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 20px, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -2000px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__bounceInDown {
|
||||
|
||||
from,
|
||||
60%,
|
||||
75%,
|
||||
90%,
|
||||
to {
|
||||
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -3000px, 0);
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 25px, 0);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: translate3d(0, -10px, 0);
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: translate3d(0, 5px, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__bounceOutDown {
|
||||
20% {
|
||||
transform: translate3d(0, 10px, 0);
|
||||
}
|
||||
|
||||
40%,
|
||||
45% {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, -20px, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 2000px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__bounce-enter--top-left,
|
||||
.Toastify__bounce-enter--bottom-left {
|
||||
animation-name: Toastify__bounceInLeft;
|
||||
}
|
||||
|
||||
.Toastify__bounce-enter--top-right,
|
||||
.Toastify__bounce-enter--bottom-right {
|
||||
animation-name: Toastify__bounceInRight;
|
||||
}
|
||||
|
||||
.Toastify__bounce-enter--top-center {
|
||||
animation-name: Toastify__bounceInDown;
|
||||
}
|
||||
|
||||
.Toastify__bounce-enter--bottom-center {
|
||||
animation-name: Toastify__bounceInUp;
|
||||
}
|
||||
|
||||
.Toastify__bounce-exit--top-left,
|
||||
.Toastify__bounce-exit--bottom-left {
|
||||
animation-name: Toastify__bounceOutLeft;
|
||||
}
|
||||
|
||||
.Toastify__bounce-exit--top-right,
|
||||
.Toastify__bounce-exit--bottom-right {
|
||||
animation-name: Toastify__bounceOutRight;
|
||||
}
|
||||
|
||||
.Toastify__bounce-exit--top-center {
|
||||
animation-name: Toastify__bounceOutUp;
|
||||
}
|
||||
|
||||
.Toastify__bounce-exit--bottom-center {
|
||||
animation-name: Toastify__bounceOutDown;
|
||||
}
|
||||
|
||||
@keyframes Toastify__zoomIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale3d(0.3, 0.3, 0.3);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__zoomOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0;
|
||||
transform: scale3d(0.3, 0.3, 0.3);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__zoom-enter {
|
||||
animation-name: Toastify__zoomIn;
|
||||
}
|
||||
|
||||
.Toastify__zoom-exit {
|
||||
animation-name: Toastify__zoomOut;
|
||||
}
|
||||
|
||||
@keyframes Toastify__flipIn {
|
||||
from {
|
||||
transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
|
||||
animation-timing-function: ease-in;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: perspective(400px) rotate3d(1, 0, 0, 10deg);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: perspective(400px) rotate3d(1, 0, 0, -5deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: perspective(400px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__flipOut {
|
||||
from {
|
||||
transform: perspective(400px);
|
||||
}
|
||||
|
||||
30% {
|
||||
transform: perspective(400px) rotate3d(1, 0, 0, -20deg);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: perspective(400px) rotate3d(1, 0, 0, 90deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__flip-enter {
|
||||
animation-name: Toastify__flipIn;
|
||||
}
|
||||
|
||||
.Toastify__flip-exit {
|
||||
animation-name: Toastify__flipOut;
|
||||
}
|
||||
|
||||
@keyframes Toastify__slideInRight {
|
||||
from {
|
||||
transform: translate3d(110%, 0, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__slideInLeft {
|
||||
from {
|
||||
transform: translate3d(-110%, 0, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__slideInUp {
|
||||
from {
|
||||
transform: translate3d(0, 110%, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__slideInDown {
|
||||
from {
|
||||
transform: translate3d(0, -110%, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__slideOutRight {
|
||||
from {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
visibility: hidden;
|
||||
transform: translate3d(110%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__slideOutLeft {
|
||||
from {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
visibility: hidden;
|
||||
transform: translate3d(-110%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__slideOutDown {
|
||||
from {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
visibility: hidden;
|
||||
transform: translate3d(0, 500px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes Toastify__slideOutUp {
|
||||
from {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
to {
|
||||
visibility: hidden;
|
||||
transform: translate3d(0, -500px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__slide-enter--top-left,
|
||||
.Toastify__slide-enter--bottom-left {
|
||||
animation-name: Toastify__slideInLeft;
|
||||
}
|
||||
|
||||
.Toastify__slide-enter--top-right,
|
||||
.Toastify__slide-enter--bottom-right {
|
||||
animation-name: Toastify__slideInRight;
|
||||
}
|
||||
|
||||
.Toastify__slide-enter--top-center {
|
||||
animation-name: Toastify__slideInDown;
|
||||
}
|
||||
|
||||
.Toastify__slide-enter--bottom-center {
|
||||
animation-name: Toastify__slideInUp;
|
||||
}
|
||||
|
||||
.Toastify__slide-exit--top-left,
|
||||
.Toastify__slide-exit--bottom-left {
|
||||
animation-name: Toastify__slideOutLeft;
|
||||
}
|
||||
|
||||
.Toastify__slide-exit--top-right,
|
||||
.Toastify__slide-exit--bottom-right {
|
||||
animation-name: Toastify__slideOutRight;
|
||||
}
|
||||
|
||||
.Toastify__slide-exit--top-center {
|
||||
animation-name: Toastify__slideOutUp;
|
||||
}
|
||||
|
||||
.Toastify__slide-exit--bottom-center {
|
||||
animation-name: Toastify__slideOutDown;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=ReactToastify.css.map */
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
10
samples/react-msgraph-extension/src/webparts/graphextension/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare interface IGraphextensionWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'GraphextensionWebPartStrings' {
|
||||
const strings: IGraphextensionWebPartStrings;
|
||||
export = strings;
|
||||
}
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.9/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
|
@ -58,6 +58,10 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
|
|||
- How JavaScript library can be loaded by the help of requirejs in web part.
|
||||
- Mobile Touch capabilities
|
||||
|
||||
## SharePoint info
|
||||
|
||||
When using the webpart in SharePoint, either in the SharePoint Workbench or deployed, the webpart reads by default from a List called "Swiper Content" with fields Title, ImageUrl, Description of type Single line of text. The list has to be created manually.
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-slide-swiper" />
|
||||
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@ export default class Card extends React.Component<ICardProps, {}> {
|
|||
return (
|
||||
<div className={styles.card}>
|
||||
<div className={styles.wrapper}>
|
||||
<img src={this.props.listItem.imageUrl} className={styles.image} />
|
||||
<img src={this.props.listItem.ImageUrl} className={styles.image} />
|
||||
<a href="#" className={styles.url} >
|
||||
<h3 className={styles.title}>{this.props.listItem.title}</h3>
|
||||
<h3 className={styles.title}>{this.props.listItem.Title}</h3>
|
||||
</a>
|
||||
<p className={styles.description}>{this.props.listItem.description}</p>
|
||||
<p className={styles.description}>{this.props.listItem.Description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export class ListItem {
|
||||
|
||||
public title: string;
|
||||
public description:string;
|
||||
public imageUrl: string;
|
||||
public Title: string;
|
||||
public Description:string;
|
||||
public ImageUrl: string;
|
||||
}
|
|
@ -9,124 +9,124 @@ export class ListMock implements IListService {
|
|||
const fakeData: Array<ListItem> = [
|
||||
|
||||
{
|
||||
title: 'A convergent value empowers the standard-setters',
|
||||
description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/OFFICE365.png'
|
||||
Title: 'A convergent value empowers the standard-setters',
|
||||
Description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/OFFICE365.png'
|
||||
},
|
||||
{
|
||||
title: 'The Digital Marketers empower a digitized correlation',
|
||||
description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/POWERSHELL.png'
|
||||
Title: 'The Digital Marketers empower a digitized correlation',
|
||||
Description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/POWERSHELL.png'
|
||||
},
|
||||
{
|
||||
title: 'The market thinker strategically standardizes a competitive success',
|
||||
description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/PYTHON.png'
|
||||
Title: 'The market thinker strategically standardizes a competitive success',
|
||||
Description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/PYTHON.png'
|
||||
},
|
||||
{
|
||||
title: 'We are going to secure our cross-pollinations',
|
||||
description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/SP.png'
|
||||
Title: 'We are going to secure our cross-pollinations',
|
||||
Description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/SP.png'
|
||||
},
|
||||
{
|
||||
title: 'A convergent value empowers the standard-setters',
|
||||
description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/JAVASCRIPT.png'
|
||||
Title: 'A convergent value empowers the standard-setters',
|
||||
Description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/JAVASCRIPT.png'
|
||||
},
|
||||
{
|
||||
title: 'The Digital Marketers empower a digitized correlation',
|
||||
description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/POWERSHELL.png'
|
||||
Title: 'The Digital Marketers empower a digitized correlation',
|
||||
Description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/POWERSHELL.png'
|
||||
},
|
||||
{
|
||||
title: 'The market thinker strategically standardizes a competitive success',
|
||||
description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/PYTHON.png'
|
||||
Title: 'The market thinker strategically standardizes a competitive success',
|
||||
Description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/PYTHON.png'
|
||||
},
|
||||
{
|
||||
title: 'We are going to secure our cross-pollinations',
|
||||
description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/SP.png'
|
||||
Title: 'We are going to secure our cross-pollinations',
|
||||
Description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/SP.png'
|
||||
},
|
||||
{
|
||||
title: 'A convergent value empowers the standard-setters',
|
||||
description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/OFFICE365.png'
|
||||
Title: 'A convergent value empowers the standard-setters',
|
||||
Description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/OFFICE365.png'
|
||||
},
|
||||
{
|
||||
title: 'The Digital Marketers empower a digitized correlation',
|
||||
description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/JAVASCRIPT.png'
|
||||
Title: 'The Digital Marketers empower a digitized correlation',
|
||||
Description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/JAVASCRIPT.png'
|
||||
},
|
||||
{
|
||||
title: 'The market thinker strategically standardizes a competitive success',
|
||||
description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/PYTHON.png'
|
||||
Title: 'The market thinker strategically standardizes a competitive success',
|
||||
Description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/PYTHON.png'
|
||||
},
|
||||
{
|
||||
title: 'We are going to secure our cross-pollinations',
|
||||
description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/SP.png'
|
||||
Title: 'We are going to secure our cross-pollinations',
|
||||
Description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/SP.png'
|
||||
},
|
||||
{
|
||||
title: 'A convergent value empowers the standard-setters',
|
||||
description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/OFFICE365.png'
|
||||
Title: 'A convergent value empowers the standard-setters',
|
||||
Description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/OFFICE365.png'
|
||||
},
|
||||
{
|
||||
title: 'The Digital Marketers empower a digitized correlation',
|
||||
description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/POWERSHELL.png'
|
||||
Title: 'The Digital Marketers empower a digitized correlation',
|
||||
Description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/POWERSHELL.png'
|
||||
},
|
||||
{
|
||||
title: 'The market thinker strategically standardizes a competitive success',
|
||||
description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/JAVASCRIPT.png'
|
||||
Title: 'The market thinker strategically standardizes a competitive success',
|
||||
Description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/JAVASCRIPT.png'
|
||||
},
|
||||
{
|
||||
title: 'We are going to secure our cross-pollinations',
|
||||
description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/SP.png'
|
||||
Title: 'We are going to secure our cross-pollinations',
|
||||
Description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/SP.png'
|
||||
},
|
||||
{
|
||||
title: 'A convergent value empowers the standard-setters',
|
||||
description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/OFFICE365.png'
|
||||
Title: 'A convergent value empowers the standard-setters',
|
||||
Description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/OFFICE365.png'
|
||||
},
|
||||
{
|
||||
title: 'The Digital Marketers empower a digitized correlation',
|
||||
description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/POWERSHELL.png'
|
||||
Title: 'The Digital Marketers empower a digitized correlation',
|
||||
Description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/POWERSHELL.png'
|
||||
},
|
||||
{
|
||||
title: 'The market thinker strategically standardizes a competitive success',
|
||||
description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/PYTHON.png'
|
||||
Title: 'The market thinker strategically standardizes a competitive success',
|
||||
Description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/PYTHON.png'
|
||||
},
|
||||
{
|
||||
title: 'We are going to secure our cross-pollinations',
|
||||
description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/JAVASCRIPT.png'
|
||||
Title: 'We are going to secure our cross-pollinations',
|
||||
Description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/JAVASCRIPT.png'
|
||||
},
|
||||
{
|
||||
title: 'A convergent value empowers the standard-setters',
|
||||
description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/OFFICE365.png'
|
||||
Title: 'A convergent value empowers the standard-setters',
|
||||
Description: 'The General Head of IT Strategy benchmarks business-for-business agilities',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/OFFICE365.png'
|
||||
},
|
||||
{
|
||||
title: 'The Digital Marketers empower a digitized correlation',
|
||||
description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/POWERSHELL.png'
|
||||
Title: 'The Digital Marketers empower a digitized correlation',
|
||||
Description: 'Whereas synchronized brand values promote strategy formulations',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/POWERSHELL.png'
|
||||
},
|
||||
{
|
||||
title: 'The market thinker strategically standardizes a competitive success',
|
||||
description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/PYTHON.png'
|
||||
Title: 'The market thinker strategically standardizes a competitive success',
|
||||
Description: 'The thinkers/planners benchmark a disciplined growth momentum',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/PYTHON.png'
|
||||
},
|
||||
{
|
||||
title: 'We are going to secure our cross-pollinations',
|
||||
description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
imageUrl: 'https://blog.velingeorgiev.com/static/images/SP.png'
|
||||
Title: 'We are going to secure our cross-pollinations',
|
||||
Description: 'We are working hard to reintermediate a competitive advantage, while the gatekeeper straightforwardly identifies barriers to success',
|
||||
ImageUrl: 'https://blog.velingeorgiev.com/static/images/SP.png'
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"msjsdiag.debugger-for-chrome"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
/**
|
||||
* Install Chrome Debugger Extension for Visual Studio Code to debug your components with the
|
||||
* Chrome browser: https://aka.ms/spfx-debugger-extensions
|
||||
*/
|
||||
"version": "0.2.0",
|
||||
"configurations": [{
|
||||
"name": "Local workbench",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "https://localhost:4321/temp/workbench.html",
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"sourceMaps": true,
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack:///.././src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"--remote-debugging-port=9222"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Hosted workbench",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "https://enter-your-SharePoint-site/_layouts/workbench.aspx",
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"sourceMaps": true,
|
||||
"sourceMapPathOverrides": {
|
||||
"webpack:///.././src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../src/*": "${webRoot}/src/*",
|
||||
"webpack:///../../../../../src/*": "${webRoot}/src/*"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"--remote-debugging-port=9222",
|
||||
"-incognito"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
// Configure glob patterns for excluding files and folders in the file explorer.
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/bower_components": true,
|
||||
"**/coverage": true,
|
||||
"**/lib-amd": true,
|
||||
"src/**/*.scss.ts": true
|
||||
},
|
||||
"typescript.tsdk": ".\\node_modules\\typescript\\lib"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.7.1",
|
||||
"libraryName": "react-teams-tabs-pnpjs",
|
||||
"libraryId": "1e68649b-930f-4502-a858-12aa997bda01",
|
||||
"packageManager": "npm",
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
# react-teams-tabs-pnpjs - MS Teams Channels and Tabs from Modern Team site.
|
||||
|
||||
## Summary
|
||||
|
||||
A SPFx WebPart using [@pnp/graph/teams](https://pnp.github.io/pnpjs/graph/docs/teams/). It shows Channels and Tabs (with link) from a Modern Team Site connected to Microsoft Teams.
|
||||
|
||||
## react-teams-tabs-pnpjs preview
|
||||
![WebPartInAction](./assets/react-teams-tabs-pnpjs-webpart.png)
|
||||
|
||||
## react-teams-tabs-pnpjs in action
|
||||
![WebPartInAction](./assets/react-teams-tabs-pnpjs-webpart-animated.gif)
|
||||
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-teams-tabs-pnpjs | [Federico Porceddu](https://www.federicoporceddu.com)
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|October 30, 2019|Initial release
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
* Clone this repository
|
||||
* in the command line run:
|
||||
* restore dependencies: `npm install`
|
||||
* build solution `gulp build --ship`
|
||||
* bundle solution: `gulp bundle --ship`
|
||||
* package solution: `gulp package-solution --ship`
|
||||
* locate solution at `.\sharepoint\solution\react-teams-tabs-pnpjs.sppkg`
|
||||
* upload it to your tenant app catalog
|
||||
* [approve permission requests](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/use-aadhttpclient#manage-permission-requests) into SharePoint Online Admin API Permission page
|
||||
* add `react-teams-tabs-pnpjs` app to your site
|
||||
* add `react-teams-tabs-pnpjs` webpart to your page to see it in action
|
||||
|
||||
## Features
|
||||
|
||||
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
* How to use Microsoft Graph with PnPJS
|
||||
* How to use [@pnp/graph/teams](https://pnp.github.io/pnpjs/graph/docs/teams/)
|
||||
* How to configure SharePoint Online Tenant and SPFx solution to allow Microsoft Graph calls.
|
||||
* Microsoft Graph API for Microsoft Teams
|
||||
* [Fabric UI Nav component](https://developer.microsoft.com/en-us/fabric#/controls/web/nav)
|
||||
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/react-teams-tabs-pnpjs" />
|
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 7.7 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"react-teams-tabs-pnpjs-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/reactTeamsTabsPnpjs/ReactTeamsTabsPnpjsWebPart.js",
|
||||
"manifest": "./src/webparts/reactTeamsTabsPnpjs/ReactTeamsTabsPnpjsWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"ReactTeamsTabsPnpjsWebPartStrings": "lib/webparts/reactTeamsTabsPnpjs/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-teams-tabs-pnpjs",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-teams-tabs-pnpjs-client-side-solution",
|
||||
"id": "1e68649b-930f-4502-a858-12aa997bda01",
|
||||
"version": "4.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Group.ReadWrite.All"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-teams-tabs-pnpjs.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "react-teams-tabs-pnpjs",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/rush-stack-compiler-3.2": "^0.5.2",
|
||||
"@microsoft/sp-core-library": "^1.9.1",
|
||||
"@microsoft/sp-lodash-subset": "^1.9.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "^1.9.1",
|
||||
"@microsoft/sp-webpart-base": "^1.9.1",
|
||||
"@pnp/common": "^1.3.6",
|
||||
"@pnp/graph": "^1.3.6",
|
||||
"@pnp/logging": "^1.3.6",
|
||||
"@pnp/odata": "^1.3.6",
|
||||
"@pnp/sp": "^1.3.6",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "^16.8.8",
|
||||
"@types/react-dom": "^16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"office-ui-fabric-react": "^6.189.2",
|
||||
"react": "^16.8.5",
|
||||
"react-dom": "^16.8.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "^1.9.1",
|
||||
"@microsoft/sp-module-interfaces": "^1.9.1",
|
||||
"@microsoft/sp-tslint-rules": "^1.9.1",
|
||||
"@microsoft/sp-webpart-workbench": "^1.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "a19ee11a-0490-435a-8f93-e777db19d602",
|
||||
"alias": "ReactTeamsTabsPnpjsWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "react-teams-tabs-pnpjs" },
|
||||
"description": { "default": "react-teams-tabs-pnpjs description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "react-teams-tabs-pnpjs"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
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 'ReactTeamsTabsPnpjsWebPartStrings';
|
||||
import ReactTeamsTabsPnpjs from './components/ReactTeamsTabsPnpjs';
|
||||
import { IReactTeamsTabsPnpjsProps } from './components/IReactTeamsTabsPnpjsProps';
|
||||
import { graph } from "@pnp/graph";
|
||||
import { sp } from "@pnp/sp";
|
||||
|
||||
|
||||
|
||||
export interface IReactTeamsTabsPnpjsWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class ReactTeamsTabsPnpjsWebPart extends BaseClientSideWebPart<IReactTeamsTabsPnpjsWebPartProps> {
|
||||
|
||||
public onInit(): Promise<void> {
|
||||
|
||||
return super.onInit().then(_ => {
|
||||
sp.setup({
|
||||
spfxContext: this.context
|
||||
});
|
||||
|
||||
graph.setup({
|
||||
spfxContext: this.context
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IReactTeamsTabsPnpjsProps > = React.createElement(
|
||||
ReactTeamsTabsPnpjs,
|
||||
{
|
||||
description: this.properties.description
|
||||
}
|
||||
);
|
||||
|
||||
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
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface IReactTeamsTabsPnpjsProps {
|
||||
description: string;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import {
|
||||
ClientSideText,
|
||||
ClientSideWebpart,
|
||||
sp,
|
||||
ClientSidePage
|
||||
} from "@pnp/sp";
|
||||
import { graph, Channel, Channels } from "@pnp/graph";
|
||||
|
||||
|
||||
|
||||
|
||||
export class ReactTeamsTabsHelper {
|
||||
|
||||
public static async getGroupId(): Promise<string> {
|
||||
|
||||
var id: string = "";
|
||||
|
||||
var props: any = await sp.web.select("AllProperties")
|
||||
.expand("AllProperties")
|
||||
.get();
|
||||
|
||||
if (props.AllProperties["GroupId"] != null) {
|
||||
id = props.AllProperties["GroupId"];
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public static async getChannels(groupId: string): Promise<any[]> {
|
||||
|
||||
var channels: any[]= [];
|
||||
|
||||
channels = await graph.teams.getById(groupId).channels.get();
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
public static async getTabsFromChannel(groupId: string, channelId: string): Promise<any[]> {
|
||||
|
||||
var tabs: any[] = [];
|
||||
|
||||
tabs = await graph.teams.getById(groupId).channels.getById(channelId)
|
||||
.tabs
|
||||
.get();
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.reactTeamsTabsPnpjs {
|
||||
.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);
|
||||
}
|
||||
|
||||
.itemContent{
|
||||
margin-left: 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.itemName{
|
||||
font-size: initial;
|
||||
white-space: nowrap;
|
||||
overflow: 'hidden';
|
||||
text-overflow: 'ellipsis';
|
||||
}
|
||||
|
||||
.itemCell{
|
||||
margin: 2px;
|
||||
min-height: 54;
|
||||
padding: 10;
|
||||
box-sizing: 'border-box';
|
||||
//border: 1px solid;
|
||||
display: 'flex';
|
||||
//background : $ms-color-themeDarkAlt;
|
||||
background : rgb(196, 195, 195);
|
||||
|
||||
}
|
||||
|
||||
.itemCell:hover{
|
||||
background : rgb(160, 160, 160);
|
||||
color : white;
|
||||
}
|
||||
|
||||
|
||||
.tablink{
|
||||
//color : $ms-color-neutralTertiary;
|
||||
margin-bottom : 10;
|
||||
color : rgb(77, 77, 77);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tablink:hover{
|
||||
//color : $ms-color-neutralTertiary;
|
||||
text-decoration: underline;
|
||||
margin-bottom : 10;
|
||||
color : white;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import * as React from 'react';
|
||||
import styles from './ReactTeamsTabsPnpjs.module.scss';
|
||||
import { IReactTeamsTabsPnpjsProps } from './IReactTeamsTabsPnpjsProps';
|
||||
import { Nav } from 'office-ui-fabric-react/lib/Nav';
|
||||
import { MessageBar } from 'office-ui-fabric-react';
|
||||
import { ReactTeamsTabsHelper } from './ReactTeamsTabsHelper';
|
||||
|
||||
|
||||
export interface IReactTeamsTabsPnpjsState {
|
||||
pivotArray: any[];
|
||||
}
|
||||
export default class ReactTeamsTabsPnpjs extends React.Component<IReactTeamsTabsPnpjsProps, IReactTeamsTabsPnpjsState> {
|
||||
|
||||
constructor(props: IReactTeamsTabsPnpjsProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
pivotArray: []
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className={styles.reactTeamsTabsPnpjs}>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.column}>
|
||||
<div>
|
||||
<MessageBar>
|
||||
Here you can find Channels list and Tabs from MS Teams linked to this site.
|
||||
</MessageBar>
|
||||
<Nav
|
||||
groups={this.state.pivotArray}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
public componentDidMount() {
|
||||
|
||||
|
||||
var groupId: Promise<string> = ReactTeamsTabsHelper.getGroupId();
|
||||
|
||||
groupId.then(group => {
|
||||
console.log("GroupID: " + group);
|
||||
var tmpChannels: any[] = [];
|
||||
if (group != "") {
|
||||
var channels: Promise<any[]> = ReactTeamsTabsHelper.getChannels(group);
|
||||
|
||||
channels.then(chans => {
|
||||
console.log("Channels " + chans.length);
|
||||
chans.forEach(channel => {
|
||||
var tabs: Promise<any[]> = ReactTeamsTabsHelper.getTabsFromChannel(group, channel.id);
|
||||
var tmpTabs: any[] = [];
|
||||
tabs.then(itemTabs => {
|
||||
console.log("Channel" + channel.displayName + "tabs " + itemTabs.length);
|
||||
itemTabs.forEach(tab => {
|
||||
tmpTabs.push({ key: tab.id, name: tab.displayName, url: tab.webUrl, target: '_blank' });
|
||||
});
|
||||
tmpChannels.push({ name: channel.displayName + " (" + tmpTabs.length + ")", links: tmpTabs });
|
||||
tmpChannels.sort(this.mySorter);
|
||||
this.setState({ pivotArray: tmpChannels });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
//TODO show generic message, because there is not a team linked to current site
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public mySorter(a: any, b: any) {
|
||||
var x = a.name.toLowerCase();
|
||||
var y = b.name.toLowerCase();
|
||||
//fix to manage general channel at first position, like Teams order
|
||||
//verify language general label
|
||||
if (x.startsWith("general")) {
|
||||
return -1;
|
||||
} else if (y.startsWith("general")) {
|
||||
return 1;
|
||||
}
|
||||
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
10
samples/react-teams-tabs-pnpjs/src/webparts/reactTeamsTabsPnpjs/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare interface IReactTeamsTabsPnpjsWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'ReactTeamsTabsPnpjsWebPartStrings' {
|
||||
const strings: IReactTeamsTabsPnpjsWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|