Merge pull request #1045 from fredupstair/dev

react-teams-tabs-pnpjs - MS Teams Channels and Tabs from Modern Team site.
This commit is contained in:
Laura Kokkarinen 2019-11-03 11:13:55 -05:00 committed by GitHub
commit c5d3741779
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 22516 additions and 0 deletions

View File

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

View File

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

View File

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