Merge branch 'dev'

This commit is contained in:
VesaJuvonen 2019-12-09 11:14:03 +02:00
commit b2944cfb9f
136 changed files with 77023 additions and 0 deletions

View File

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

View File

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

View File

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

View File

@ -0,0 +1,43 @@
{
/**
* Install Chrome Debugger Extension for Visual Studio Code to debug your components with the
* Chrome browser: https://aka.ms/spfx-debugger-extensions
*/
"version": "0.2.0",
"configurations": [{
"name": "Local workbench",
"type": "chrome",
"request": "launch",
"url": "https://localhost:4321/temp/workbench.html",
"webRoot": "${workspaceRoot}",
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///.././src/*": "${webRoot}/src/*",
"webpack:///../../../src/*": "${webRoot}/src/*",
"webpack:///../../../../src/*": "${webRoot}/src/*",
"webpack:///../../../../../src/*": "${webRoot}/src/*"
},
"runtimeArgs": [
"--remote-debugging-port=9222"
]
},
{
"name": "Hosted workbench",
"type": "chrome",
"request": "launch",
"url": "https://enter-your-SharePoint-site/_layouts/workbench.aspx",
"webRoot": "${workspaceRoot}",
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///.././src/*": "${webRoot}/src/*",
"webpack:///../../../src/*": "${webRoot}/src/*",
"webpack:///../../../../src/*": "${webRoot}/src/*",
"webpack:///../../../../../src/*": "${webRoot}/src/*"
},
"runtimeArgs": [
"--remote-debugging-port=9222",
"-incognito"
]
}
]
}

View File

@ -0,0 +1,13 @@
// Place your settings in this file to overwrite default and user settings.
{
// Configure glob patterns for excluding files and folders in the file explorer.
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true,
"**/bower_components": true,
"**/coverage": true,
"**/lib-amd": true,
"src/**/*.scss.ts": true
},
"typescript.tsdk": ".\\node_modules\\typescript\\lib"
}

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.9.1",
"libraryName": "react-admin-sc-catalog-pnpjs",
"libraryId": "59954bf3-e8a7-4def-933b-c1b618e0ab19",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,61 @@
# react-admin-sc-catalog-pnpjs - Site Collection App Catalogs Summary View.
## Summary
A SPFx WebPart using [@pnp/sp/appcatalog](https://pnp.github.io/pnpjs/sp/docs/alm/) and [@pnp/spfx-controls-react](https://sharepoint.github.io/sp-dev-fx-controls-react/). It allows to see in a single view all the SiteCollection catalogs and the Apps installed with some useful metadata.
It needs Globlal Administrator or SharePoint Online Administrator permissions in order to access Site collection App Catalogs hidden list (https://yourtenant.sharepoint.com/sites/appcatalog/Lists/SiteCollectionAppCatalogs) and each Site Collection App Catalog. This WebPart use [@pnp/spfx-controls-react WebPartTitle](https://sharepoint.github.io/sp-dev-fx-controls-react/controls/WebPartTitle/) and [@pnp/spfx-controls-react ListView](https://sharepoint.github.io/sp-dev-fx-controls-react/controls/ListView/) components.
## react-admin-sc-catalog-pnpjs in action
![WebPartInAction](./assets/react-admin-sc-catalog-pnpjs-webpart-animated.gif)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
## Applies to
* [SharePoint Framework](https:/dev.office.com/sharepoint)
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
## Solution
Solution|Author(s)
--------|---------
react-admin-sc-catalog-pnpjs | [Federico Porceddu](https://www.federicoporceddu.com)
## Version history
Version|Date|Comments
-------|----|--------
1.0|November 07, 2019|Initial release
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
---
## Minimal Path to Awesome
* Clone this repository
* in the command line run:
* restore dependencies: `npm install`
* build solution `gulp build --ship`
* bundle solution: `gulp bundle --ship`
* package solution: `gulp package-solution --ship`
* locate solution at `.\sharepoint\solution\react-admin-sc-catalog-pnpjs.sppkg`
* upload it to your tenant app catalog
* add `react-admin-sc-catalog-pnpjs` app to your site
* add `react-admin-sc-catalog-pnpjs` webpart to your page to see it in action
## Features
This Web Part illustrates the following concepts on top of the SharePoint Framework:
* How to use [@pnp/sp/appcatalog](https://pnp.github.io/pnpjs/sp/docs/alm/).
* How to use [@pnp/spfx-controls-react WebPartTitle](https://sharepoint.github.io/sp-dev-fx-controls-react/controls/WebPartTitle/)
* How to use [@pnp/spfx-controls-react ListView](https://sharepoint.github.io/sp-dev-fx-controls-react/controls/ListView/)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/react-admin-sc-catalog-pnpjs" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -0,0 +1,19 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"site-collection-catalog-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/siteCollectionCatalog/SiteCollectionCatalogWebPart.js",
"manifest": "./src/webparts/siteCollectionCatalog/SiteCollectionCatalogWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"SiteCollectionCatalogWebPartStrings": "lib/webparts/siteCollectionCatalog/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
}
}

View File

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

View File

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

View File

@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-admin-sc-catalog-pnpjs-client-side-solution",
"id": "59954bf3-e8a7-4def-933b-c1b618e0ab19",
"version": "2.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/react-admin-sc-catalog-pnpjs.sppkg"
}
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
{
"name": "react-admin-sc-catalog-pnpjs",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.9.1",
"@microsoft/sp-lodash-subset": "1.9.1",
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
"@microsoft/sp-webpart-base": "1.9.1",
"@pnp/common": "^1.3.6",
"@pnp/graph": "^1.3.6",
"@pnp/logging": "^1.3.6",
"@pnp/odata": "^1.3.6",
"@pnp/sp": "^1.3.6",
"@pnp/spfx-controls-react": "1.15.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"resolutions": {
"@types/react": "16.8.8"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.9.1",
"@microsoft/sp-tslint-rules": "1.9.1",
"@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1",
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2"
}
}

View File

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

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "87cc523d-1f83-4486-a11d-cf78a7c8c749",
"alias": "SiteCollectionCatalogWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "SiteCollectionCatalog" },
"description": { "default": "SiteCollectionCatalog description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "SiteCollectionCatalog"
}
}]
}

