Merge pull request #1 from SharePoint/master
Merging latest from SharePoint/sp-dev-fx-webparts
|
@ -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" />
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -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 -->"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -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);
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<div>{{ message }}</div>
|
|
@ -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() { }
|
||||
}
|
10
samples/angularelements-html-templatefile/src/webparts/helloAngularTemplate/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare interface IHelloAngularWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'HelloAngularWebPartStrings' {
|
||||
const strings: IHelloAngularWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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" />
|
After Width: | Height: | Size: 48 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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 -->"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
|
@ -0,0 +1,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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
});
|
10
samples/js-public-unjoined-teams/src/webparts/publicUnjoinedTeams/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare interface IPublicUnjoinedTeamsWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
TitleFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'PublicUnjoinedTeamsWebPartStrings' {
|
||||
const strings: IPublicUnjoinedTeamsWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -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"
|
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 933 B |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
@ -16,8 +16,7 @@
|
|||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env",
|
||||
"sharepoint"
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,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"
|
||||
}
|
||||
}
|
|
@ -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" />
|
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 34 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -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 -->"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
|
@ -0,0 +1,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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "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"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface IModernThemeManagerWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'ModernThemeManagerWebPartStrings' {
|
||||
const strings: IModernThemeManagerWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,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"
|
||||
}
|
||||
}
|
|
@ -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`
|
After Width: | Height: | Size: 178 KiB |
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "workbench-customizer",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
|
@ -0,0 +1,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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -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
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
.workbenchCustomizer {
|
||||
.redMessage {
|
||||
color: red;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
});
|
13
samples/js-workbench-customizer/src/webparts/workbenchCustomizer/loc/mystrings.d.ts
vendored
Normal 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;
|
||||
}
|