Merge pull request #1 from SharePoint/master

Merging latest from SharePoint/sp-dev-fx-webparts
This commit is contained in:
Eric Skaggs 2019-04-18 16:25:59 -07:00 committed by GitHub
commit 564a0918d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1380 changed files with 446652 additions and 83506 deletions

3256
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,71 @@
# Angular Elements with HTML Template File in SharePoint Framework
## Summary
A sample web part illustrating how to use Angular Elements in the SharePoint Framework with the help of separate template HTML File.
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/drop-1.4.1-green.svg)
## Applies to
* [SharePoint Framework](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
* [Office 365 developer tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
## Solution
Solution|Author(s)
--------|---------
angularelements-html-templatefile| Jayakumar Balasubramaniam (C# Corner MVP, Hubfly, @jayakumrB)
## Version history
Version|Date|Comments
-------|----|--------
1.0|Jan 8, 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 repo
* in the command line run:
* `npm i`
* `gulp serve`
## Features
This web part illustrates the following concepts on top of the SharePoint Framework:
* adding Angular Elements to a no-framework SharePoint Framework project
* bootstrapping Angular Elements inside a SharePoint Framework web part
* extending the building configuration to build Angular Elements
* utilizing build pipeline to compile and run angular template files in gulpfile.js
## Implementation
The below piece of code in gulpfile.js is the key to update the build pipeline:
```
//************START: Added to handle Template file url ************/
var inlineNgxTemplate = require('gulp-inline-ngx-template');
var ts = require('gulp-typescript');
var tsProject = ts.createProject('./tsconfig.json');
let tsInlines = build.subTask('tsInlines', function(gulp, buildOptions, done) {
return gulp.src('src/webparts/helloAngularTemplate/app/**/*.ts')
.pipe(inlineNgxTemplate({ base: '/src/webparts/helloAngularTemplate/app/', useRelativePaths: true }))
.pipe(tsProject())
.pipe(gulp.dest('lib/webparts/helloAngularTemplate/app'));
})
build.rig.addPostTypescriptTask(tsInlines);
//************END: Added to handle Template file url ************/
```
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/angularelements-helloworld" />

View File

@ -0,0 +1,18 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"angular-web-parts": {
"components": [
{
"entrypoint": "./lib/webparts/helloAngularTemplate/HelloAngularWebPart.js",
"manifest": "./src/webparts/helloAngularTemplate/HelloAngularWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"HelloAngularWebPartStrings": "lib/webparts/helloAngularTemplate/loc/{locale}.js"
}
}

View File

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

View File

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

View File

@ -0,0 +1,13 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "angularelements-using-templatefile-client-side-solution",
"id": "446c046f-46a4-42e5-a5e6-e9e4edfcf09d",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true
},
"paths": {
"zippedPackage": "solution/angularelements-using-templatefile.sppkg"
}
}

View File

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

View File

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

View File

@ -0,0 +1,55 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
const webpack = require('webpack');
const path = require('path');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const bundleAnalyzer = require('webpack-bundle-analyzer');
//************START: Added to handle Template file url ************/
var inlineNgxTemplate = require('gulp-inline-ngx-template');
var ts = require('gulp-typescript');
var tsProject = ts.createProject('./tsconfig.json');
let tsInlines = build.subTask('tsInlines', function(gulp, buildOptions, done) {
return gulp.src('src/webparts/helloAngularTemplate/app/**/*.ts')
.pipe(inlineNgxTemplate({ base: '/src/webparts/helloAngularTemplate/app/', useRelativePaths: true }))
.pipe(tsProject())
.pipe(gulp.dest('lib/webparts/helloAngularTemplate/app'));
})
build.rig.addPostTypescriptTask(tsInlines);
//************END: Added to handle Template file url ************/
build.configureWebpack.mergeConfig({
additionalConfiguration: (generatedConfiguration) => {
const lastDirName = path.basename(__dirname);
const dropPath = path.join(__dirname, 'temp', 'stats');
generatedConfiguration.plugins.push(new bundleAnalyzer.BundleAnalyzerPlugin({
openAnalyzer: false,
analyzerMode: 'static',
reportFilename: path.join(dropPath, `${lastDirName}.stats.html`),
generateStatsFile: true,
statsFilename: path.join(dropPath, `${lastDirName}.stats.json`),
logLevel: 'error'
}));
generatedConfiguration.plugins.push(new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)fesm5/, path.join(__dirname, './client')));
for (let i = 0; i < generatedConfiguration.plugins.length; i++) {
const p = generatedConfiguration.plugins[i];
if (p.options && p.options.mangle) {
generatedConfiguration.plugins.splice(i, 1, new UglifyJSPlugin({ uglifyOptions: { mangle: true } }));
break;
}
}
return generatedConfiguration;
}
});
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
{
"name": "angularelements-helloworld",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@angular/common": "^6.0.3",
"@angular/compiler": "^6.0.3",
"@angular/core": "^6.0.3",
"@angular/elements": "^6.0.3",
"@angular/platform-browser": "^6.0.3",
"@angular/platform-browser-dynamic": "^6.0.3",
"@microsoft/generator-sharepoint": "^1.4.1",
"@microsoft/sp-core-library": "~1.4.1",
"@microsoft/sp-lodash-subset": "~1.4.1",
"@microsoft/sp-office-ui-fabric-core": "~1.4.1",
"@microsoft/sp-webpart-base": "~1.4.1",
"@pnp/common": "^1.1.0",
"@pnp/graph": "^1.1.0",
"@pnp/logging": "^1.1.0",
"@pnp/odata": "^1.1.0",
"@pnp/sp": "^1.1.0",
"@types/webpack-env": ">=1.12.1 <1.14.0",
"@webcomponents/custom-elements": "^1.1.1",
"core-js": "^2.5.7",
"rxjs": "^6.2.0",
"zone.js": "^0.8.26"
},
"devDependencies": {
"@microsoft/sp-build-web": "~1.4.1",
"@microsoft/sp-module-interfaces": "~1.4.1",
"@microsoft/sp-webpart-workbench": "~1.4.1",
"@types/chai": ">=3.4.34 <3.6.0",
"@types/mocha": ">=2.2.33 <2.6.0",
"ajv": "~5.2.2",
"gulp": "~3.9.1",
"gulp-inline-ngx-template": "^4.0.1",
"gulp-typescript": "^5.0.0",
"uglifyjs-webpack-plugin": "^1.2.5",
"webpack-bundle-analyzer": "^2.13.1"
}
}

