Merge pull request #1047 from SharePoint/dev

Merge from dev to master
This commit is contained in:
Laura Kokkarinen 2019-11-03 11:20:22 -05:00 committed by GitHub
commit 10a69fc1aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 47105 additions and 3598 deletions

View File

@ -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",

View File

@ -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.**

View File

@ -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"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}
}

View File

@ -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;

View File

@ -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 { }

View File

@ -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';

View File

@ -1,6 +1,7 @@
import { ControlMode } from '../../common/datatypes/ControlMode';
import { IFieldConfiguration } from './components/IFieldConfiguration';
export interface IListFormWebPartProps {
title: string;
description: string;

View File

@ -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",

View File

@ -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', {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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}

View File

@ -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;

View File

@ -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",

View File

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

View File

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

View File

@ -0,0 +1,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"
}
}

View File

@ -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" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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"
}
}

View File

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

View File

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

View File

@ -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"
}
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -0,0 +1,5 @@
export default class Constants {
public static readonly ExtensionName = "com.ejazhussain.settings";
}

View File

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

View File

@ -0,0 +1,4 @@
export interface IFormSchema {
Theme?: string;
Tags?: string;
}

View File

@ -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;
}
}

View File

@ -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);
});
});
});
}
}

View File

@ -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"
}
}]
}

View File

@ -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
})
]
}
]
}
]
};
}
}

View File

@ -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;
}
}
}

View File

@ -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>
);
}
}

View File

@ -0,0 +1,5 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
export interface IGraphextensionProps {
webpartContext:WebPartContext;
}

View File

@ -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 */

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

View File

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

View File

@ -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" />

View File

@ -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>
);

View File

@ -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;
}

View File

@ -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'
}
];

View File

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

View File

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

View File

@ -0,0 +1,5 @@
{
"recommendations": [
"msjsdiag.debugger-for-chrome"
]
}

View File

@ -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"
]
}
]
}

View File

@ -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"
}

View File

@ -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"
}
}

View File

@ -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" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -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"
}
}

View File

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

View File

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

View File

@ -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"
}
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

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

View File

@ -0,0 +1,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"
}
}]
}

View File

@ -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
})
]
}
]
}
]
};
}
}

View File

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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

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

View File

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

View File

@ -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"
]
}

View File

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