View File

@ -0,0 +1,80 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version, DisplayMode } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import * as strings from 'SiteCollectionCatalogWebPartStrings';
import SiteCollectionCatalog from './components/SiteCollectionCatalog';
import { sp } from "@pnp/sp";
export interface ISiteCollectionCatalogWebPartProps {
description: string;
title: string;
displayMode: DisplayMode;
updateProperty: (value: string) => void;
}
export default class SiteCollectionCatalogWebPart extends BaseClientSideWebPart<ISiteCollectionCatalogWebPartProps> {
public onInit(): Promise<void> {
return super.onInit().then(_ => {
// other init code may be present
sp.setup({
spfxContext: this.context
});
});
}
public render(): void {
const element: React.ReactElement<ISiteCollectionCatalogWebPartProps> = React.createElement(
SiteCollectionCatalog,
{
description: this.properties.description,
title: this.properties.title,
displayMode: this.displayMode,
updateProperty: (value: string) => {
this.properties.title = value;
}
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,5 @@
export interface ISiteCollectionCatalogState {
siteAppCatalogs: any[];
}

View File

@ -0,0 +1,74 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.siteCollectionCatalog {
.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,118 @@
import * as React from 'react';
import styles from './SiteCollectionCatalog.module.scss';
import { ISiteCollectionCatalogState } from './ISiteCollectionCatalogProps';
import { SiteCollectionCatalogHelper } from './SiteCollectionCatalogHelper';
import { ListView, IViewField, SelectionMode } from "@pnp/spfx-controls-react/lib/ListView";
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import { ISiteCollectionCatalogWebPartProps } from '../SiteCollectionCatalogWebPart';
import { IColumn } from 'office-ui-fabric-react/lib/components/DetailsList';
const _viewFields: IViewField[] = [
{
name: "SiteTitle",
linkPropertyName: "SiteURL",
displayName: "Site Collection",
sorting: true,
minWidth: 120,
isResizable: true
},
{
name: "AppTitle",
displayName: "App Title",
sorting: true,
minWidth: 200,
isResizable: true,
render: (item: any) => {
var _color = "Red";
if (!item.NoApps) {
_color = "Green";
}
const element: any = React.createElement("span", { style: { color: _color } }, item.AppTitle);
return element;
}
},
{
name: "AppCatalogVersion",
displayName: "AppCatalogVersion",
sorting: true,
minWidth: 100
},
{
name: "Deployed",
displayName: "Deployed",
minWidth: 70,
render: (item: any) => {
var _color = "Red";
if (String(item.Deployed).toLowerCase() == "true") {
_color = "Green";
}
const element: any = React.createElement("span", { style: { color: _color } }, item.Deployed);
return element;
}
},
{
name: "InstalledVersion",
displayName: "Installed Version",
sorting: true,
minWidth: 100
},
{
name: "IsClientSideSolution",
displayName: "Is Client Side Solution",
minWidth: 150,
render: (item: any) => {
var _color = "Red";
if (String(item.IsClientSideSolution).toLowerCase() == "true") {
_color = "Green";
}
const element: any = React.createElement("span", { style: { color: _color } }, item.IsClientSideSolution);
return element;
}
}
];
export default class SiteCollectionCatalog extends React.Component<ISiteCollectionCatalogWebPartProps, ISiteCollectionCatalogState> {
constructor(props: ISiteCollectionCatalogWebPartProps) {
super(props);
this.state = {
siteAppCatalogs: []
};
}
public componentDidMount() {
var tmpRes: Promise<any[]> = SiteCollectionCatalogHelper.getSiteCollectionCatalogList();
tmpRes.then(res => {
this.setState({ siteAppCatalogs: res });
});
}
private _getSelection(items: any[]) {
console.log('Selected items:', items);
}
public render(): React.ReactElement<ISiteCollectionCatalogWebPartProps> {
return (
<div className={styles.siteCollectionCatalog}>
<WebPartTitle displayMode={this.props.displayMode}
title={this.props.title}
updateProperty={this.props.updateProperty} />
<ListView
items={this.state.siteAppCatalogs}
viewFields={_viewFields}
compact={true}
selectionMode={SelectionMode.none}
selection={this._getSelection}
showFilter={true}
filterPlaceHolder="Search..."
/>
</div>
);
}
}

View File

@ -0,0 +1,84 @@
import { sp, Site } from "@pnp/sp";
import { combine } from "@pnp/common";
export class SiteCollectionCatalogHelper {
public static async getSiteCollectionCatalogList(): Promise<SiteCatalogApps[]> {
let resultData = new Array<SiteCatalogApps>();
var tenantAppCatalogWeb = await sp.getTenantAppCatalogWeb();
let siteCollectionAppCatalogs = await tenantAppCatalogWeb.lists.getByTitle("Site Collection App Catalogs").items.get();
let data = await Promise.all(siteCollectionAppCatalogs.map(async siteCollCatalog => {
let siteUrl = siteCollCatalog["SiteCollectionUrl"];
let tmpSite = new Site(siteUrl);
let tmpCatalog = await tmpSite.rootWeb.getSiteCollectionAppCatalog(siteUrl);
let apps = await tmpCatalog.get();
let tmpWeb = (await (await tmpSite.getRootWeb()).select("Title").get());
if (apps.length == 0) {
let tmpApp = new SiteCatalogApps();
tmpApp = this.MapAppProps(tmpWeb, siteUrl, null, true);
resultData.push(tmpApp);
} else {
apps.forEach(app => {
let tmpApp = new SiteCatalogApps();
tmpApp = this.MapAppProps(tmpWeb, siteUrl, app, false);
resultData.push(tmpApp);
});
}
}));
return await resultData;
}
public static MapAppProps(tmpWeb: any, siteUrl: string, app: any, IsEmpty: boolean): SiteCatalogApps {
var tmpApp: SiteCatalogApps = new SiteCatalogApps();
if (IsEmpty) {
tmpApp.SiteTitle = tmpWeb["Title"];
tmpApp.SiteURL = combine(siteUrl, "/AppCatalog/");
tmpApp.AppTitle = "There are no apps Available ";
tmpApp.AppCatalogVersion = "-";
tmpApp.Deployed = "-";
tmpApp.InstalledVersion = "-";
tmpApp.IsEnabled = "-";
tmpApp.IsClientSideSolution = "-";
tmpApp.NoApps = true;
} else {
tmpApp.SiteTitle = tmpWeb["Title"];
tmpApp.SiteURL = combine(siteUrl, "/AppCatalog/");
tmpApp.AppTitle = app["Title"];
tmpApp.AppCatalogVersion = app["AppCatalogVersion"];
tmpApp.Deployed = String(app["Deployed"]);
tmpApp.InstalledVersion = app["InstalledVersion"];
tmpApp.IsEnabled = String(app["IsEnabled"]);
tmpApp.IsClientSideSolution = String(app["IsClientSideSolution"]);
tmpApp.NoApps = false;
}
return tmpApp;
}
}
export class SiteCatalogApps {
public SiteTitle: string;
public SiteURL: string;
public AppTitle: string;
public AppCatalogVersion: string;
public Deployed: string;
public InstalledVersion: string;
public IsEnabled: string;
public IsClientSideSolution: string;
public NoApps: boolean;
}

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 ISiteCollectionCatalogWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'SiteCollectionCatalogWebPartStrings' {
const strings: ISiteCollectionCatalogWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.8.2",
"libraryName": "pnpjs-project-online",
"libraryId": "ef683471-0607-4681-a03f-725a67b08014",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,59 @@
# React PnPjs Project Online
## Summary
This sample shows how to use SPFx to consume data from the Project Online REST API using a custom module for PnPjs.
The web part is currently logging the data returned from the API to the browser console as a simple proof of concept.
Custom PnPjs module: [pnpjs-project-online-package](https://www.npmjs.com/package/pnpjs-project-online-package)
![Demo](./assets/Preview.gif)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.8.2-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)
- [Project Online](<https://docs.microsoft.com/en-us/previous-versions/office/project-javascript-api/jj712612(v%3Doffice.15)>)
## Prerequisites
- Office 365 subscription with SharePoint Online and Project Online licence
- SharePoint Framework [development environment](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment) already set up.
- Project site with sample data available.
## Solution
| Solution | Author(s) |
| -------------------------- | -------------- |
| react-pnpjs-project-online | Joel Rodrigues |
## Version history
| Version | Date | Comments |
| ------- | ---------------- | --------------- |
| 1.0 | December 4, 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 PnPjs with a custom package to interact with Project Online REST API
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-pnpjs-project-online" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 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": {
"project-online-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/projectOnline/ProjectOnlineWebPart.js",
"manifest": "./src/webparts/projectOnline/ProjectOnlineWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"ProjectOnlineWebPartStrings": "lib/webparts/projectOnline/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": "pnpjs-project-online",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "pnpjs-project-online-client-side-solution",
"id": "ef683471-0607-4681-a03f-725a67b08014",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/pnpjs-project-online.sppkg"
}
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
{
"name": "pnpjs-project-online",
"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.8.2",
"@microsoft/sp-lodash-subset": "1.8.2",
"@microsoft/sp-office-ui-fabric-core": "1.8.2",
"@microsoft/sp-property-pane": "1.8.2",
"@microsoft/sp-webpart-base": "1.8.2",
"@pnp/common": "1.3.7",
"@pnp/logging": "1.3.7",
"@pnp/odata": "1.3.7",
"@types/es6-promise": "0.0.33",
"@types/react": "16.7.22",
"@types/react-dom": "16.8.0",
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "6.143.0",
"pnpjs-project-online-package": "0.0.2",
"react": "16.7.0",
"react-dom": "16.7.0"
},
"resolutions": {
"@types/react": "16.7.22"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.8.2",
"@microsoft/sp-tslint-rules": "1.8.2",
"@microsoft/sp-module-interfaces": "1.8.2",
"@microsoft/sp-webpart-workbench": "1.8.2",
"@microsoft/rush-stack-compiler-2.9": "0.7.7",
"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,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "0880bb0a-db38-4f66-97ca-1a15459d2967",
"alias": "ProjectOnlineWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "ProjectOnline" },
"description": { "default": "ProjectOnline description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "ProjectOnline"
}
}]
}

View File

@ -0,0 +1,75 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import {
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { setup as pnpSetup } from "@pnp/common";
import { project } from "pnpjs-project-online-package";
import * as strings from 'ProjectOnlineWebPartStrings';
import { ProjectOnline } from './components/ProjectOnline';
import { IProjectOnlineProps } from './components/ProjectOnline';
export interface IProjectOnlineWebPartProps {
description: string;
}
export default class ProjectOnlineWebPart extends BaseClientSideWebPart<IProjectOnlineWebPartProps> {
public onInit(): Promise<void> {
return super.onInit().then(_ => {
// other init code may be present
project.setup({
spfxContext: this.context
});
});
}
public render(): void {
const element: React.ReactElement<IProjectOnlineProps> = React.createElement(
ProjectOnline,
{
description: this.properties.description
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,9 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.calendars {
button {
margin: 5px;
}
}

View File

@ -0,0 +1,76 @@
import * as React from 'react';
import styles from './Calendars.module.scss';
import { ICalendarsProps } from './ICalendarsProps';
import { project, Calendar, CommandResult, CalendarCollection, CalendarExceptionCollection, CalendarException } from "pnpjs-project-online-package";
import { Button } from "office-ui-fabric-react/lib/Button";
import { dateAdd } from '@pnp/common';
export class Calendars extends React.Component<ICalendarsProps, {}> {
private _calendars: CalendarCollection[];
public render(): React.ReactElement<ICalendarsProps> {
return (
<div className={styles.calendars}>
<Button text='Get all calendars' onClick={this._getAllCalendars}></Button>
<Button text='Get calendar by Id' onClick={this._getCalendarById}></Button>
{/* <Button text='Add calendar' onClick={this._addCalendar}></Button> */}
{/* <Button text='Add calendar exception' onClick={this._addCalendarException}></Button> */}
<Button text='Copy calendar' onClick={this._copyCalendar}></Button>
<Button text='Delete calendar' onClick={this._deleteCalendar}></Button>
</div>
);
}
private _getAllCalendars = async () => {
this._calendars = await project.calendars.get();
console.log('Calendars', this._calendars);
}
// private _addCalendar = async () => {
// const calendar: CommandResult<Calendar> = await project.calendars.add({
// Name: 'Test Calendar ' + Date.now()
// });
// console.log(calendar);
// }
private _getCalendarById = async () => {
const calendar: Calendar = await project.calendars.getById('c421980e-457e-e911-b07c-00155d088c05').get();
console.log('Calendar', calendar);
const calendarExceptions: CalendarExceptionCollection[] = await project.calendars.getById('c421980e-457e-e911-b07c-00155d088c05').baseCalendarExceptions.get();
console.log('Calendar exceptions', calendarExceptions);
}
private _copyCalendar = async () => {
const newCalendarName = 'Test Calendar ' + Date.now();
const calendar: CommandResult<Calendar> = await project.calendars.getById('b6635b2e-e747-4771-a78b-24f7509629d0').copyTo(newCalendarName);
console.log('Calendar', calendar);
}
private _deleteCalendar = async () => {
await project.calendars.getById('1cdc823e-417e-e911-b08a-00155d10891f').delete();
console.log('Calendar deleted');
}
// private _addCalendarException = async () => {
// const calendarException: CommandResult<CalendarException> = await project.calendars.getById('c421980e-457e-e911-b07c-00155d088c05').baseCalendarExceptions.add({
// Name: 'Test Calendar exception ' + Date.now(),
// Start: new Date(2019,1,15),
// Finish: new Date(2019,12,15),
// Shift1Start: 1,
// Shift1Finish: 60,
// Shift2Start: 1,
// Shift2Finish: 60,
// Shift3Start: 1,
// Shift3Finish: 60,
// Shift4Start: 1,
// Shift4Finish: 60,
// Shift5Start: 1,
// Shift5Finish: 60,
// });
// console.log('Calendar exception', calendarException);
// }
}

View File

@ -0,0 +1,2 @@
export interface ICalendarsProps {
}

View File

@ -0,0 +1,2 @@
export * from './ICalendarsProps';
export * from './Calendars';

View File

@ -0,0 +1,3 @@
export interface IProjectAssignmentsProps {
projectId: string;
}

View File

@ -0,0 +1,9 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.buttons {
button {
margin: 5px;
}
}

View File

@ -0,0 +1,52 @@
import * as React from 'react';
import styles from './ProjectAssignments.module.scss';
import { IProjectAssignmentsProps } from './IProjectAssignmentsProps';
import { project, User, CustomFieldCollection, PublishedAssignmentCollection, Calendar, PublishedTaskLinkCollection, PublishedTask, PublishedAssignment, PublishedProjectResource } from "pnpjs-project-online-package";
import { Button } from "office-ui-fabric-react/lib/Button";
export class ProjectAssignments extends React.Component<IProjectAssignmentsProps, {}> {
public render(): React.ReactElement<IProjectAssignmentsProps> {
return (
<div className={styles.buttons}>
<Button text='Get all project assignments' onClick={this._getAllProjectAssignments}></Button>
<Button text='Get project assignment by Id' onClick={this._getProjectAssignmentById}></Button>
</div>
);
}
private _getAllProjectAssignments = async () => {
const projectAssignments: PublishedAssignmentCollection[] = await project.projects.getById(this.props.projectId).assignments.get();
console.log('Project assignments', projectAssignments);
}
private _getProjectAssignmentById = async () => {
const id = '56701829-2d77-e911-8166-000d3a6dc32c';
const publishedAssignment: PublishedAssignment = await project.projects.getById(this.props.projectId).assignments.getById(id).get();
console.log('Assignment', publishedAssignment);
const assignmentTask: PublishedTask = await project.projects.getById(this.props.projectId).assignments.getById(id).task.get();
console.log('Assignment Task', assignmentTask);
const assignmentResource: PublishedProjectResource = await project.projects.getById(this.props.projectId).assignments.getById(id).resource.get();
console.log('Assignment Resource', assignmentResource);
const assignmentOwner: PublishedTask = await project.projects.getById(this.props.projectId).assignments.getById(id).owner.get();
console.log('Assignment Owner', assignmentOwner);
const customFields: CustomFieldCollection[] = await project.projects.getById(this.props.projectId).assignments.getById(id).customFields.get();
console.log('Custom Fields', customFields);
const parentAssignment: PublishedAssignment = await project.projects.getById(this.props.projectId).assignments.getById(id).parent.get();
console.log('Parent Assignment', parentAssignment);
}
}

View File

@ -0,0 +1,2 @@
export * from './IProjectAssignmentsProps';
export * from './ProjectAssignments';

View File

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

View File

@ -0,0 +1,3 @@
export interface IProjectOnlineState {
projectId: string;
}

View File

@ -0,0 +1,22 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.projectOnline {
.container {
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);
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
}

View File

@ -0,0 +1,55 @@
import * as React from 'react';
import styles from './ProjectOnline.module.scss';
import { IProjectOnlineProps, IProjectOnlineState } from '.';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Projects } from "../Projects";
import { ProjectTasks } from "../ProjectTasks";
import { ProjectAssignments } from "../ProjectAssignments/index";
import { Calendars } from '../Calendars';
export class ProjectOnline extends React.Component<IProjectOnlineProps, IProjectOnlineState> {
constructor(props: IProjectOnlineProps) {
super(props);
this.state = {
projectId: ''
};
}
public render(): React.ReactElement<IProjectOnlineProps> {
return (
<div className={styles.projectOnline}>
<div className={styles.container}>
<span className={styles.title}>Welcome to Project Online!</span>
<p className={styles.subTitle}>Customize Project Online experiences using SPFx Web Parts.</p>
<TextField label="Project Id" value={this.state.projectId} onChange={this._onChange} />
<h3>Projects</h3>
<Projects projectId={this.state.projectId}></Projects>
<h3>Project Tasks</h3>
<ProjectTasks projectId={this.state.projectId}></ProjectTasks>
<h3>Project Assignments</h3>
<ProjectAssignments projectId={this.state.projectId}></ProjectAssignments>
<h3>Calendars</h3>
<Calendars></Calendars>
</div>
</div>
);
}
private _onChange = (ev: React.FormEvent<HTMLInputElement>, newValue?: string) => {
this.setState({ projectId: newValue || '' });
}
}

View File

@ -0,0 +1,3 @@
export * from './IProjectOnlineProps';
export * from './IProjectOnlineState';
export * from './ProjectOnline';

View File

@ -0,0 +1,3 @@
export interface IProjectTasksProps {
projectId: string;
}

View File

@ -0,0 +1,9 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.buttons {
button {
margin: 5px;
}
}

View File

@ -0,0 +1,57 @@
import * as React from 'react';
import styles from './ProjectTasks.module.scss';
import { IProjectTasksProps } from './IProjectTasksProps';
import { project, User, CustomFieldCollection, PublishedAssignmentCollection, Calendar, PublishedTaskLinkCollection, PublishedTaskCollection, PublishedTask } from "pnpjs-project-online-package";
import { Button } from "office-ui-fabric-react/lib/Button";
export class ProjectTasks extends React.Component<IProjectTasksProps, {}> {
public render(): React.ReactElement<IProjectTasksProps> {
return (
<div className={styles.buttons}>
<Button text='Get all project tasks' onClick={this._getAllProjectTasks}></Button>
<Button text='Get project task by Id' onClick={this._getProjectTaskById}></Button>
</div>
);
}
private _getAllProjectTasks = async () => {
const projectTasks: PublishedTaskCollection[] = await project.projects.getById(this.props.projectId).tasks.get();
console.log('Project tasks', projectTasks);
}
private _getProjectTaskById = async () => {
const id = '3a701829-2d77-e911-8166-000d3a6dc32c';
const publishedTask: PublishedTask = await project.projects.getById(this.props.projectId).tasks.getById(id).get();
console.log('Published task', publishedTask);
const publishedAssignmentCollection: PublishedAssignmentCollection[] = await project.projects.getById(this.props.projectId).tasks.getById(id).assignments.get();
console.log('Published Assignment Collection', publishedAssignmentCollection);
const calendar: Calendar = await project.projects.getById(this.props.projectId).tasks.getById(id).calendar.get();
console.log('Calendar', calendar);
const customFieldCollection: CustomFieldCollection[] = await project.projects.getById(this.props.projectId).tasks.getById(id).customFields.get();
console.log('Custom Field Collection', customFieldCollection);
const parentTaskLink: PublishedTask = await project.projects.getById(this.props.projectId).tasks.getById(id).parent.get();
console.log('Parent Task Link', parentTaskLink);
const predecessorTasks: PublishedTaskLinkCollection[] = await project.projects.getById(this.props.projectId).tasks.getById(id).predecessors.get();
console.log('Predecessor Tasks', predecessorTasks);
const successorTasks: PublishedTaskLinkCollection[] = await project.projects.getById(this.props.projectId).tasks.getById(id).successors.get();
console.log('Successor Tasks', successorTasks);
const statusManager: User = await project.projects.getById(this.props.projectId).tasks.getById(id).statusManager.get();
console.log('Status Manager', statusManager);
}
}

View File

@ -0,0 +1,2 @@
export * from './IProjectTasksProps';
export * from './ProjectTasks';

View File

@ -0,0 +1,3 @@
export interface IProjectsProps {
projectId: string;
}

View File

@ -0,0 +1,9 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.buttons {
button {
margin: 5px;
}
}

View File

@ -0,0 +1,117 @@
import * as React from 'react';
import styles from './Projects.module.scss';
import { IProjectsProps } from './IProjectsProps';
import { project, PublishedProject, ProjectCollection, User, CustomFieldCollection, Phase, ProjectSummaryTask, QueueJobCollection, Stage, PublishedAssignmentCollection, Calendar, DraftProject, PublishedProjectResourceCollection, PublishedTaskLinkCollection, PublishedTaskCollection, CommandResult, QueueJob } from "pnpjs-project-online-package";
import { Button } from "office-ui-fabric-react/lib/Button";
import { TypedHash } from '@pnp/common';
export class Projects extends React.Component<IProjectsProps, {}> {
public render(): React.ReactElement<IProjectsProps> {
return (
<div className={styles.buttons}>
<Button text='Get all projects' onClick={this._getAllProjects}></Button>
<Button text='Get project by Id' onClick={this._getProjectById}></Button>
<Button text='Add project' onClick={this._addProject}></Button>
<Button text='Update project' onClick={this._updateProject}></Button>
<Button text='Submit Workflow' onClick={this._updateProjectWorkflow}></Button>
<Button text='Delete project' onClick={this._deleteProject}></Button>
</div>
);
}
private _getAllProjects = async () => {
const projects: ProjectCollection[] = await project.projects.get();
console.log('Projects', projects);
}
private _addProject = async () => {
const proj: CommandResult<PublishedProject> = await project.projects.add({
Name: 'JR test ' + Date.now(),
Description: 'Test project',
EnterpriseProjectTypeId: '7ca316cc-b347-e711-80d1-00155d3c701a'
});
console.log(proj);
}
private _getProjectById = async () => {
const publishedProject: PublishedProject = await project.projects.getById(this.props.projectId).get();
console.log('Project', publishedProject);
const user: User = await project.projects.getById(this.props.projectId).checkedOutBy.get();
console.log('Checked out by', user);
const customFields: CustomFieldCollection[] = await project.projects.getById(this.props.projectId).customFields.get();
console.log('Custom Fields', customFields);
const enterpriseProjectType: CustomFieldCollection = await project.projects.getById(this.props.projectId).enterpriseProjectType.get();
console.log('Enterprise Project Type', enterpriseProjectType);
const phase: Phase = await project.projects.getById(this.props.projectId).phase.get();
console.log('Phase', phase);
const projectSummaryTask: ProjectSummaryTask = await project.projects.getById(this.props.projectId).projectSummaryTask.get();
console.log('Project Summary Task', projectSummaryTask);
const queueJobs: QueueJobCollection[] = await project.projects.getById(this.props.projectId).queueJobs.get();
console.log('Queue Jobs', queueJobs);
const stage: Stage = await project.projects.getById(this.props.projectId).stage.get();
console.log('Stage', stage);
const assignments: PublishedAssignmentCollection[] = await project.projects.getById(this.props.projectId).assignments.get();
console.log('Assignments', assignments);
const calendar: Calendar = await project.projects.getById(this.props.projectId).calendar.get();
console.log('Calendar', calendar);
const draft: DraftProject = await project.projects.getById(this.props.projectId).draft.get();
console.log('Draft', draft);
const includeCustomFields: PublishedProject = await project.projects.getById(this.props.projectId).includeCustomFields.get();
console.log('Include Custom Fields', includeCustomFields);
const owner: User = await project.projects.getById(this.props.projectId).owner.get();
console.log('Owner', owner);
const projectResources: PublishedProjectResourceCollection[] = await project.projects.getById(this.props.projectId).projectResources.get();
console.log('Project Resources', projectResources);
const taskLinks: PublishedTaskLinkCollection[] = await project.projects.getById(this.props.projectId).taskLinks.get();
console.log('Task Links', taskLinks);
const tasks: PublishedTaskCollection[] = await project.projects.getById(this.props.projectId).tasks.get();
console.log('Tasks', tasks);
}
private _updateProject = async () => {
const checkedOutProject: CommandResult<DraftProject> = await project.projects.getById(this.props.projectId).checkOut();
console.log('CheckOut', checkedOutProject);
const updateValue: TypedHash<string> = {
'Description': 'Updated project ' + Date.now()
};
const update: CommandResult<QueueJob> = await checkedOutProject.instance.update(
updateValue
);
console.log('Update', update);
const publish: CommandResult<QueueJob> = await checkedOutProject.instance.publish(false);
console.log('Publish', publish);
const checkIn: CommandResult<QueueJob> = await checkedOutProject.instance.checkIn(true);
console.log('Check In', checkIn);
}
private _updateProjectWorkflow = async () => {
const submitToWorkflow: void = await project.projects.getById(this.props.projectId).submitToWorkflow();
console.log('Submit To Workflow', submitToWorkflow);
}
private _deleteProject = async () => {
const deleteJob: CommandResult<QueueJob> = await project.projects.getById(this.props.projectId).delete();
console.log('Delete', deleteJob);
}
}

View File

@ -0,0 +1,2 @@
export * from './IProjectsProps';
export * from './Projects';

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 IProjectOnlineWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'ProjectOnlineWebPartStrings' {
const strings: IProjectOnlineWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

View File

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

View File

@ -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/react-quotes/.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,5 @@
{
"recommendations": [
"msjsdiag.debugger-for-chrome"
]
}

View File

@ -0,0 +1,43 @@
{
/**
* Install Chrome Debugger Extension for Visual Studio Code to debug your components with the
* Chrome browser: https://aka.ms/spfx-debugger-extensions
*/
"version": "0.2.0",
"configurations": [{
"name": "Local workbench",
"type": "chrome",
"request": "launch",
"url": "https://localhost:4321/temp/workbench.html",
"webRoot": "${workspaceRoot}",
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///.././src/*": "${webRoot}/src/*",
"webpack:///../../../src/*": "${webRoot}/src/*",
"webpack:///../../../../src/*": "${webRoot}/src/*",
"webpack:///../../../../../src/*": "${webRoot}/src/*"
},
"runtimeArgs": [
"--remote-debugging-port=9222"
]
},
{
"name": "Hosted workbench",
"type": "chrome",
"request": "launch",
"url": "https://enter-your-SharePoint-site/_layouts/workbench.aspx",
"webRoot": "${workspaceRoot}",
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///.././src/*": "${webRoot}/src/*",
"webpack:///../../../src/*": "${webRoot}/src/*",
"webpack:///../../../../src/*": "${webRoot}/src/*",
"webpack:///../../../../../src/*": "${webRoot}/src/*"
},
"runtimeArgs": [
"--remote-debugging-port=9222",
"-incognito"
]
}
]
}

View File

@ -0,0 +1,13 @@
// Place your settings in this file to overwrite default and user settings.
{
// Configure glob patterns for excluding files and folders in the file explorer.
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true,
"**/bower_components": true,
"**/coverage": true,
"**/lib-amd": true,
"src/**/*.scss.ts": true
},
"typescript.tsdk": ".\\node_modules\\typescript\\lib"
}

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.9.1",
"libraryName": "react-quotes",
"libraryId": "aa9211b8-e9b8-44bf-a804-af2db58bc03e",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,48 @@
# react-quotes
## Summary
This webpart displays a quote of the day by querying a third-party api or can display a quote entered manually into the webpart property pane.
![picture of the web part in action](./assets/react-quotes-sample.png)
## Used SharePoint Framework Version
![drop](https://img.shields.io/badge/version-1.9.1-green.svg)
## Applies to
* [SharePoint Framework](https:/dev.office.com/sharepoint)
## Solution
Solution|Author(s)
--------|---------
react-quotes | Zach Roberts
## Version history
Version|Date|Comments
-------|----|--------
1.0| November 11, 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`
* In the browser that opens add the webpart to your page.
* After the webpart has loaded it will load the quote automatically or you can edit the webparts properties to display a manual quote.
## Features
This webpart loads a random quote from a third-party api (https://favqs.com/api). Additionally a quote can be entered manually and the text color of the quote and author can be adjusted through the webpart properties.
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,19 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"react-quotes-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/reactQuotes/ReactQuotesWebPart.js",
"manifest": "./src/webparts/reactQuotes/ReactQuotesWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"ReactQuotesWebPartStrings": "lib/webparts/reactQuotes/loc/{locale}.js",
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
}
}

View File

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

View File

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

View File

@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-quotes-client-side-solution",
"id": "aa9211b8-e9b8-44bf-a804-af2db58bc03e",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/react-quotes.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 -->"
}

7
samples/react-quotes/gulpfile.js vendored Normal file
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);

18337
samples/react-quotes/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
{
"name": "react-quotes",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.9.1",
"@microsoft/sp-lodash-subset": "1.9.1",
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
"@microsoft/sp-webpart-base": "1.9.1",
"@pnp/spfx-property-controls": "1.16.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"axios": "^0.19.0",
"office-ui-fabric-react": "6.189.2",
"react": "16.8.5",
"react-dom": "16.8.5"
},
"resolutions": {
"@types/react": "16.8.8"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.9.1",
"@microsoft/sp-tslint-rules": "1.9.1",
"@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1",
"@microsoft/rush-stack-compiler-2.9": "0.7.16",
"gulp": "~3.9.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2"
}
}

View File

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

View File

@ -0,0 +1,27 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "4f425057-db76-4edd-98ee-73f1e4e81727",
"alias": "ReactQuotesWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "react-quotes" },
"description": { "default": "react-quotes description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "react-quotes"
}
}]
}

View File

@ -0,0 +1,105 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import * as strings from 'ReactQuotesWebPartStrings';
import ReactQuotes from './components/ReactQuotes';
import { IReactQuotesProps } from './components/IReactQuotesProps';
import { PropertyFieldColorPicker, PropertyFieldColorPickerStyle } from '@pnp/spfx-property-controls/lib/PropertyFieldColorPicker';
import { PropertyPaneToggle } from '@microsoft/sp-property-pane';
export interface IReactQuotesWebPartProps {
description: string;
quoteColor: string;
authorColor: string;
manual: boolean;
manualQuote: string;
manualAuthor: string;
}
export default class ReactQuotesWebPart extends BaseClientSideWebPart<IReactQuotesWebPartProps> {
public render(): void {
const element: React.ReactElement<IReactQuotesProps > = React.createElement(
ReactQuotes,
{
description: this.properties.description,
quoteColor: this.properties.quoteColor,
authorColor: this.properties.authorColor,
manual: this.properties.manual,
manualQuote: this.properties.manualQuote,
manualAuthor: this.properties.manualAuthor
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: 'Quote Settings',
groupFields: [
PropertyPaneTextField('description', {
label: 'Title'
}),
PropertyFieldColorPicker('quoteColor', {
label: 'Quote Color',
selectedColor: this.properties.quoteColor,
onPropertyChange: this.onPropertyPaneFieldChanged,
properties: this.properties,
disabled: false,
isHidden: false,
alphaSliderHidden: false,
iconName: 'Precipitation',
key: 'quoteFieldID'
}),
PropertyFieldColorPicker('authorColor', {
label: 'Author Color',
selectedColor: this.properties.authorColor,
onPropertyChange: this.onPropertyPaneFieldChanged,
properties: this.properties,
disabled: false,
isHidden: false,
alphaSliderHidden: false,
iconName: 'Precipitation',
key: 'authorFieldID'
}),
PropertyPaneToggle('manual', {
label: 'Use manual quote?'
}),
PropertyPaneTextField('manualQuote', {
label: 'Quote',
disabled: !this.properties.manual
}),
PropertyPaneTextField('manualAuthor', {
label: 'Author',
disabled: !this.properties.manual
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,8 @@
export interface IReactQuotesProps {
description: string;
quoteColor: string;
authorColor: string;
manual: boolean;
manualQuote: string;
manualAuthor: string;
}

View File

@ -0,0 +1,5 @@
export interface IReactQuotesState {
quote: string;
author: string;
loading: boolean;
}

View File

@ -0,0 +1,74 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.reactQuotes {
.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,64 @@
import * as React from 'react';
import styles from './ReactQuotes.module.scss';
import { IReactQuotesProps } from './IReactQuotesProps';
import { IReactQuotesState } from './IReactQuotesState';
import { escape } from '@microsoft/sp-lodash-subset';
import { Spinner } from 'office-ui-fabric-react/lib/spinner';
import axios from 'axios';
export default class ReactQuotes extends React.Component<IReactQuotesProps, IReactQuotesState> {
constructor(props: IReactQuotesProps){
super(props);
this.state = {
quote: "",
author: "",
loading: false
};
}
public render(): React.ReactElement<IReactQuotesProps> {
return (
<div className={ styles.reactQuotes }>
<h2>{this.props.description}</h2>
{this.state.loading !== true ?
//If state is loading show spinner otherwise show quote
<div>
{this.props.manual !== true ?
//If manual quote is not on then show quote generated from third party
<div>
<h3 style={{color: this.props.quoteColor}}>"<i>{this.state.quote}</i>"</h3>
<h5 style={{color: this.props.authorColor}}>- {this.state.author}</h5>
</div>
: //Show Manual quote
<div>
<h3 style={{color: this.props.quoteColor}}>"<i>{this.props.manualQuote}</i>"</h3>
<h5 style={{color: this.props.authorColor}}>- {this.props.manualAuthor}</h5>
</div>
}
</div>
:
<div>
<Spinner label="Loading quote..." />
</div>
}
</div>
);
}
public componentDidMount() {
console.log(this.props.manual);
this.setState({loading: true});
axios.get('https://favqs.com/api/qotd').then(res => {
const quote = res.data.quote.body;
const author = res.data.quote.author;
this.setState({
quote: quote,
author: author,
loading: false
});
});
}
}

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 IReactQuotesWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'ReactQuotesWebPartStrings' {
const strings: IReactQuotesWebPartStrings;
export = strings;
}

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