View File

@ -0,0 +1,26 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "8845f211-1e21-42ae-85b1-da871d3e4dc4",
"alias": "HelloAngularWebPart",
"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": "Angular HTML Template File" },
"description": { "default": "An Angular webpart using template files" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "Hello Angular Template HTML Files"
}
}]
}

View File

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

View File

@ -0,0 +1,57 @@
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';
import styles from './HelloAngularWebPart.module.scss';
import * as strings from 'HelloAngularWebPartStrings';
import '@webcomponents/custom-elements/src/native-shim';
import 'core-js/es7/reflect';
import { AppModule } from './app/app.module';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
export interface IHelloAngularWebPartProps {
description: string;
}
export default class HelloAngularWebPart extends BaseClientSideWebPart<IHelloAngularWebPartProps> {
public render(): void {
platformBrowserDynamic().bootstrapModule(AppModule, { ngZone: 'noop' }).then(() => {
this.domElement.innerHTML = `<hello-world message="${this.properties.description}"></hello-world>`;
});
}
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,27 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { HelloWorldComponent } from './helloWorld.component';
@NgModule({
imports: [
BrowserModule
],
declarations: [
HelloWorldComponent
],
entryComponents: [
HelloWorldComponent
]
})
export class AppModule {
constructor(private injector: Injector) {}
public ngDoBootstrap() {
if(!customElements.get("hello-world")) {
const AppElement = createCustomElement(HelloWorldComponent, { injector: this.injector });
customElements.define('hello-world', AppElement);
}
}
}

View File

@ -0,0 +1,12 @@
import { Component, OnInit, Input } from "@angular/core";
@Component({
selector: 'hello-world',
templateUrl:"helloWorld.component.html"
})
export class HelloWorldComponent implements OnInit {
@Input()
public message: string;
public ngOnInit() { }
}

View File

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

View File

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

View File

@ -0,0 +1,13 @@
{
"@microsoft/generator-sharepoint": {
"plusBeta": true,
"isCreatingSolution": true,
"environment": "spo",
"version": "1.7.1",
"libraryName": "js-public-unjoined-teams",
"libraryId": "f04aba32-b056-47c4-811b-6f17d94cc8d9",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,51 @@
# Public teams I'm not a member of
## Summary
This web part lists all the public teams the current user is not yet a member of. They can then join any of those teams by clicking the button right next to the team name. This web part can also be added to Teams as a tab (built with the 1.7.1 plusbeta/preview version).
![picture of the web part in action](./assets/js-public-unjoined-teams.gif)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.7.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
To be able to pin this web part as a Teams tab, your tenant currently needs to be in the targeted release mode. If your tenant is not in targeted release, you can still use the web part in SharePoint as usual.
## Solution
Solution|Author(s)
--------|---------
js-public-unjoined-teams | Laura Kokkarinen ([laurakokkarinen.com](https://laurakokkarinen.com), [@LauraKokkarinen](https://twitter.com/LauraKokkarinen))
## Version history
Version|Date|Comments
-------|----|--------
1.0|February 5, 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 bundle --ship`
- `gulp package solution --ship`
- Deploy the solution package under \sharepoint\solution to the SharePoint app catalog
- Approve the required Microsoft Graph permissions in the SharePoint admin center (Preview, API management)
## Features
This web part lists all the public teams the current user is not yet a member of. They can then join any of those teams by clicking the button right next to the team name. The Teams client does not present this kind of a complete list by default, so the web part is particularly handy to all end users who might not even be aware of all the public teams that are available. This web part can also be added to Teams as a tab. The web part colors are based on the site color theme.
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-public-unjoined-teams" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 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": {
"public-unjoined-teams-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/publicUnjoinedTeams/PublicUnjoinedTeamsWebPart.js",
"manifest": "./src/webparts/publicUnjoinedTeams/PublicUnjoinedTeamsWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"PublicUnjoinedTeamsWebPartStrings": "lib/webparts/publicUnjoinedTeams/loc/{locale}.js"
}
}

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": "js-public-unjoined-teams",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,28 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "js-public-unjoined-teams-client-side-solution",
"id": "f04aba32-b056-47c4-811b-6f17d94cc8d9",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false,
"webApiPermissionRequests": [
{
"resource": "Microsoft Graph",
"scope": "User.Read.All"
},
{
"resource": "Microsoft Graph",
"scope": "Directory.Read.All"
},
{
"resource": "Microsoft Graph",
"scope": "Group.ReadWrite.All"
}
]
},
"paths": {
"zippedPackage": "solution/js-public-unjoined-teams.sppkg"
}
}

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,31 @@
{
"name": "js-public-unjoined-teams",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.7.1-plusbeta",
"@microsoft/sp-webpart-base": "1.7.1-plusbeta",
"@microsoft/sp-lodash-subset": "1.7.1-plusbeta",
"@microsoft/sp-office-ui-fabric-core": "1.7.1-plusbeta",
"@types/webpack-env": "1.13.1",
"@types/es6-promise": "0.0.33"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.7.1-plusbeta",
"@microsoft/sp-tslint-rules": "1.7.1-plusbeta",
"@microsoft/sp-module-interfaces": "1.7.1-plusbeta",
"@microsoft/sp-webpart-workbench": "1.7.1-plusbeta",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2"
}
}

View File

@ -0,0 +1,19 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "62eb4ba8-6014-4bd8-a428-097ff64f01c6",
"alias": "PublicUnjoinedTeamsWebPart",
"componentType": "WebPart",
"version": "*",
"manifestVersion": 2,
"requiresCustomScript": false,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
"group": { "default": "Other" },
"title": { "default": "Public Unjoined Teams" },
"description": { "default": "Lists all the public teams the current user is not a member of, and displays a button for joining." },
"officeFabricIconFontName": "TeamsLogoInverse",
"properties": {
"title": "Public Teams I'm not a member of"
}
}]
}

View File

@ -0,0 +1,36 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.publicUnjoinedTeams {
.title {
@include ms-font-xl;
color: "[theme: neutralSecondary, default: #80539D]";
margin-bottom: 15px;
}
.container {
max-width: 700px;
margin: 0;
}
.row {
@include ms-Grid-row;
padding: 4px 15px;
}
button:disabled {
background-color: $ms-color-neutralLight;
color: $ms-color-black;
cursor: auto;
}
button {
text-decoration: none;
height: 30px;
min-width: 65px;
background-color: $ms-color-themePrimary;
border: none;
color: $ms-color-white;
cursor: pointer;
margin-right: 10px;
}
}

View File

@ -0,0 +1,131 @@
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { difference, find } from '@microsoft/sp-lodash-subset';
import { MSGraphClient } from '@microsoft/sp-http';
import styles from './PublicUnjoinedTeamsWebPart.module.scss';
import * as strings from 'PublicUnjoinedTeamsWebPartStrings';
export interface IPublicUnjoinedTeamsWebPartProps {
title: string;
}
export default class PublicUnjoinedTeamsWebPart extends BaseClientSideWebPart<IPublicUnjoinedTeamsWebPartProps> {
public async render(): Promise<any> {
var unjoinedTeams = await this.getUnjoinedTeams();
this.domElement.innerHTML = `
<div class="${ styles.publicUnjoinedTeams }">
<div class="${styles.title}">
${this.properties.title}
</div>
<div id="container" class="${styles.container}">
<!-- content gets appended here -->
</div>
</div>`;
await this.renderTeams(unjoinedTeams);
}
protected async getUnjoinedTeams(): Promise<any> {
var joinedTeamIds:Array<string> = (await this.graphGet(`https://graph.microsoft.com/beta/me/joinedTeams`)).value.map(x => x.id);
var allTeams= (await this.graphGet(`https://graph.microsoft.com/beta/groups?$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&$top=999`)).value;
var publicTeamIds:Array<string> = allTeams.filter(team => team.visibility === 'Public').map(x => x.id);
var missingTeamIds:Array<string> = difference(publicTeamIds, joinedTeamIds);
let missingTeams = JSON.parse('{"value": []}');
missingTeamIds.forEach(teamId => {
var team = find(allTeams, {'id': teamId});
missingTeams['value'].push(team);
});
return missingTeams.value;
}
protected async renderTeams(teamsToShow) {
const container: Element = this.domElement.querySelector('#container');
var userId:string = (await this.graphGet("me")).id;
for (var team of teamsToShow)
{
var row:HTMLDivElement = document.createElement("div");
row.className = styles.row;
container.appendChild(row);
var button:HTMLButtonElement = document.createElement("button");
button.id = team.id;
button.innerHTML = "Join";
button.onclick = (e:Event) => this.addMember(e.srcElement, userId);
row.appendChild(button);
var span:HTMLSpanElement = document.createElement("span");
span.innerHTML = ` ${team.displayName}`;
row.appendChild(span);
}
}
protected addMember(source:Element, userId:string) {
var button:HTMLButtonElement = <HTMLButtonElement>source;
button.disabled = true;
button.innerText = "Joined!";
var body:string = `{"@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/${userId}"}`;
this.graphPost(`https://graph.microsoft.com/v1.0/groups/${button.id}/members/$ref`, body);
}
protected async graphGet(url: string, value?:Array<string>) {
return await this.callGraph(url, "GET");
}
protected async graphPost(url: string, body:string) {
return await this.callGraph(url, "POST", body);
}
protected async callGraph(url: string, method:string, body?:string): Promise<any> {
const graphClient:MSGraphClient = await this.context.msGraphClientFactory.getClient();
var response;
if (method.toUpperCase() == "GET") {
response = await graphClient.api(url).get();
}
if (method.toUpperCase() == "POST") {
response = await graphClient.api(url).post(body);
}
return response;
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('title', {
label: strings.TitleFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Lists all the public teams the current user is not a member of, and displays a button for joining.",
"BasicGroupName": "Group Name",
"TitleFieldLabel": "Title"
}
});

View File

@ -0,0 +1,10 @@
declare interface IPublicUnjoinedTeamsWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
TitleFieldLabel: string;
}
declare module 'PublicUnjoinedTeamsWebPartStrings' {
const strings: IPublicUnjoinedTeamsWebPartStrings;
export = strings;
}

View File

@ -1,8 +1,8 @@
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.2/MicrosoftTeams.schema.json",
"manifestVersion": "1.2",
"packageName": "CalendarFeedSummary",
"id": "0459b501-da31-43c7-9a9a-d5c59cc2d667",
"packageName": "PublicUnjoinedTeams",
"id": "62eb4ba8-6014-4bd8-a428-097ff64f01c6",
"version": "0.1",
"developer": {
"name": "SPFx + Teams Dev",
@ -11,11 +11,11 @@
"termsOfUseUrl": "https://www.microsoft.com/en-us/servicesagreement"
},
"name": {
"short": "CalendarFeedSummary"
"short": "PublicUnjoinedTeams"
},
"description": {
"short": "Shows a summary view of a list of calendar events retrieved from an external feed.",
"full": "Shows a summary view of a list of calendar events retrieved from an external feed."
"short": "Allows users to join public Teams they are not yet members of.",
"full": "Allows users to join public Teams they are not yet members of."
},
"icons": {
"outline": "tab20x20.png",
@ -24,7 +24,7 @@
"accentColor": "#004578",
"configurableTabs": [
{
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=0459b501-da31-43c7-9a9a-d5c59cc2d667",
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=62eb4ba8-6014-4bd8-a428-097ff64f01c6",
"canUpdateConfiguration": false,
"scopes": [
"team"

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 933 B

After

Width:  |  Height:  |  Size: 933 B

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -16,8 +16,7 @@
],
"types": [
"es6-promise",
"webpack-env",
"sharepoint"
"webpack-env"
],
"lib": [
"es5",

View File

@ -2,7 +2,7 @@
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.7.0",
"version": "1.8.0",
"libraryName": "skype-presence-spfx",
"libraryId": "fcfeb3e4-bf84-4405-81c1-9c8f5dbaabe6",
"packageManager": "npm",

View File

@ -28,6 +28,7 @@ js-skype-status|[Vincent Biret](https://github.com/baywet)
Version|Date|Comments
-------|----|--------
1.4|March 27th, 2019|Upgrade to SPFx 1.8.0
1.3|November 18th, 2018|Upgrade to SPFx 1.7.0
1.2|July 4th, 2018|Fixed a bug when subscribing to the current user's status
1.1|June 22nd, 2018|Upgraded to SPFX 1.5

File diff suppressed because it is too large Load Diff

View File

@ -11,19 +11,20 @@
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.7.0",
"@microsoft/sp-lodash-subset": "1.7.0",
"@microsoft/sp-office-ui-fabric-core": "1.7.0",
"@microsoft/sp-webpart-base": "1.7.0",
"@microsoft/sp-core-library": "1.8.0",
"@microsoft/sp-lodash-subset": "1.8.0",
"@microsoft/sp-office-ui-fabric-core": "1.8.0",
"@microsoft/sp-webpart-base": "1.8.0",
"@types/es6-promise": "0.0.33",
"@types/webpack-env": "1.13.1",
"jquery": "^3.3.1"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.7.0",
"@microsoft/sp-module-interfaces": "1.7.0",
"@microsoft/sp-tslint-rules": "1.7.0",
"@microsoft/sp-webpart-workbench": "1.7.0",
"@microsoft/rush-stack-compiler-2.7": "0.4.0",
"@microsoft/sp-build-web": "1.8.0",
"@microsoft/sp-module-interfaces": "1.8.0",
"@microsoft/sp-tslint-rules": "1.8.0",
"@microsoft/sp-webpart-workbench": "1.8.0",
"@types/chai": "3.4.34",
"@types/jquery": "^3.3.4",
"@types/mocha": "2.2.38",

View File

@ -12,6 +12,7 @@
// 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

View File

@ -1,9 +1,6 @@
import { Version } from "@microsoft/sp-core-library";
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from "@microsoft/sp-webpart-base";
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
import { IPropertyPaneConfiguration, PropertyPaneTextField } from "@microsoft/sp-property-pane";
import { escape } from "@microsoft/sp-lodash-subset";
import * as jquery from "jquery";

View File

@ -1,4 +1,5 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-2.7/includes/tsconfig-web.json",
"include": [
"src/**/*.ts"
],
@ -14,6 +15,9 @@
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"inlineSources": false,
"noUnusedLocals": false,
"strictNullChecks": false,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,

View File

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

32
samples/js-theme-manager/.gitignore vendored Normal file
View File

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

View File

@ -0,0 +1,11 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.7.1",
"libraryName": "js-theme-manager",
"libraryId": "555147fb-b773-446f-aef2-1b25001f92d9",
"packageManager": "npm",
"componentType": "webpart"
}
}

View File

@ -0,0 +1,72 @@
# Modern Experience Theme Manager
## Summary
This sample web part provides a user interface for creating, updating, deleting and applying a Modern Experience SharePoint theme in SharePoint Online.
The Theme Palette can be generated using the UI Fabric Theme Generator at: https://developer.microsoft.com/en-us/fabric#/styles/themegenerator
<h2>The following four features are available within this sample:</h2>
<b>Create a theme:</b><br>
Using a provided theme name and theme color palette a Modern Experience them is created and available at the tenant level.
![preview](./assets/create-a-theme.png)
<b>Update a theme:</b><br>
By selecting a pre-existing theme from the dropdown, the theme at the tenant level will be updated with the palette provided in the Theme Palette texbox.
![preview](./assets/update-a-theme.png)
<b>Delete a theme:</b><br>
By selecting a pre-existing theme from the dropdown, the theme will be deleted from the tenant level.
![preview](./assets/delete-a-theme.png)
<b>Appply a theme:</b><br>
By providing a Site Collection URL, along with a theme name and palette, the theme will be applied to the Site Collection directly without being added to the tenant Company Theme options.<br>
NOTE: This is a great option to provide theme management of a Site Collection without adding a theme to the "Company Themes" choices within the "Change the Look" options at the tenant level. The web part could be added to a Site Collection App Catalog to ensure availability of the web part is only available to those approved for theme management.
![preview](./assets/apply-a-theme.png)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/drop-1.7.1-orange.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)
--------|---------
js-theme-manager | David Warner II ([@DavidWarnerII](https://twitter.com/davidwarnerii) / [Warner Digital](http://warner.digital))
js-theme-manager | Beau Cameron ([@Beau__Cameron](https://twitter.com/@Beau__Cameron) / [Blog](https://beaucameron.net/))
## Version history
Version|Date|Comments
-------|----|--------
1.0|February 27, 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`
## Features
This Web Part illustrates the following concepts on top of the SharePoint Framework:
- Using the SharePoint Online REST API to manage Modern Experience Themes
## Additional Information:
- [Office UI Fabric Theme Palette Generator](https://developer.microsoft.com/en-us/fabric#/styles/themegenerator)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/js-theme-manager" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 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": {
"modern-theme-manager-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/modernThemeManager/ModernThemeManagerWebPart.js",
"manifest": "./src/webparts/modernThemeManager/ModernThemeManagerWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"ModernThemeManagerWebPartStrings": "lib/webparts/modernThemeManager/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

@ -2,6 +2,6 @@
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "react-search-refiners",
"container": "js-theme-manager",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,12 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "js-theme-manager-client-side-solution",
"id": "555147fb-b773-446f-aef2-1b25001f92d9",
"version": "1.0.0.0",
"includeClientSideAssets": true
},
"paths": {
"zippedPackage": "solution/js-theme-manager.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);

View File

@ -0,0 +1,31 @@
{
"name": "js-theme-manager",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.7.1",
"@microsoft/sp-webpart-base": "1.7.1",
"@microsoft/sp-lodash-subset": "1.7.1",
"@microsoft/sp-office-ui-fabric-core": "1.7.1",
"@types/webpack-env": "1.13.1",
"@types/es6-promise": "0.0.33"
},
"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",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2"
}
}

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": "11a36ff0-b026-4860-9e92-8e3fa283ad0f",
"alias": "ModernThemeManagerWebPart",
"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": "Modern Theme Manager" },
"description": { "default": "Modern Theme Manager description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "Modern Theme Manager"
}
}]
}

View File

@ -0,0 +1,120 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.jsGenericWebpartThemeGenerator {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
border:0px solid;
}
.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;
}
.input {
display: block;
width:300px;
}
.textarea {
display: block;
width:300px;
height:255px;
margin-bottom: 20px;
}
.siteurl {
margin-bottom: 20px;
width:300px;
}
#availableThemesSelect {
display: inline;
}
.hide{
display:none !important;
}
.show {
display: inherit;
}
.genericWrapper {
display: block;
}
#themeSelectWrapper,
#themeNameWrapper,
#themePaletteWrapper,
#themeSiteURLWrapper {
margin-left:25px;
}
.themeGeneratorURL {
color: "[theme: themePrimary, default: #fff]";
}
.radio {
cursor: pointer;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
margin-top: 10px;
margin-left:25px;
.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,407 @@
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';
import styles from './ModernThemeManagerWebPart.module.scss';
import * as strings from 'ModernThemeManagerWebPartStrings';
import { IDigestCache, DigestCache } from '@microsoft/sp-http';
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http';
export interface IModernThemeManagerWebPartProps {
description: string;
}
export default class ModernThemeManagerWebPart extends BaseClientSideWebPart<IModernThemeManagerWebPartProps> {
public render(): void {
this.domElement.innerHTML = `
<div class="${ styles.jsGenericWebpartThemeGenerator}">
<div class="${ styles.container}">
<div class="${ styles.row}">
<div class="${ styles.column}">
<div class="${ styles.title}">Modern Experience SharePoint Theme Manager</div>
<a class="${ styles.themeGeneratorURL}" href="https://developer.microsoft.com/en-us/fabric#/styles/themegenerator" target="_blank">
UI Fabric Theme Generator
</a>
<div>
<p class="${ styles.subTitle}">Theme Actions</p>
<label class="${ styles.radio}"><input type="radio" name="themeAction" value="create"> Create a theme</label>
<label class="${ styles.radio}"><input type="radio" name="themeAction" value="update"> Update a theme</label>
<label class="${ styles.radio}"><input type="radio" name="themeAction" value="delete"> Delete a theme</label>
<label class="${ styles.radio}"><input type="radio" name="themeAction" value="apply"> Apply a theme</label>
</div>
<div class="${ styles.hide} ${styles.genericWrapper}" id="${styles.themeSelectWrapper}">
<p class="${ styles.subTitle}">Available Themes</p>
<select id="${ styles.availableThemesSelect}" name="availableThemes">
</select>
</div>
<div class="${ styles.hide} ${styles.genericWrapper}" id="${styles.themeNameWrapper}">
<p class="${ styles.subTitle}">Theme Name</p>
<div>
<input id="${ styles.input}" class="${styles.input}">
</div>
</div>
<div class="${ styles.hide} ${styles.genericWrapper}" id="${styles.themePaletteWrapper}">
<p class="${ styles.subTitle}">Theme Palette</p>
<div>
<textarea id="${ styles.textarea}" class="${styles.textarea}"></textarea>
</div>
</div>
<div class="${ styles.hide} ${styles.genericWrapper}" id="${styles.themeSiteURLWrapper}">
<p class="${ styles.subTitle}">Relative Site URL (ex: "/sites/SiteCollectionName")</p>
<div>
<input id="${ styles.siteurl}" class="${styles.siteurl}">
</div>
</div>
<div id="createTheme" class="${ styles.button} ${styles.hide}">
<span class="${ styles.label}">Create Theme</span>
</div>
<div id="updateTheme" class="${ styles.button} ${styles.hide}">
<span class="${ styles.label}">Update Theme</span>
</div>
<div id="deleteTheme" class="${ styles.button} ${styles.hide}">
<span class="${ styles.label}">Delete Theme</span>
</div>
<div id="applyTheme" class="${ styles.button} ${styles.hide}">
<span class="${ styles.label}">Apply Theme</span>
</div>
</div>
</div>
</div>
</div>`;
this.setupClickEvent();
}
/***** *****
Create event listeners for Radio & Buttons
***** *****/
public setupClickEvent(): void {
let btnCreateTheme = document.getElementById("createTheme");
btnCreateTheme.addEventListener("click", (e: Event) => this.createTheme());
let btnUpdateTheme = document.getElementById("updateTheme");
btnUpdateTheme.addEventListener("click", (e: Event) => this.updateTheme());
let btnDeleteTheme = document.getElementById("deleteTheme");
btnDeleteTheme.addEventListener("click", (e: Event) => this.deleteTheme());
let btnApplyTheme = document.getElementById("applyTheme");
btnApplyTheme.addEventListener("click", (e: Event) => this.applyThemeNew());
let radioThemeActions = document.getElementsByName("themeAction");
let parent = this;
for (var i = 0, max = radioThemeActions.length; i < max; i++) {
radioThemeActions[i].onclick = function () {
let selectedValue = (<HTMLInputElement>this).value;
if (selectedValue == 'delete') {
parent.displayDeleteOptions();
}
else if (selectedValue == 'create') {
parent.displayCreateOptions();
}
else if (selectedValue == 'update') {
parent.displayUpdateOptions();
}
else if (selectedValue == 'apply') {
parent.displayApplyOptions();
}
};
};
}
/***** *****
Hide All Wrappers:
Generic method for hiding all of the form elements
***** *****/
public hideAllWrappers(): void {
// Hide any other elements that might have been displayed
document.getElementById(styles.themeNameWrapper).classList.add(styles.hide);
document.getElementById(styles.themePaletteWrapper).classList.add(styles.hide);
let wrappers = document.getElementsByClassName(styles.genericWrapper);
for (let i = 0; i < wrappers.length; i++) {
wrappers[i].classList.add(styles.hide);
}
let buttons = document.getElementsByClassName(styles.button);
for (let i = 0; i < buttons.length; i++) {
buttons[i].classList.add(styles.hide);
}
}
/***** *****
Display Update Options:
This method is used to display the form elements for the Theme Update Options.
***** *****/
public displayUpdateOptions(): void {
// Hide all wrappers
this.hideAllWrappers();
this.populateExistingThemes("/_api/thememanager/GetTenantThemingOptions", {}).then((success: boolean) => {
if (success) {
// Display the dropdown.
document.getElementById(styles.themeSelectWrapper).classList.remove(styles.hide);
document.getElementById(styles.themePaletteWrapper).classList.remove(styles.hide);
document.getElementById('updateTheme').classList.remove(styles.hide);
}
})
};
/***** *****
Display Create Options:
This method is used to display the form elements for the Theme Creation Options.
***** *****/
public displayCreateOptions(): void {
// Hide all wrappers
this.hideAllWrappers();
// Display the dropdown.
document.getElementById(styles.themeNameWrapper).classList.remove(styles.hide);
document.getElementById(styles.themePaletteWrapper).classList.remove(styles.hide);
document.getElementById('createTheme').classList.remove(styles.hide);
}
/***** *****
Display Delete Options:
This method is used to display the form elements for the Theme Deletion Options.
***** *****/
public displayDeleteOptions(): void {
// Hide all wrappers
this.hideAllWrappers();
this.populateExistingThemes("/_api/thememanager/GetTenantThemingOptions", {}).then((success: boolean) => {
if (success) {
// Display the dropdown.
document.getElementById(styles.themeSelectWrapper).classList.remove(styles.hide);
document.getElementById('deleteTheme').classList.remove(styles.hide);
}
});
}
/***** *****
Display Apply Options:
This method is used to display the form elements for the Theme Apply Options.
***** *****/
public displayApplyOptions(): void {
// Hide all wrappers
this.hideAllWrappers();
// Display the dropdown.
document.getElementById(styles.themeNameWrapper).classList.remove(styles.hide);
document.getElementById(styles.themePaletteWrapper).classList.remove(styles.hide);
document.getElementById(styles.themeSiteURLWrapper).classList.remove(styles.hide);
document.getElementById('applyTheme').classList.remove(styles.hide);
}
/***** *****
Populate Existing Themes:
This method retrieves the currently available themes in the tenant and inserts the values into the dropdown.
***** *****/
public populateExistingThemes(url, params): Promise<boolean> {
return this.context.spHttpClient.get("/_api/thememanager/GetTenantThemingOptions", SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
}).then((themeJSON: any) => {
// Clear the select
let themeSelect = <HTMLInputElement>document.getElementById(styles.availableThemesSelect);
themeSelect.innerHTML = "";
for (let i = 0, max = themeJSON.themePreviews.length; i < max; i++) {
let option = document.createElement("option");
option.text = themeJSON.themePreviews[i].name;
(<HTMLInputElement>themeSelect).appendChild(option);
}
return true;
});
}
/***** *****
Create new theme at tenant level:
Collects the data needed to create a new theme at the tenant level and passes it to the creation execution method.
***** *****/
public createTheme(): void {
// Gather the theme properties
let themeTitle: string = (<HTMLInputElement>document.getElementById(styles.input)).value;
let themePalette: JSON = JSON.parse((<HTMLInputElement>document.getElementById(styles.textarea)).value);
let themePaletteJSON = {
"palette": themePalette
};
// Pass the theme properties to themeManagerExecution method
this.themeManagerExecution(this.context.pageContext.site.serverRelativeUrl + "/_api/thememanager/AddTenantTheme", { name: themeTitle, themeJson: JSON.stringify(themePaletteJSON) })
.then((sucess: boolean) => {
if (sucess) {
//it worked
alert('The theme has been successfully created');
}
else {
//it didn't
alert('An error has occurred');
}
});
}
/***** *****
Deletes a theme at tenant level:
Collects the data needed to delete a theme at the tenant level and passes it to the deletion execution method.
***** *****/
public deleteTheme(): void {
// Gather the theme properties
let themeTitle: string = (<HTMLInputElement>document.getElementById(styles.availableThemesSelect)).value;
// Setup the success message
let successMessage: string = 'The theme has been successfully deleted';
// Pass the theme properties to themeManagerExecution method
this.themeManagerExecution(this.context.pageContext.site.serverRelativeUrl + "/_api/thememanager/DeleteTenantTheme", { name: themeTitle })
.then((sucess: boolean) => {
if (sucess) {
//it worked
alert('The theme has been successfully deleted');
}
else {
//it didn't
alert('An error has occurred');
}
});
}
/***** *****
Updates a theme at tenant level:
Collects the data needed to update a theme at the tenant level and passes it to the update execution method.
***** *****/
public updateTheme(): void {
// Gather the theme properties
let themeTitle: string = (<HTMLInputElement>document.getElementById(styles.availableThemesSelect)).value;
let themePalette: JSON = JSON.parse((<HTMLInputElement>document.getElementById(styles.textarea)).value);
let themePaletteJSON = {
"palette": themePalette
}
// Pass the theme properties to themeManagerExecution method
this.themeManagerExecution(this.context.pageContext.site.serverRelativeUrl + "/_api/thememanager/UpdateTenantTheme", { name: themeTitle, themeJson: JSON.stringify(themePaletteJSON) })
.then((sucess: boolean) => {
if (sucess) {
//it worked
alert('The theme has been successfully updated');
}
else {
//it didn't
alert('An error has occurred');
}
});
}
/***** *****
Apply a theme to a site collection:
Collects the data needed to apply a theme directly to a site colleciton.
NOTE: This does NOT create a theme choice at the tenant level. It will directly apply the theme to a site collection.
***** *****/
public applyThemeNew(): void {
// Gather the theme properties
let themeURL: string = (<HTMLInputElement>document.getElementById(styles.siteurl)).value;
let themeTitle: string = (<HTMLInputElement>document.getElementById(styles.input)).value;
let themePalette: JSON = JSON.parse((<HTMLInputElement>document.getElementById(styles.textarea)).value);
let themePaletteJSON = {
"palette": themePalette
}
const digestCache: IDigestCache = this.context.serviceScope.consume(DigestCache.serviceKey);
digestCache.fetchDigest(themeURL).then((digest: string): void => {
// Pass the theme properties to themeManagerExecution method
this.themeManagerExecution(themeURL + "/_api/thememanager/ApplyTheme", { name: themeTitle, themeJson: JSON.stringify(themePaletteJSON) })
.then((sucess: boolean) => {
if (sucess) {
//it worked
alert('The theme has been successfully applied');
}
else {
//it didn't
alert('An error has occurred');
}
});
});
}
/***** *****
Generic method for creating, updating, deleting and applying a theme.
***** *****/
public themeManagerExecution(url: string, params: any): Promise<boolean> {
let options: ISPHttpClientOptions = {
body: JSON.stringify(params)
}
return this.context.spHttpClient.post(url, SPHttpClient.configurations.v1, options)
.then((response: SPHttpClientResponse) => {
return response.ok
});
}
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,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface IModernThemeManagerWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'ModernThemeManagerWebPartStrings' {
const strings: IModernThemeManagerWebPartStrings;
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
}
}

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,11 @@
{
"@microsoft/generator-sharepoint": {
"packageManager": "pnpm",
"isCreatingSolution": true,
"environment": "spo",
"version": "1.7.1",
"libraryName": "workbench-customizer",
"libraryId": "5d6f4a5a-9d2b-4a93-a283-16b8f5ea75d6",
"componentType": "webpart"
}
}

View File

@ -0,0 +1,47 @@
# Workbench customizer
## Summary
This sample shows how the Workbench page can be customized to display in a way that better mimics a modern SharePoint page.
This is done using CSS overrides on some of the page styles, which does not cause any negative impact on your site as the web part is not intended to be consumed by final users, only developers.
The web part also has some properties that control which customizations are applied to the workbench page (all enabled by default).
![Demo](./assets/Preview.png)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/drop-1.7.1-green.svg)
## Applies to
* [SharePoint Framework](https:/dev.office.com/sharepoint)
## Prerequisites
* Office 365 subscription with SharePoint Online licence
* SharePoint Framework [development environment](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment) already set up.
## Solution
Solution|Author(s)
--------|---------
workbench-customizer|Joel Rodrigues
## Version history
Version|Date|Comments
-------|----|--------
1.0|January 24, 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`

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 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": {
"workbench-customizer-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/workbenchCustomizer/WorkbenchCustomizerWebPart.js",
"manifest": "./src/webparts/workbenchCustomizer/WorkbenchCustomizerWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"WorkbenchCustomizerWebPartStrings": "lib/webparts/workbenchCustomizer/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": "workbench-customizer",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "workbench-customizer-client-side-solution",
"id": "5d6f4a5a-9d2b-4a93-a283-16b8f5ea75d6",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true
},
"paths": {
"zippedPackage": "solution/workbench-customizer.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,32 @@
{
"name": "workbench-customizer",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.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",
"@types/es6-promise": "0.0.33",
"@types/webpack-env": "1.13.1",
"npm": "^6.7.0"
},
"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",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2"
}
}

File diff suppressed because it is too large Load Diff

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,30 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "19882113-30db-44bd-8ed7-15c951fc2323",
"alias": "WorkbenchCustomizerWebPart",
"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": "Workbench Customizer" },
"description": { "default": "Workbench Customizer web part to configure workbench page styles" },
"officeFabricIconFontName": "DeveloperTools",
"properties": {
"requiresPageRefresh": false,
"maxWidth": true,
"centerCanvas": true,
"overflow": true,
"padding": true
}
}]
}

View File

@ -0,0 +1,6 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.workbenchCustomizer {
.redMessage {
color: red;
}
}

View File

@ -0,0 +1,97 @@
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneToggle
} from '@microsoft/sp-webpart-base';
import styles from './WorkbenchCustomizerWebPart.module.scss';
import { escape } from '@microsoft/sp-lodash-subset';
import * as strings from 'WorkbenchCustomizerWebPartStrings';
export interface IWorkbenchCustomizerWebPartProps {
requiresPageRefresh: boolean;
maxWidth: boolean;
centerCanvas: boolean;
overflow: boolean;
padding: boolean;
}
export default class WorkbenchCustomizerWebPart extends BaseClientSideWebPart<IWorkbenchCustomizerWebPartProps> {
public onInit(): Promise<void> {
this.properties.requiresPageRefresh = false;
return Promise.resolve();
}
public async render(): Promise<void> {
if (this.properties.maxWidth) {
await import('./styles/maxWidth.module.scss');
}
if (this.properties.centerCanvas) {
await import('./styles/centerCanvas.module.scss');
}
if (this.properties.overflow) {
await import('./styles/overflow.module.scss');
}
if (this.properties.padding) {
await import('./styles/padding.module.scss');
}
this.domElement.innerHTML = `
<div class="${styles.workbenchCustomizer}">
${this.properties.requiresPageRefresh
? `<div class="${styles.redMessage}">Please refresh the page to update workbench styles</div>`
: ''
}
<div>Max width enabled: ${this.properties.maxWidth}</div>
<div>Center canvas zone: ${this.properties.centerCanvas}</div>
<div>Custom overflow enabled: ${this.properties.overflow}</div>
<div>Custom padding enabled: ${this.properties.padding}</div>
</div>`;
}
public onPropertyPaneFieldChanged(path: string, oldValue: any, newValue: any): void {
if (!newValue) {
// request a page refresh if any of the options was disabled
// no real smart logic implemented at this point
this.properties.requiresPageRefresh = true;
}
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneToggle('maxWidth', {
label: strings.MaxWidthFieldLabel
}),
PropertyPaneToggle('centerCanvas', {
label: strings.CenterCanvasFieldLabel
}),
PropertyPaneToggle('overflow', {
label: strings.OverflowFieldLabel
}),
PropertyPaneToggle('padding', {
label: strings.PaddingFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,10 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Configuration",
"MaxWidthFieldLabel": "Enable custom max width",
"CenterCanvasFieldLabel": "Center canvas",
"OverflowFieldLabel": "Enable custom overflow",
"PaddingFieldLabel": "Enable custom padding"
}
});

View File

@ -0,0 +1,13 @@
declare interface IWorkbenchCustomizerWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
MaxWidthFieldLabel: string;
CenterCanvasFieldLabel: string;
OverflowFieldLabel: string;
PaddingFieldLabel: string;
}
declare module 'WorkbenchCustomizerWebPartStrings' {
const strings: IWorkbenchCustomizerWebPartStrings;
export = strings;
}

Some files were not shown because too many files have changed in this diff Show More