├── slides.pptx ├── demos ├── .DS_Store ├── 01-ace-sp-rest │ ├── src │ │ ├── index.ts │ │ └── adaptiveCardExtensions │ │ │ └── sharePointRest │ │ │ ├── assets │ │ │ └── MicrosoftLogo.png │ │ │ ├── loc │ │ │ ├── en-us.js │ │ │ └── mystring.d.ts │ │ │ ├── SharePointRestPropertyPane.ts │ │ │ ├── quickView │ │ │ ├── template │ │ │ │ ├── NewItemQuickView.json │ │ │ │ └── QuickViewTemplate.json │ │ │ ├── QuickView.ts │ │ │ └── NewItemQuickView.ts │ │ │ ├── SharePointRestAdaptiveCardExtension.manifest.json │ │ │ ├── cardView │ │ │ └── CardView.ts │ │ │ ├── sp.service.ts │ │ │ └── SharePointRestAdaptiveCardExtension.ts │ ├── config │ │ ├── sass.json │ │ ├── write-manifests.json │ │ ├── serve.json │ │ ├── deploy-azure-storage.json │ │ ├── config.json │ │ └── package-solution.json │ ├── .npmignore │ ├── .vscode │ │ ├── settings.json │ │ └── launch.json │ ├── gulpfile.js │ ├── .gitignore │ ├── .yo-rc.json │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ └── .eslintrc.js ├── 03-geo-location │ ├── src │ │ ├── index.ts │ │ └── adaptiveCardExtensions │ │ │ └── campusShuttle │ │ │ ├── assets │ │ │ ├── MicrosoftLogo.png │ │ │ └── campus_locations.json │ │ │ ├── quickView │ │ │ ├── index.ts │ │ │ ├── template │ │ │ │ ├── SaveTripCard.json │ │ │ │ ├── ConfirmationCard.json │ │ │ │ ├── UpdateTripCard.json │ │ │ │ ├── SetOriginCard.json │ │ │ │ ├── SetDestinationCard.json │ │ │ │ └── StartTripCard.json │ │ │ ├── SaveTrip.ts │ │ │ ├── SetOrigin.ts │ │ │ ├── UpdateTrip.ts │ │ │ ├── ConfirmationQuickView.ts │ │ │ ├── StartTrip.ts │ │ │ └── SetDestination.ts │ │ │ ├── loc │ │ │ ├── mystring.d.ts │ │ │ └── en-us.js │ │ │ ├── CampusShuttlePropertyPane.ts │ │ │ ├── CampusShuttleAdaptiveCardExtension.manifest.json │ │ │ ├── cardView │ │ │ └── CardView.ts │ │ │ ├── CampusShuttleAdaptiveCardExtension.ts │ │ │ └── sp.service.ts │ ├── config │ │ ├── sass.json │ │ ├── write-manifests.json │ │ ├── serve.json │ │ ├── deploy-azure-storage.json │ │ ├── config.json │ │ └── package-solution.json │ ├── .npmignore │ ├── .vscode │ │ ├── settings.json │ │ └── launch.json │ ├── gulpfile.js │ ├── .gitignore │ ├── .yo-rc.json │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ └── .eslintrc.js ├── 02-ace-image-viewer │ ├── src │ │ ├── index.ts │ │ └── adaptiveCardExtensions │ │ │ └── aceImageViewer │ │ │ ├── assets │ │ │ └── MicrosoftLogo.png │ │ │ ├── loc │ │ │ ├── en-us.js │ │ │ └── mystring.d.ts │ │ │ ├── quickView │ │ │ ├── QuickView.ts │ │ │ └── template │ │ │ │ └── QuickViewTemplate.json │ │ │ ├── nasa.service.ts │ │ │ ├── AceImageViewerPropertyPane.ts │ │ │ ├── AceImageViewerAdaptiveCardExtension.manifest.json │ │ │ ├── AceImageViewerAdaptiveCardExtension.ts │ │ │ └── cardView │ │ │ └── CardView.ts │ ├── config │ │ ├── sass.json │ │ ├── write-manifests.json │ │ ├── serve.json │ │ ├── deploy-azure-storage.json │ │ ├── config.json │ │ └── package-solution.json │ ├── .npmignore │ ├── .vscode │ │ ├── settings.json │ │ └── launch.json │ ├── gulpfile.js │ ├── .gitignore │ ├── .yo-rc.json │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ └── .eslintrc.js └── README.md ├── LICENSE ├── .gitignore ├── SECURITY.md └── README.md /slides.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-ace/main/slides.pptx -------------------------------------------------------------------------------- /demos/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-ace/main/demos/.DS_Store -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/index.ts: -------------------------------------------------------------------------------- 1 | // A file is required to be in the root of the /src directory by the TypeScript compiler 2 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/index.ts: -------------------------------------------------------------------------------- 1 | // A file is required to be in the root of the /src directory by the TypeScript compiler 2 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/src/index.ts: -------------------------------------------------------------------------------- 1 | // A file is required to be in the root of the /src directory by the TypeScript compiler 2 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/config/sass.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/core-build/sass.schema.json" 3 | } -------------------------------------------------------------------------------- /demos/03-geo-location/config/sass.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/core-build/sass.schema.json" 3 | } -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/config/sass.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/core-build/sass.schema.json" 3 | } -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/config/write-manifests.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json", 3 | "cdnBasePath": "" 4 | } -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/config/write-manifests.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json", 3 | "cdnBasePath": "" 4 | } -------------------------------------------------------------------------------- /demos/03-geo-location/config/write-manifests.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json", 3 | "cdnBasePath": "" 4 | } -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/.npmignore: -------------------------------------------------------------------------------- 1 | !dist 2 | config 3 | 4 | gulpfile.js 5 | 6 | release 7 | src 8 | temp 9 | 10 | tsconfig.json 11 | tslint.json 12 | 13 | *.log 14 | 15 | .yo-rc.json 16 | .vscode 17 | -------------------------------------------------------------------------------- /demos/03-geo-location/.npmignore: -------------------------------------------------------------------------------- 1 | !dist 2 | config 3 | 4 | gulpfile.js 5 | 6 | release 7 | src 8 | temp 9 | 10 | tsconfig.json 11 | tslint.json 12 | 13 | *.log 14 | 15 | .yo-rc.json 16 | .vscode 17 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/.npmignore: -------------------------------------------------------------------------------- 1 | !dist 2 | config 3 | 4 | gulpfile.js 5 | 6 | release 7 | src 8 | temp 9 | 10 | tsconfig.json 11 | tslint.json 12 | 13 | *.log 14 | 15 | .yo-rc.json 16 | .vscode 17 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/assets/MicrosoftLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-ace/main/demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/assets/MicrosoftLogo.png -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/assets/MicrosoftLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-ace/main/demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/assets/MicrosoftLogo.png -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/src/adaptiveCardExtensions/aceImageViewer/assets/MicrosoftLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharepoint/sp-dev-training-spfx-ace/main/demos/02-ace-image-viewer/src/adaptiveCardExtensions/aceImageViewer/assets/MicrosoftLogo.png -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/config/serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json", 3 | "port": 4321, 4 | "https": true, 5 | "initialPage": "https://{tenantDomain}/_layouts/workbench.aspx" 6 | } 7 | -------------------------------------------------------------------------------- /demos/03-geo-location/config/serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json", 3 | "port": 4321, 4 | "https": true, 5 | "initialPage": "https://{tenantDomain}/_layouts/workbench.aspx" 6 | } 7 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/config/serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json", 3 | "port": 4321, 4 | "https": true, 5 | "initialPage": "https://{tenantDomain}/_layouts/workbench.aspx" 6 | } 7 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/index.ts: -------------------------------------------------------------------------------- 1 | export * from './StartTrip'; 2 | export * from './SetOrigin'; 3 | export * from './SetDestination'; 4 | export * from './SaveTrip'; 5 | export * from './UpdateTrip'; 6 | export * from './ConfirmationQuickView'; 7 | -------------------------------------------------------------------------------- /demos/03-geo-location/config/deploy-azure-storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json", 3 | "workingDir": "./release/assets/", 4 | "account": "", 5 | "container": "ace-campus-shuttle", 6 | "accessKey": "" 7 | } -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/config/deploy-azure-storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json", 3 | "workingDir": "./release/assets/", 4 | "account": "", 5 | "container": "ace-share-point-rest", 6 | "accessKey": "" 7 | } -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/config/deploy-azure-storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json", 3 | "workingDir": "./release/assets/", 4 | "account": "", 5 | "container": "ace-image-viewer", 6 | "accessKey": "" 7 | } -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/template/SaveTripCard.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": "http://adaptivecards.io/schemas/adaptive-card.json", 3 | "type": "AdaptiveCard", 4 | "version": "1.5", 5 | "body": [{ 6 | "type": "TextBlock", "text": "${title}" 7 | }], 8 | "actions": [{ 9 | "type": "Action.Submit", 10 | "id": "close", 11 | "title": "Close" 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Configure glob patterns for excluding files and folders in the file explorer. 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.DS_Store": true, 7 | "**/bower_components": true, 8 | "**/coverage": true, 9 | "**/lib-amd": true, 10 | "src/**/*.scss.ts": true 11 | }, 12 | "typescript.tsdk": ".\\node_modules\\typescript\\lib" 13 | } -------------------------------------------------------------------------------- /demos/03-geo-location/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Configure glob patterns for excluding files and folders in the file explorer. 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.DS_Store": true, 7 | "**/bower_components": true, 8 | "**/coverage": true, 9 | "**/lib-amd": true, 10 | "src/**/*.scss.ts": true 11 | }, 12 | "typescript.tsdk": ".\\node_modules\\typescript\\lib" 13 | } -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Configure glob patterns for excluding files and folders in the file explorer. 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.DS_Store": true, 7 | "**/bower_components": true, 8 | "**/coverage": true, 9 | "**/lib-amd": true, 10 | "src/**/*.scss.ts": true 11 | }, 12 | "typescript.tsdk": ".\\node_modules\\typescript\\lib" 13 | } -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const build = require('@microsoft/sp-build-web'); 4 | 5 | build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`); 6 | 7 | var getTasks = build.rig.getTasks; 8 | build.rig.getTasks = function () { 9 | var result = getTasks.call(build.rig); 10 | 11 | result.set('serve', result.get('serve-deprecated')); 12 | 13 | return result; 14 | }; 15 | 16 | build.initialize(require('gulp')); 17 | -------------------------------------------------------------------------------- /demos/03-geo-location/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const build = require('@microsoft/sp-build-web'); 4 | 5 | build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`); 6 | 7 | var getTasks = build.rig.getTasks; 8 | build.rig.getTasks = function () { 9 | var result = getTasks.call(build.rig); 10 | 11 | result.set('serve', result.get('serve-deprecated')); 12 | 13 | return result; 14 | }; 15 | 16 | build.initialize(require('gulp')); 17 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const build = require('@microsoft/sp-build-web'); 4 | 5 | build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`); 6 | 7 | var getTasks = build.rig.getTasks; 8 | build.rig.getTasks = function () { 9 | var result = getTasks.call(build.rig); 10 | 11 | result.set('serve', result.get('serve-deprecated')); 12 | 13 | return result; 14 | }; 15 | 16 | build.initialize(require('gulp')); 17 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/loc/mystring.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ICampusShuttleAdaptiveCardExtensionStrings { 2 | PropertyPaneDescription: string; 3 | TitleFieldLabel: string; 4 | Title: string; 5 | SubTitle: string; 6 | PrimaryText: string; 7 | Description: string; 8 | QuickViewButton: string; 9 | } 10 | 11 | declare module 'CampusShuttleAdaptiveCardExtensionStrings' { 12 | const strings: ICampusShuttleAdaptiveCardExtensionStrings; 13 | export = strings; 14 | } 15 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/loc/en-us.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | return { 3 | "PropertyPaneDescription": "Write 1-3 sentences describing the functionality of this component.", 4 | "TitleFieldLabel": "Card title", 5 | "Title": "Adaptive Card Extension", 6 | "SubTitle": "Quick view", 7 | "PrimaryText": "SPFx Adaptive Card Extension", 8 | "Description": "Create your SPFx Adaptive Card Extension solution!", 9 | "QuickViewButton": "Quick view" 10 | } 11 | }); -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/loc/mystring.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ISharePointRestAdaptiveCardExtensionStrings { 2 | PropertyPaneDescription: string; 3 | TitleFieldLabel: string; 4 | Title: string; 5 | SubTitle: string; 6 | PrimaryText: string; 7 | Description: string; 8 | QuickViewButton: string; 9 | } 10 | 11 | declare module 'SharePointRestAdaptiveCardExtensionStrings' { 12 | const strings: ISharePointRestAdaptiveCardExtensionStrings; 13 | export = strings; 14 | } 15 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/loc/en-us.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | return { 3 | "PropertyPaneDescription": "Write 1-3 sentences describing the functionality of this component.", 4 | "TitleFieldLabel": "Card title", 5 | "Title": "Adaptive Card Extension", 6 | "SubTitle": "Quick view", 7 | "PrimaryText": "SPFx Adaptive Card Extension", 8 | "Description": "Create your SPFx Adaptive Card Extension solution!", 9 | "QuickViewButton": "Quick view" 10 | } 11 | }); -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directories 7 | node_modules 8 | 9 | # Build generated files 10 | dist 11 | lib 12 | release 13 | solution 14 | temp 15 | *.sppkg 16 | .heft 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # OSX 22 | .DS_Store 23 | 24 | # Visual Studio files 25 | .ntvs_analysis.dat 26 | .vs 27 | bin 28 | obj 29 | 30 | # Resx Generated Code 31 | *.resx.ts 32 | 33 | # Styles Generated Code 34 | *.scss.ts 35 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/src/adaptiveCardExtensions/aceImageViewer/loc/en-us.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | return { 3 | "PropertyPaneDescription": "Write 1-3 sentences describing the functionality of this component.", 4 | "TitleFieldLabel": "Card title", 5 | "Title": "Adaptive Card Extension", 6 | "SubTitle": "Quick view", 7 | "PrimaryText": "SPFx Adaptive Card Extension", 8 | "Description": "Create your SPFx Adaptive Card Extension solution!", 9 | "QuickViewButton": "Quick view" 10 | } 11 | }); -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/src/adaptiveCardExtensions/aceImageViewer/loc/mystring.d.ts: -------------------------------------------------------------------------------- 1 | declare interface IAceImageViewerAdaptiveCardExtensionStrings { 2 | PropertyPaneDescription: string; 3 | TitleFieldLabel: string; 4 | Title: string; 5 | SubTitle: string; 6 | PrimaryText: string; 7 | Description: string; 8 | QuickViewButton: string; 9 | } 10 | 11 | declare module 'AceImageViewerAdaptiveCardExtensionStrings' { 12 | const strings: IAceImageViewerAdaptiveCardExtensionStrings; 13 | export = strings; 14 | } 15 | -------------------------------------------------------------------------------- /demos/03-geo-location/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directories 7 | node_modules 8 | 9 | # Build generated files 10 | dist 11 | lib 12 | release 13 | solution 14 | temp 15 | *.sppkg 16 | .heft 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # OSX 22 | .DS_Store 23 | 24 | # Visual Studio files 25 | .ntvs_analysis.dat 26 | .vs 27 | bin 28 | obj 29 | 30 | # Resx Generated Code 31 | *.resx.ts 32 | 33 | # Styles Generated Code 34 | *.scss.ts 35 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directories 7 | node_modules 8 | 9 | # Build generated files 10 | dist 11 | lib 12 | release 13 | solution 14 | temp 15 | *.sppkg 16 | .heft 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # OSX 22 | .DS_Store 23 | 24 | # Visual Studio files 25 | .ntvs_analysis.dat 26 | .vs 27 | bin 28 | obj 29 | 30 | # Resx Generated Code 31 | *.resx.ts 32 | 33 | # Styles Generated Code 34 | *.scss.ts 35 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/template/ConfirmationCard.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": "http://adaptivecards.io/schemas/adaptive-card.json", 3 | "type": "AdaptiveCard", 4 | "version": "1.5", 5 | "body": [ 6 | { 7 | "type": "TextBlock", 8 | "text": "${title}", 9 | "size": "Large" 10 | }, 11 | { 12 | "type": "TextBlock", 13 | "text": "${description}" 14 | } 15 | ], 16 | "actions": [ 17 | { 18 | "id": "confirm", 19 | "type": "Action.Submit", 20 | "title": "${title}", 21 | "style": "positive" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /demos/README.md: -------------------------------------------------------------------------------- 1 | # Microsoft SharePoint Framework Training Module - Create Adaptive Card Extensions (ACE) for Microsoft Viva Connections 2 | 3 | This folder contains demos for the SharePoint Framework training module on **Create Adaptive Card Extensions (ACE) for Microsoft Viva Connections**. 4 | 5 | ## Demos 6 | 7 | - [Create SPFx Basic Card ACE showing SharePoint list data](./01-ace-sp-rest) 8 | - [Create an SPFx Image Card ACE displaying image carousel](./02-ace-image-viewer) 9 | - [Create an SPFx ACE with geo-location capabilities](./03-geo-location) 10 | 11 | ## Running demonstrations 12 | 13 | Each demonstration is included as source code for convenience. 14 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/template/UpdateTripCard.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": "http://adaptivecards.io/schemas/adaptive-card.json", 3 | "type": "AdaptiveCard", 4 | "version": "1.5", 5 | "body": [ 6 | { 7 | "type": "TextBlock", 8 | "weight": "Bolder", 9 | "text": "${title}" 10 | } 11 | ], 12 | "actions": [ 13 | { 14 | "id": "cancel", 15 | "type": "Action.Submit", 16 | "title": "Cancel Current Trip" 17 | }, 18 | { 19 | "id": "pickup", 20 | "type": "Action.Submit", 21 | "title": "Pickup Passenger", 22 | "style": "positive" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /demos/03-geo-location/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", 3 | "version": "2.0", 4 | "bundles": { 5 | "campus-shuttle-adaptive-card-extension": { 6 | "components": [ 7 | { 8 | "entrypoint": "./lib/adaptiveCardExtensions/campusShuttle/CampusShuttleAdaptiveCardExtension.js", 9 | "manifest": "./src/adaptiveCardExtensions/campusShuttle/CampusShuttleAdaptiveCardExtension.manifest.json" 10 | } 11 | ] 12 | } 13 | }, 14 | "externals": {}, 15 | "localizedResources": { 16 | "CampusShuttleAdaptiveCardExtensionStrings": "lib/adaptiveCardExtensions/campusShuttle/loc/{locale}.js" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", 3 | "version": "2.0", 4 | "bundles": { 5 | "share-point-rest-adaptive-card-extension": { 6 | "components": [ 7 | { 8 | "entrypoint": "./lib/adaptiveCardExtensions/sharePointRest/SharePointRestAdaptiveCardExtension.js", 9 | "manifest": "./src/adaptiveCardExtensions/sharePointRest/SharePointRestAdaptiveCardExtension.manifest.json" 10 | } 11 | ] 12 | } 13 | }, 14 | "externals": {}, 15 | "localizedResources": { 16 | "SharePointRestAdaptiveCardExtensionStrings": "lib/adaptiveCardExtensions/sharePointRest/loc/{locale}.js" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", 3 | "version": "2.0", 4 | "bundles": { 5 | "ace-image-viewer-adaptive-card-extension": { 6 | "components": [ 7 | { 8 | "entrypoint": "./lib/adaptiveCardExtensions/aceImageViewer/AceImageViewerAdaptiveCardExtension.js", 9 | "manifest": "./src/adaptiveCardExtensions/aceImageViewer/AceImageViewerAdaptiveCardExtension.manifest.json" 10 | } 11 | ] 12 | } 13 | }, 14 | "externals": {}, 15 | "localizedResources": { 16 | "AceImageViewerAdaptiveCardExtensionStrings": "lib/adaptiveCardExtensions/aceImageViewer/loc/{locale}.js" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Hosted workbench", 6 | "type": "edge", 7 | "request": "launch", 8 | "url": "https://{tenantDomain}/_layouts/workbench.aspx", 9 | "webRoot": "${workspaceRoot}", 10 | "sourceMaps": true, 11 | "sourceMapPathOverrides": { 12 | "webpack:///.././src/*": "${webRoot}/src/*", 13 | "webpack:///../../../src/*": "${webRoot}/src/*", 14 | "webpack:///../../../../src/*": "${webRoot}/src/*", 15 | "webpack:///../../../../../src/*": "${webRoot}/src/*" 16 | }, 17 | "runtimeArgs": [ 18 | "--remote-debugging-port=9222", 19 | "-incognito" 20 | ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /demos/03-geo-location/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Hosted workbench", 6 | "type": "edge", 7 | "request": "launch", 8 | "url": "https://{tenantDomain}/_layouts/workbench.aspx", 9 | "webRoot": "${workspaceRoot}", 10 | "sourceMaps": true, 11 | "sourceMapPathOverrides": { 12 | "webpack:///.././src/*": "${webRoot}/src/*", 13 | "webpack:///../../../src/*": "${webRoot}/src/*", 14 | "webpack:///../../../../src/*": "${webRoot}/src/*", 15 | "webpack:///../../../../../src/*": "${webRoot}/src/*" 16 | }, 17 | "runtimeArgs": [ 18 | "--remote-debugging-port=9222", 19 | "-incognito" 20 | ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/template/SetOriginCard.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 3 | "type": "AdaptiveCard", 4 | "version": "1.5", 5 | "body": [ 6 | { 7 | "type": "TextBlock", 8 | "weight": "Bolder", 9 | "size": "large", 10 | "text": "${title}" 11 | }, 12 | { 13 | "type": "TextBlock", 14 | "text": "${description}", 15 | "wrap": true 16 | } 17 | ], 18 | "actions": [ 19 | { 20 | "id": "originLocation", 21 | "type": "VivaAction.GetLocation", 22 | "title": "Select location on the map", 23 | "parameters": { 24 | "chooseLocationOnMap": true 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Hosted workbench", 6 | "type": "edge", 7 | "request": "launch", 8 | "url": "https://{tenantDomain}/_layouts/workbench.aspx", 9 | "webRoot": "${workspaceRoot}", 10 | "sourceMaps": true, 11 | "sourceMapPathOverrides": { 12 | "webpack:///.././src/*": "${webRoot}/src/*", 13 | "webpack:///../../../src/*": "${webRoot}/src/*", 14 | "webpack:///../../../../src/*": "${webRoot}/src/*", 15 | "webpack:///../../../../../src/*": "${webRoot}/src/*" 16 | }, 17 | "runtimeArgs": [ 18 | "--remote-debugging-port=9222", 19 | "-incognito" 20 | ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "@microsoft/generator-sharepoint": { 3 | "plusBeta": false, 4 | "isCreatingSolution": true, 5 | "nodeVersion": "16.18.1", 6 | "sdksVersions": { 7 | "@microsoft/microsoft-graph-client": "3.0.2", 8 | "@microsoft/teams-js": "2.9.1" 9 | }, 10 | "version": "1.17.1", 11 | "libraryName": "ace-image-viewer", 12 | "libraryId": "a08a0220-abeb-49c1-bcb6-29c14114dda9", 13 | "environment": "spo", 14 | "packageManager": "npm", 15 | "solutionName": "AceImageViewer", 16 | "solutionShortDescription": "AceImageViewer description", 17 | "skipFeatureDeployment": true, 18 | "isDomainIsolated": false, 19 | "componentType": "adaptiveCardExtension", 20 | "aceTemplateType": "Image" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "@microsoft/generator-sharepoint": { 3 | "plusBeta": false, 4 | "isCreatingSolution": true, 5 | "nodeVersion": "16.18.1", 6 | "sdksVersions": { 7 | "@microsoft/microsoft-graph-client": "3.0.2", 8 | "@microsoft/teams-js": "2.9.1" 9 | }, 10 | "version": "1.17.1", 11 | "libraryName": "ace-share-point-rest", 12 | "libraryId": "2410765d-2565-43d6-99a0-895156cb1cfe", 13 | "environment": "spo", 14 | "packageManager": "npm", 15 | "solutionName": "AceSharePointRest", 16 | "solutionShortDescription": "AceSharePointRest description", 17 | "skipFeatureDeployment": true, 18 | "isDomainIsolated": false, 19 | "componentType": "adaptiveCardExtension", 20 | "aceTemplateType": "Basic" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demos/03-geo-location/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "@microsoft/generator-sharepoint": { 3 | "plusBeta": false, 4 | "isCreatingSolution": true, 5 | "nodeVersion": "16.18.1", 6 | "sdksVersions": { 7 | "@microsoft/microsoft-graph-client": "3.0.2", 8 | "@microsoft/teams-js": "2.9.1" 9 | }, 10 | "version": "1.17.1", 11 | "libraryName": "ace-campus-shuttle", 12 | "libraryId": "02d4dbbf-164a-47c5-9628-de6d2c35b76e", 13 | "environment": "spo", 14 | "packageManager": "npm", 15 | "solutionName": "AceCampusShuttle", 16 | "solutionShortDescription": "AceCampusShuttle description", 17 | "skipFeatureDeployment": true, 18 | "isDomainIsolated": false, 19 | "componentType": "adaptiveCardExtension", 20 | "aceTemplateType": "PrimaryText" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/src/adaptiveCardExtensions/aceImageViewer/quickView/QuickView.ts: -------------------------------------------------------------------------------- 1 | import { ISPFxAdaptiveCard, BaseAdaptiveCardView } from '@microsoft/sp-adaptive-card-extension-base'; 2 | // import * as strings from 'AceImageViewerAdaptiveCardExtensionStrings'; 3 | import { IAceImageViewerAdaptiveCardExtensionProps, IAceImageViewerAdaptiveCardExtensionState } from '../AceImageViewerAdaptiveCardExtension'; 4 | import { IMarsRoverPhoto } from '../nasa.service'; 5 | 6 | export class QuickView extends BaseAdaptiveCardView< 7 | IAceImageViewerAdaptiveCardExtensionProps, 8 | IAceImageViewerAdaptiveCardExtensionState, 9 | IMarsRoverPhoto 10 | > { 11 | public get data(): IMarsRoverPhoto { 12 | return this.state.roverPhotos[this.state.currentIndex]; 13 | } 14 | 15 | public get template(): ISPFxAdaptiveCard { 16 | return require('./template/QuickViewTemplate.json'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/CampusShuttlePropertyPane.ts: -------------------------------------------------------------------------------- 1 | import { IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-property-pane'; 2 | import * as strings from 'CampusShuttleAdaptiveCardExtensionStrings'; 3 | 4 | export class CampusShuttlePropertyPane { 5 | public getPropertyPaneConfiguration(): IPropertyPaneConfiguration { 6 | return { 7 | pages: [ 8 | { 9 | header: { description: strings.PropertyPaneDescription }, 10 | groups: [ 11 | { 12 | groupFields: [ 13 | PropertyPaneTextField('title', { 14 | label: strings.TitleFieldLabel 15 | }), 16 | PropertyPaneTextField('listId', { 17 | label: 'List ID (GUID)' 18 | }) 19 | ] 20 | } 21 | ] 22 | } 23 | ] 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/SharePointRestPropertyPane.ts: -------------------------------------------------------------------------------- 1 | import { IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-property-pane'; 2 | import * as strings from 'SharePointRestAdaptiveCardExtensionStrings'; 3 | 4 | export class SharePointRestPropertyPane { 5 | public getPropertyPaneConfiguration(): IPropertyPaneConfiguration { 6 | return { 7 | pages: [ 8 | { 9 | header: { description: strings.PropertyPaneDescription }, 10 | groups: [ 11 | { 12 | groupFields: [ 13 | PropertyPaneTextField('title', { 14 | label: strings.TitleFieldLabel 15 | }), 16 | PropertyPaneTextField('listId', { 17 | label: 'List ID (GUID)' 18 | }) 19 | ] 20 | } 21 | ] 22 | } 23 | ] 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@microsoft/rush-stack-compiler-4.5/includes/tsconfig-web.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "forceConsistentCasingInFileNames": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "jsx": "react", 9 | "declaration": true, 10 | "sourceMap": true, 11 | "experimentalDecorators": true, 12 | "skipLibCheck": true, 13 | "outDir": "lib", 14 | "inlineSources": false, 15 | "strictNullChecks": false, 16 | "noImplicitAny": true, 17 | 18 | "typeRoots": [ 19 | "./node_modules/@types", 20 | "./node_modules/@microsoft" 21 | ], 22 | "types": [ 23 | "webpack-env" 24 | ], 25 | "lib": [ 26 | "es5", 27 | "dom", 28 | "es2015.collection", 29 | "es2015.promise" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.tsx" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /demos/03-geo-location/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@microsoft/rush-stack-compiler-4.5/includes/tsconfig-web.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "forceConsistentCasingInFileNames": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "jsx": "react", 9 | "declaration": true, 10 | "sourceMap": true, 11 | "experimentalDecorators": true, 12 | "skipLibCheck": true, 13 | "outDir": "lib", 14 | "inlineSources": false, 15 | "strictNullChecks": false, 16 | "noImplicitAny": true, 17 | 18 | "typeRoots": [ 19 | "./node_modules/@types", 20 | "./node_modules/@microsoft" 21 | ], 22 | "types": [ 23 | "webpack-env" 24 | ], 25 | "lib": [ 26 | "es5", 27 | "dom", 28 | "es2015.collection", 29 | "es2015.promise" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.tsx" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@microsoft/rush-stack-compiler-4.5/includes/tsconfig-web.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "forceConsistentCasingInFileNames": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "jsx": "react", 9 | "declaration": true, 10 | "sourceMap": true, 11 | "experimentalDecorators": true, 12 | "skipLibCheck": true, 13 | "outDir": "lib", 14 | "inlineSources": false, 15 | "strictNullChecks": false, 16 | "noImplicitAny": true, 17 | 18 | "typeRoots": [ 19 | "./node_modules/@types", 20 | "./node_modules/@microsoft" 21 | ], 22 | "types": [ 23 | "webpack-env" 24 | ], 25 | "lib": [ 26 | "es5", 27 | "dom", 28 | "es2015.collection", 29 | "es2015.promise" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.tsx" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/quickView/template/NewItemQuickView.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 3 | "type": "AdaptiveCard", 4 | "version": "1.5", 5 | "body": [{ 6 | "type": "Container", 7 | "separator": true, 8 | "items": [ 9 | { 10 | "type": "Input.Text", 11 | "id": "title", 12 | "label": "Title", 13 | "placeholder": "Title", 14 | "isMultiline": false 15 | }, 16 | { 17 | "type": "Input.Text", 18 | "id": "description", 19 | "label": "Description", 20 | "placeholder": "Description", 21 | "isMultiline": true 22 | }, 23 | { 24 | "type": "ActionSet", 25 | "actions": [{ 26 | "type": "Action.Submit", 27 | "id": "save", 28 | "title": "Save", 29 | "style": "positive" 30 | }] 31 | } 32 | ] 33 | }] 34 | } 35 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ace-share-point-rest", 3 | "version": "0.0.1", 4 | "private": true, 5 | "engines": { 6 | "node": ">=16.13.0 <17.0.0" 7 | }, 8 | "main": "lib/index.js", 9 | "scripts": { 10 | "build": "gulp bundle", 11 | "clean": "gulp clean", 12 | "test": "gulp test" 13 | }, 14 | "dependencies": { 15 | "tslib": "2.3.1", 16 | "@microsoft/sp-core-library": "1.17.1", 17 | "@microsoft/sp-property-pane": "1.17.1", 18 | "@microsoft/sp-adaptive-card-extension-base": "1.17.1" 19 | }, 20 | "devDependencies": { 21 | "@microsoft/rush-stack-compiler-4.5": "0.4.0", 22 | "@rushstack/eslint-config": "2.5.1", 23 | "@microsoft/eslint-plugin-spfx": "1.17.1", 24 | "@microsoft/eslint-config-spfx": "1.17.1", 25 | "@microsoft/sp-build-web": "1.17.1", 26 | "@types/webpack-env": "~1.15.2", 27 | "ajv": "^6.12.5", 28 | "eslint": "8.7.0", 29 | "gulp": "4.0.2", 30 | "typescript": "4.5.5", 31 | "@microsoft/sp-module-interfaces": "1.17.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ace-image-viewer", 3 | "version": "0.0.1", 4 | "private": true, 5 | "engines": { 6 | "node": ">=16.13.0 <17.0.0" 7 | }, 8 | "main": "lib/index.js", 9 | "scripts": { 10 | "build": "gulp bundle", 11 | "clean": "gulp clean", 12 | "test": "gulp test" 13 | }, 14 | "dependencies": { 15 | "tslib": "2.3.1", 16 | "@microsoft/sp-core-library": "1.17.1", 17 | "@microsoft/sp-property-pane": "1.17.1", 18 | "@microsoft/sp-adaptive-card-extension-base": "1.17.1" 19 | }, 20 | "devDependencies": { 21 | "@microsoft/rush-stack-compiler-4.5": "0.4.0", 22 | "@rushstack/eslint-config": "2.5.1", 23 | "@microsoft/eslint-plugin-spfx": "1.17.1", 24 | "@microsoft/eslint-config-spfx": "1.17.1", 25 | "@microsoft/sp-build-web": "1.17.1", 26 | "@types/webpack-env": "~1.15.2", 27 | "ajv": "^6.12.5", 28 | "eslint": "8.7.0", 29 | "gulp": "4.0.2", 30 | "typescript": "4.5.5", 31 | "@microsoft/sp-module-interfaces": "1.17.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /demos/03-geo-location/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ace-campus-shuttle", 3 | "version": "0.0.1", 4 | "private": true, 5 | "engines": { 6 | "node": ">=16.13.0 <17.0.0" 7 | }, 8 | "main": "lib/index.js", 9 | "scripts": { 10 | "build": "gulp bundle", 11 | "clean": "gulp clean", 12 | "test": "gulp test" 13 | }, 14 | "dependencies": { 15 | "tslib": "2.3.1", 16 | "@microsoft/sp-core-library": "1.17.1", 17 | "@microsoft/sp-property-pane": "1.17.1", 18 | "@microsoft/sp-adaptive-card-extension-base": "1.17.1" 19 | }, 20 | "devDependencies": { 21 | "@microsoft/rush-stack-compiler-4.5": "0.4.0", 22 | "@rushstack/eslint-config": "2.5.1", 23 | "@microsoft/eslint-plugin-spfx": "1.17.1", 24 | "@microsoft/eslint-config-spfx": "1.17.1", 25 | "@microsoft/sp-build-web": "1.17.1", 26 | "@types/webpack-env": "~1.15.2", 27 | "ajv": "^6.12.5", 28 | "eslint": "8.7.0", 29 | "gulp": "4.0.2", 30 | "typescript": "4.5.5", 31 | "@microsoft/sp-module-interfaces": "1.17.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/SaveTrip.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseAdaptiveCardView, 3 | IActionArguments, 4 | ISPFxAdaptiveCard 5 | } from '@microsoft/sp-adaptive-card-extension-base'; 6 | import { 7 | ICampusShuttleAdaptiveCardExtensionProps, 8 | ICampusShuttleAdaptiveCardExtensionState 9 | } from '../CampusShuttleAdaptiveCardExtension'; 10 | 11 | export interface ISaveTripData { 12 | title: string; 13 | } 14 | 15 | export class SaveTrip extends BaseAdaptiveCardView< 16 | ICampusShuttleAdaptiveCardExtensionProps, 17 | ICampusShuttleAdaptiveCardExtensionState, 18 | ISaveTripData 19 | > { 20 | public get data(): ISaveTripData { 21 | return { 22 | title: 'Trip saved successfully.' 23 | }; 24 | } 25 | 26 | public get template(): ISPFxAdaptiveCard { 27 | return require('./template/SaveTripCard.json'); 28 | } 29 | 30 | public onAction(action: IActionArguments): void { 31 | if (action.id === 'close') { 32 | this.quickViewNavigator.close(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/src/adaptiveCardExtensions/aceImageViewer/quickView/template/QuickViewTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 3 | "version": "1.5", 4 | "type": "AdaptiveCard", 5 | "body": [ 6 | { 7 | "type": "Image", 8 | "url": "${img_src}" 9 | }, 10 | { 11 | "type": "TextBlock", 12 | "text": "${rover.name} rover image #${id}", 13 | "horizontalAlignment": "Center" 14 | }, 15 | { 16 | "type": "TextBlock", 17 | "text": "Photo Details", 18 | "spacing": "Medium", 19 | "separator": true, 20 | "size": "Large", 21 | "weight": "Bolder" 22 | }, 23 | { 24 | "type": "FactSet", 25 | "facts": [ 26 | { 27 | "title": "Rover:", 28 | "value": "${rover.name}" 29 | }, 30 | { 31 | "title": "Camera:", 32 | "value": "${camera.full_name}" 33 | }, 34 | { 35 | "title": "Date taken:", 36 | "value": "${earth_date} (sol ${sol})" 37 | } 38 | ] 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Microsoft SharePoint 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/src/adaptiveCardExtensions/aceImageViewer/nasa.service.ts: -------------------------------------------------------------------------------- 1 | import { AdaptiveCardExtensionContext } from '@microsoft/sp-adaptive-card-extension-base'; 2 | import { HttpClient } from '@microsoft/sp-http'; 3 | 4 | export interface IMarsRoverCamera { 5 | id: number; 6 | name: string; 7 | rover_id: number; 8 | full_name: string; 9 | } 10 | 11 | export interface IMarsRoverVehicle { 12 | id: number; 13 | name: string; 14 | landing_date: Date; 15 | launch_date: Date; 16 | status: string; 17 | } 18 | 19 | export interface IMarsRoverPhoto { 20 | id: number; 21 | sol: number; 22 | camera: IMarsRoverCamera; 23 | rover: IMarsRoverVehicle; 24 | img_src: string; 25 | earth_date: Date; 26 | } 27 | 28 | export const fetchRoverPhotos = async ( 29 | spContext: AdaptiveCardExtensionContext, 30 | apiKey: string, 31 | rover: string, 32 | mars_sol: number): Promise => { 33 | const results: { photos: IMarsRoverPhoto[] } = await ( 34 | await spContext.httpClient.get( 35 | `https://api.nasa.gov/mars-photos/api/v1/rovers/${rover}/photos?sol=${mars_sol}&page=1&api_key=${apiKey}`, 36 | HttpClient.configurations.v1 37 | ) 38 | ).json(); 39 | 40 | return Promise.resolve(results.photos); 41 | } 42 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/assets/campus_locations.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "title": "UF: Reitz Student Union", "latitude": 29.6463258, "longitude": -82.3499756 }, 3 | { "title": "UF: The Hub", "latitude": 29.648018, "longitude": -82.345664 }, 4 | { "title": "UF: Department of Computer and Information Science and Engineering", "latitude": 29.6476101, "longitude": -82.3466208 }, 5 | { "title": "UF: Materials Science and Engineering", "latitude": 29.6476101, "longitude": -82.3466208 }, 6 | { "title": "UF: Turlington Hall", "latitude": 29.6476101, "longitude": -82.3466208 }, 7 | { "title": "UF: McCarty Hall A", "latitude": 29.6476101, "longitude": -82.3466208 }, 8 | { "title": "UF: Peabody Hall", "latitude": 29.6502915, "longitude": -82.3433807 }, 9 | { "title": "UF: Norman Hall", "latitude": 29.6486165, "longitude": -82.3398393 }, 10 | { "title": "UF: Warrington College of Business", "latitude": 29.65093, "longitude": -82.3402091 }, 11 | { "title": "UF: Mechanical and Aerospace Engineering Building A", "latitude": 29.6436917, "longitude": -82.3478054 }, 12 | { "title": "UF: New Physics Building (NPB)", "latitude": 29.6439734, "longitude": -82.3506927 }, 13 | { "title": "UF: Murphree Hall", "latitude": 29.6508923, "longitude": -82.3480633 } 14 | ] 15 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/config/package-solution.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", 3 | "solution": { 4 | "name": "ace-image-viewer-client-side-solution", 5 | "id": "a08a0220-abeb-49c1-bcb6-29c14114dda9", 6 | "version": "1.0.0.0", 7 | "includeClientSideAssets": true, 8 | "skipFeatureDeployment": true, 9 | "isDomainIsolated": false, 10 | "developer": { 11 | "name": "", 12 | "websiteUrl": "", 13 | "privacyUrl": "", 14 | "termsOfUseUrl": "", 15 | "mpnId": "Undefined-1.17.1" 16 | }, 17 | "metadata": { 18 | "shortDescription": { 19 | "default": "AceImageViewer description" 20 | }, 21 | "longDescription": { 22 | "default": "AceImageViewer description" 23 | }, 24 | "screenshotPaths": [], 25 | "videoUrl": "", 26 | "categories": [] 27 | }, 28 | "features": [ 29 | { 30 | "title": "ace-image-viewer Feature", 31 | "description": "The feature that activates elements of the ace-image-viewer solution.", 32 | "id": "e1672ec3-a190-4027-9ea4-66a86f6542e3", 33 | "version": "1.0.0.0" 34 | } 35 | ] 36 | }, 37 | "paths": { 38 | "zippedPackage": "solution/ace-image-viewer.sppkg" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /demos/03-geo-location/config/package-solution.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", 3 | "solution": { 4 | "name": "ace-campus-shuttle-client-side-solution", 5 | "id": "02d4dbbf-164a-47c5-9628-de6d2c35b76e", 6 | "version": "1.0.0.0", 7 | "includeClientSideAssets": true, 8 | "skipFeatureDeployment": true, 9 | "isDomainIsolated": false, 10 | "developer": { 11 | "name": "", 12 | "websiteUrl": "", 13 | "privacyUrl": "", 14 | "termsOfUseUrl": "", 15 | "mpnId": "Undefined-1.17.1" 16 | }, 17 | "metadata": { 18 | "shortDescription": { 19 | "default": "AceCampusShuttle description" 20 | }, 21 | "longDescription": { 22 | "default": "AceCampusShuttle description" 23 | }, 24 | "screenshotPaths": [], 25 | "videoUrl": "", 26 | "categories": [] 27 | }, 28 | "features": [ 29 | { 30 | "title": "ace-campus-shuttle Feature", 31 | "description": "The feature that activates elements of the ace-campus-shuttle solution.", 32 | "id": "2bb124a4-d098-46de-a121-4b18fe702889", 33 | "version": "1.0.0.0" 34 | } 35 | ] 36 | }, 37 | "paths": { 38 | "zippedPackage": "solution/ace-campus-shuttle.sppkg" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/config/package-solution.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", 3 | "solution": { 4 | "name": "ace-share-point-rest-client-side-solution", 5 | "id": "2410765d-2565-43d6-99a0-895156cb1cfe", 6 | "version": "1.0.0.0", 7 | "includeClientSideAssets": true, 8 | "skipFeatureDeployment": true, 9 | "isDomainIsolated": false, 10 | "developer": { 11 | "name": "", 12 | "websiteUrl": "", 13 | "privacyUrl": "", 14 | "termsOfUseUrl": "", 15 | "mpnId": "Undefined-1.17.1" 16 | }, 17 | "metadata": { 18 | "shortDescription": { 19 | "default": "AceSharePointRest description" 20 | }, 21 | "longDescription": { 22 | "default": "AceSharePointRest description" 23 | }, 24 | "screenshotPaths": [], 25 | "videoUrl": "", 26 | "categories": [] 27 | }, 28 | "features": [ 29 | { 30 | "title": "ace-share-point-rest Feature", 31 | "description": "The feature that activates elements of the ace-share-point-rest solution.", 32 | "id": "22817eff-9477-413a-8532-da40ca1b75f7", 33 | "version": "1.0.0.0" 34 | } 35 | ] 36 | }, 37 | "paths": { 38 | "zippedPackage": "solution/ace-share-point-rest.sppkg" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/template/SetDestinationCard.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 3 | "type": "AdaptiveCard", 4 | "version": "1.5", 5 | "body": [ 6 | { 7 | "type": "TextBlock", 8 | "weight": "Bolder", 9 | "text": "${title}" 10 | }, 11 | { 12 | "type": "TextBlock", 13 | "text": "${description}" 14 | }, 15 | { 16 | "type": "TextBlock", 17 | "text": "Select a known location..." 18 | }, 19 | { 20 | "id": "knownDestinationSelection", 21 | "type": "Input.ChoiceSet", 22 | "choices": [ 23 | { 24 | "$data": "${campus_locations}", 25 | "title": "${title}", 26 | "value": "${latitude},${longitude}" 27 | } 28 | ] 29 | }, 30 | { 31 | "type": "TextBlock", 32 | "text": "... or select a specific location on the map:" 33 | } 34 | ], 35 | "actions": [ 36 | { 37 | "id": "destinationLocation", 38 | "type": "VivaAction.GetLocation", 39 | "title": "Select trip destination from map", 40 | "parameters": { "chooseLocationOnMap": true } 41 | }, 42 | { 43 | "id": "save", 44 | "type": "Action.Submit", 45 | "title": "Save destination location", 46 | "style": "positive" 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/CampusShuttleAdaptiveCardExtension.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx/adaptive-card-extension-manifest.schema.json", 3 | "id": "50b971a0-deba-4bf2-854a-6be6a7646db6", 4 | "alias": "CampusShuttleAdaptiveCardExtension", 5 | "componentType": "AdaptiveCardExtension", 6 | 7 | // The "*" signifies that the version should be taken from the package.json 8 | "version": "*", 9 | "manifestVersion": 2, 10 | 11 | // If true, the component can only be installed on sites where Custom Script is allowed. 12 | // Components that allow authors to embed arbitrary script code should set this to true. 13 | // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f 14 | "requiresCustomScript": false, 15 | "supportedHosts": ["Dashboard"], 16 | "preconfiguredEntries": [{ 17 | "groupId": "bd067b1e-3ad5-4d5d-a5fe-505f07d7f59c", // Dashboard 18 | "group": { "default": "Dashboard" }, 19 | "title": { "default": "Campus Shuttle" }, 20 | "description": { "default": "Campus Shuttle description" }, 21 | "iconImageUrl": "https://res.cdn.office.net/files/fabric-cdn-prod_20230308.001/assets/brand-icons/product-monoline/svg/vivaconnections_32x1.svg", 22 | "properties": { 23 | "title": "Campus Shuttle" 24 | }, 25 | "cardSize": "Medium" 26 | }] 27 | } -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/SharePointRestAdaptiveCardExtension.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx/adaptive-card-extension-manifest.schema.json", 3 | "id": "609e387c-07bd-46b1-80ff-e7ef40269147", 4 | "alias": "SharePointRestAdaptiveCardExtension", 5 | "componentType": "AdaptiveCardExtension", 6 | 7 | // The "*" signifies that the version should be taken from the package.json 8 | "version": "*", 9 | "manifestVersion": 2, 10 | 11 | // If true, the component can only be installed on sites where Custom Script is allowed. 12 | // Components that allow authors to embed arbitrary script code should set this to true. 13 | // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f 14 | "requiresCustomScript": false, 15 | "supportedHosts": ["Dashboard"], 16 | "preconfiguredEntries": [{ 17 | "groupId": "bd067b1e-3ad5-4d5d-a5fe-505f07d7f59c", // Dashboard 18 | "group": { "default": "Dashboard" }, 19 | "title": { "default": "SharePoint REST" }, 20 | "description": { "default": "SharePoint REST description" }, 21 | "iconImageUrl": "https://res.cdn.office.net/files/fabric-cdn-prod_20230308.001/assets/brand-icons/product-monoline/svg/vivaconnections_32x1.svg", 22 | "properties": { 23 | "title": "SharePoint REST" 24 | }, 25 | "cardSize": "Medium" 26 | }] 27 | } -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/quickView/QuickView.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ISPFxAdaptiveCard, 3 | BaseAdaptiveCardView, 4 | IActionArguments // << add 5 | } from '@microsoft/sp-adaptive-card-extension-base'; 6 | // import * as strings from 'SharePointRestAdaptiveCardExtensionStrings'; 7 | import { ISharePointRestAdaptiveCardExtensionProps, ISharePointRestAdaptiveCardExtensionState } from '../SharePointRestAdaptiveCardExtension'; 8 | 9 | import { IListItem } from '../sp.service'; 10 | 11 | export interface IQuickViewData extends IListItem { 12 | previousEnabled: boolean; 13 | nextEnabled: boolean; 14 | } 15 | 16 | export class QuickView extends BaseAdaptiveCardView< 17 | ISharePointRestAdaptiveCardExtensionProps, 18 | ISharePointRestAdaptiveCardExtensionState, 19 | IQuickViewData 20 | > { 21 | public get data(): IQuickViewData { 22 | return { 23 | previousEnabled: this.state.currentIndex !== 0, 24 | nextEnabled: this.state.currentIndex !== (this.state.listItems.length - 1), 25 | ...(this.state.listItems[this.state.currentIndex]) 26 | }; 27 | } 28 | 29 | public get template(): ISPFxAdaptiveCard { 30 | return require('./template/QuickViewTemplate.json'); 31 | } 32 | 33 | public onAction(action: IActionArguments): void { 34 | if (action.type !== 'Submit') { return ;} 35 | 36 | let currentIndex = this.state.currentIndex; 37 | this.setState({ currentIndex: currentIndex + Number(action.id) }); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/quickView/NewItemQuickView.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ISPFxAdaptiveCard, 3 | BaseAdaptiveCardView, 4 | IActionArguments 5 | } from '@microsoft/sp-adaptive-card-extension-base'; 6 | import { 7 | ISharePointRestAdaptiveCardExtensionProps, 8 | ISharePointRestAdaptiveCardExtensionState, 9 | } from '../SharePointRestAdaptiveCardExtension'; 10 | import { 11 | fetchListItems, 12 | addListItem 13 | } from '../sp.service'; 14 | 15 | export interface INewItemQuickView { } 16 | 17 | export class NewItemQuickView extends BaseAdaptiveCardView< 18 | ISharePointRestAdaptiveCardExtensionProps, 19 | ISharePointRestAdaptiveCardExtensionState, 20 | NewItemQuickView 21 | > { 22 | 23 | public get data(): NewItemQuickView { 24 | return undefined; 25 | } 26 | 27 | public get template(): ISPFxAdaptiveCard { 28 | return require('./template/NewItemQuickView.json'); 29 | } 30 | 31 | public onAction(action: IActionArguments): void { 32 | if (action.type === 'Submit') { 33 | (async () => { 34 | // save item 35 | await addListItem( 36 | this.context, 37 | this.properties.listId, 38 | action.data.title, 39 | action.data.description 40 | ); 41 | 42 | // refresh items 43 | this.setState({ listItems: await fetchListItems(this.context, this.properties.listId) }); 44 | 45 | // remove quickview 46 | this.quickViewNavigator.close(); 47 | })(); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/template/StartTripCard.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 3 | "type": "AdaptiveCard", 4 | "version": "1.5", 5 | "body": [ 6 | { 7 | "type": "TextBlock", 8 | "text": "Start a trip", 9 | "size": "Large", 10 | "weight": "Bolder" 11 | }, 12 | { 13 | "type": "TextBlock", 14 | "text": "Select trip status:", 15 | "size": "medium", 16 | "weight": "Bolder" 17 | }, 18 | { 19 | "id": "tripType", 20 | "type": "Input.ChoiceSet", 21 | "value": "$trip.Status", 22 | "choices": [ 23 | { 24 | "title": "en route to pickup", 25 | "value": "en route" 26 | }, 27 | { 28 | "title": "starting trip", 29 | "value": "hired" 30 | } 31 | ] 32 | }, 33 | { 34 | "type": "TextBlock", 35 | "text": "Set trip details:", 36 | "size": "medium", 37 | "weight": "Bolder" 38 | } 39 | ], 40 | "actions": [ 41 | { 42 | "id": "originLocation", 43 | "type": "Action.Submit", 44 | "title": "(1) Select trip origin from map" 45 | }, 46 | { 47 | "id": "destinationLocation", 48 | "type": "Action.Submit", 49 | "title": "(2) Select / set trip destination" 50 | }, 51 | { 52 | "id": "save", 53 | "type": "Action.Submit", 54 | "title": "Save trip", 55 | "style": "positive" 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/SetOrigin.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ISPFxAdaptiveCard, 3 | BaseAdaptiveCardView, 4 | IGetLocationActionArguments 5 | } from '@microsoft/sp-adaptive-card-extension-base'; 6 | import { 7 | 8 | ICampusShuttleAdaptiveCardExtensionProps, 9 | ICampusShuttleAdaptiveCardExtensionState 10 | } from '../CampusShuttleAdaptiveCardExtension'; 11 | 12 | import { ILocation, IListItem } from '../sp.service'; 13 | 14 | export interface ISetOriginData { 15 | title: string; 16 | description: string; 17 | trip: IListItem; 18 | } 19 | 20 | export class SetOrigin extends BaseAdaptiveCardView< 21 | ICampusShuttleAdaptiveCardExtensionProps, 22 | ICampusShuttleAdaptiveCardExtensionState, 23 | ISetOriginData 24 | > { 25 | public get data(): ISetOriginData { 26 | return { 27 | title: 'Set trip starting location', 28 | description: 'Select the trip origin location by selecting it on the map.', 29 | trip: this.state.currentTrip 30 | }; 31 | } 32 | 33 | public get template(): ISPFxAdaptiveCard { 34 | return require('./template/SetOriginCard.json'); 35 | } 36 | 37 | public onAction(action: IGetLocationActionArguments): void { 38 | if (action.type === 'VivaAction.GetLocation'){ 39 | 40 | const currentTrip = this.state.currentTrip; 41 | currentTrip.OriginLocation = { 42 | latitude: action.location.latitude, 43 | longitude: action.location.longitude 44 | }; 45 | 46 | this.setState({ currentTrip: currentTrip }); 47 | 48 | this.quickViewNavigator.pop(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/src/adaptiveCardExtensions/aceImageViewer/AceImageViewerPropertyPane.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IPropertyPaneConfiguration, 3 | PropertyPaneTextField, 4 | PropertyPaneDropdown // << add 5 | } from '@microsoft/sp-property-pane'; 6 | import * as strings from 'AceImageViewerAdaptiveCardExtensionStrings'; 7 | 8 | export class AceImageViewerPropertyPane { 9 | public getPropertyPaneConfiguration(selectedRover: string = 'curiosity'): IPropertyPaneConfiguration { 10 | return { 11 | pages: [ 12 | { 13 | header: { description: strings.PropertyPaneDescription }, 14 | groups: [ 15 | { 16 | groupFields: [ 17 | PropertyPaneTextField('title', { 18 | label: strings.TitleFieldLabel 19 | }), 20 | PropertyPaneTextField('nasa_api_key', { 21 | label: 'NASA API key' 22 | }), 23 | PropertyPaneDropdown('nasa_rover', { 24 | label: 'NASA Mars rover', 25 | options: [ 26 | { index: 0, key: 'curiosity', text: 'Curiosity' }, 27 | { index: 1, key: 'opportunity', text: 'Opportunity' }, 28 | { index: 2, key: 'spirit', text: 'Spirit' } 29 | ], 30 | selectedKey: selectedRover 31 | }), 32 | PropertyPaneTextField('mars_sol', { 33 | label: 'Display photos from Mars day (Sol)' 34 | }) 35 | ] 36 | } 37 | ] 38 | } 39 | ] 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/src/adaptiveCardExtensions/aceImageViewer/AceImageViewerAdaptiveCardExtension.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx/adaptive-card-extension-manifest.schema.json", 3 | "id": "5a79b68e-95c5-44b8-abcf-c8007fd39c86", 4 | "alias": "AceImageViewerAdaptiveCardExtension", 5 | "componentType": "AdaptiveCardExtension", 6 | 7 | // The "*" signifies that the version should be taken from the package.json 8 | "version": "*", 9 | "manifestVersion": 2, 10 | 11 | // If true, the component can only be installed on sites where Custom Script is allowed. 12 | // Components that allow authors to embed arbitrary script code should set this to true. 13 | // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f 14 | "requiresCustomScript": false, 15 | "supportedHosts": ["Dashboard"], 16 | "preconfiguredEntries": [{ 17 | "groupId": "bd067b1e-3ad5-4d5d-a5fe-505f07d7f59c", // Dashboard 18 | "group": { "default": "Dashboard" }, 19 | "title": { "default": "AceImageViewer" }, 20 | "description": { "default": "AceImageViewer description" }, 21 | "iconImageUrl": "https://res.cdn.office.net/files/fabric-cdn-prod_20230308.001/assets/brand-icons/product-monoline/svg/vivaconnections_32x1.svg", 22 | "cardSize": "Large", 23 | "properties": { 24 | "title": "AceImageViewer", 25 | "description": "AceImageViewer description", 26 | "iconProperty": "", // Default to MicrosoftLogo.png 27 | "nasa_api_key": "DEMO_KEY", 28 | "nasa_rover": "curiosity", 29 | "nasa_sol": 1000 30 | } 31 | }] 32 | } 33 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/cardView/CardView.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseBasicCardView, 3 | IBasicCardParameters, 4 | IExternalLinkCardAction, 5 | IQuickViewCardAction, 6 | ICardButton 7 | } from '@microsoft/sp-adaptive-card-extension-base'; 8 | // import * as strings from 'SharePointRestAdaptiveCardExtensionStrings'; 9 | import { 10 | ISharePointRestAdaptiveCardExtensionProps, 11 | ISharePointRestAdaptiveCardExtensionState, 12 | QUICK_VIEW_REGISTRY_ID, 13 | NEW_ITEM_QUICK_VIEW_REGISTRY_ID // << add 14 | } from '../SharePointRestAdaptiveCardExtension'; 15 | 16 | export class CardView extends BaseBasicCardView { 17 | public get cardButtons(): [ICardButton] | [ICardButton, ICardButton] | undefined { 18 | if (!this.properties.listId) { 19 | return undefined; 20 | } else { 21 | return [{ 22 | title: 'Add item', 23 | action: { 24 | type: 'QuickView', 25 | parameters: { view: NEW_ITEM_QUICK_VIEW_REGISTRY_ID } 26 | } 27 | }]; 28 | } 29 | } 30 | 31 | public get data(): IBasicCardParameters { 32 | return { 33 | title: this.properties.title, 34 | primaryText: (this.state.listTitle) 35 | ? `View items in the '${this.state.listTitle}' list` 36 | : `Missing list ID`, 37 | }; 38 | } 39 | 40 | public get onCardSelection(): IQuickViewCardAction | IExternalLinkCardAction | undefined { 41 | return { 42 | type: 'QuickView', 43 | parameters: { 44 | view: QUICK_VIEW_REGISTRY_ID 45 | } 46 | }; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/UpdateTrip.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IActionArguments, 3 | ISPFxAdaptiveCard, 4 | BaseAdaptiveCardView 5 | } from '@microsoft/sp-adaptive-card-extension-base'; 6 | import { 7 | ICampusShuttleAdaptiveCardExtensionProps, 8 | ICampusShuttleAdaptiveCardExtensionState, 9 | QUICK_VIEW_CANCEL_TRIP_REGISTRY_ID 10 | } from '../CampusShuttleAdaptiveCardExtension'; 11 | 12 | import { 13 | STATUS_HIRED, 14 | upsertListItem 15 | } from '../sp.service'; 16 | 17 | export interface IUpdateTripData { 18 | title: string; 19 | } 20 | 21 | export class UpdateTrip extends BaseAdaptiveCardView< 22 | ICampusShuttleAdaptiveCardExtensionProps, 23 | ICampusShuttleAdaptiveCardExtensionState, 24 | IUpdateTripData 25 | > { 26 | public get data(): IUpdateTripData { 27 | return { 28 | title: 'Update the existing trip' 29 | }; 30 | } 31 | 32 | public get template(): ISPFxAdaptiveCard { 33 | return require('./template/UpdateTripCard.json'); 34 | } 35 | 36 | public onAction(action: IActionArguments): void { 37 | if (action.type !== 'Submit') { return; } 38 | 39 | switch (action.id) { 40 | case 'cancel': 41 | this.quickViewNavigator.push(QUICK_VIEW_CANCEL_TRIP_REGISTRY_ID); 42 | break 43 | case 'pickup': 44 | // update current item status 45 | const trip = this.state.currentTrip; 46 | trip.Status = STATUS_HIRED; 47 | 48 | // save to list 49 | (async () => { 50 | await upsertListItem(this.context, this.properties.listId, trip); 51 | })(); 52 | 53 | // update ACE 54 | this.setState({ currentTrip: trip }); 55 | 56 | this.quickViewNavigator.close(); 57 | break 58 | default: 59 | return; 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/ConfirmationQuickView.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IActionArguments, 3 | ISPFxAdaptiveCard, 4 | BaseAdaptiveCardView 5 | } from '@microsoft/sp-adaptive-card-extension-base'; 6 | import { 7 | ICampusShuttleAdaptiveCardExtensionProps, 8 | ICampusShuttleAdaptiveCardExtensionState 9 | } from '../CampusShuttleAdaptiveCardExtension'; 10 | 11 | import { 12 | deleteListItem, 13 | STATUS_AVAILABLE 14 | } from '../sp.service'; 15 | 16 | export interface IConfirmationQuickViewData { 17 | title: string; 18 | description: string; 19 | } 20 | 21 | export class ConfirmationQuickView extends BaseAdaptiveCardView< 22 | ICampusShuttleAdaptiveCardExtensionProps, 23 | ICampusShuttleAdaptiveCardExtensionState, 24 | IConfirmationQuickViewData 25 | > { 26 | constructor(private confirmType: 'cancel' | 'complete') { 27 | super(); 28 | } 29 | 30 | public get data(): IConfirmationQuickViewData { 31 | return { 32 | title: `${this.confirmType.substring(0,1).toUpperCase()}${this.confirmType.substring(1,this.confirmType.length)} Trip`, 33 | description: `Are you sure you want to ${this.confirmType} the trip?` 34 | }; 35 | } 36 | 37 | public get template(): ISPFxAdaptiveCard { 38 | return require('./template/ConfirmationCard.json'); 39 | } 40 | 41 | public onAction(action: IActionArguments): void { 42 | if (action.type === 'Submit' && action.id === 'confirm') { 43 | (async () => { 44 | // delete list item 45 | await deleteListItem(this.context, this.properties.listId, Number(this.state.currentTrip.Id)); 46 | })(); 47 | 48 | // update state to initial value 49 | this.setState({ 50 | currentTrip: { 51 | Title: this.context.pageContext.user.loginName, 52 | Status: STATUS_AVAILABLE 53 | } 54 | }); 55 | 56 | // close 57 | this.quickViewNavigator.close(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/StartTrip.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ISPFxAdaptiveCard, 3 | BaseAdaptiveCardView, 4 | IActionArguments 5 | } from '@microsoft/sp-adaptive-card-extension-base'; 6 | import * as strings from 'CampusShuttleAdaptiveCardExtensionStrings'; 7 | import { 8 | ICampusShuttleAdaptiveCardExtensionProps, 9 | ICampusShuttleAdaptiveCardExtensionState, 10 | QUICK_VIEW_SET_ORIGIN_REGISTRY_ID, 11 | QUICK_VIEW_SET_DESTINATION_REGISTRY_ID, 12 | QUICK_VIEW_SAVE_TRIP_REGISTRY_ID 13 | } from '../CampusShuttleAdaptiveCardExtension'; 14 | 15 | import { IListItem, upsertListItem } from '../sp.service'; 16 | 17 | export interface IStartTripData { 18 | title: string; 19 | trip: IListItem; 20 | } 21 | 22 | export class StartTrip extends BaseAdaptiveCardView< 23 | ICampusShuttleAdaptiveCardExtensionProps, 24 | ICampusShuttleAdaptiveCardExtensionState, 25 | IStartTripData 26 | > { 27 | 28 | public get data(): IStartTripData { 29 | return { 30 | title: strings.Title, 31 | trip: this.state.currentTrip 32 | }; 33 | } 34 | 35 | public get template(): ISPFxAdaptiveCard { 36 | return require('./template/StartTripCard.json'); 37 | } 38 | 39 | public onAction(action: IActionArguments): void { 40 | if (action.type === 'Submit') { 41 | if (action.data.tripType) { 42 | const trip = this.state.currentTrip; 43 | trip.Status = action.data.tripType; 44 | this.setState({ currentTrip: trip }); 45 | } 46 | 47 | if (action.id === 'originLocation') { 48 | this.quickViewNavigator.push(QUICK_VIEW_SET_ORIGIN_REGISTRY_ID); 49 | } else if (action.id === 'destinationLocation') { 50 | this.quickViewNavigator.push(QUICK_VIEW_SET_DESTINATION_REGISTRY_ID); 51 | } else if (action.id === 'save') { 52 | (async () => { 53 | await upsertListItem(this.context, this.properties.listId, this.state.currentTrip); 54 | this.quickViewNavigator.push(QUICK_VIEW_SAVE_TRIP_REGISTRY_ID); 55 | })(); 56 | } 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/quickView/SetDestination.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ISPFxAdaptiveCard, 3 | BaseAdaptiveCardView, 4 | IActionArguments, 5 | IGetLocationActionArguments 6 | } from '@microsoft/sp-adaptive-card-extension-base'; 7 | import { 8 | ICampusShuttleAdaptiveCardExtensionProps, 9 | ICampusShuttleAdaptiveCardExtensionState 10 | } from '../CampusShuttleAdaptiveCardExtension'; 11 | 12 | import { ILocation, IListItem } from '../sp.service'; 13 | 14 | import { sortBy } from '@microsoft/sp-lodash-subset'; 15 | 16 | interface ICampusLocations { 17 | title: string; 18 | latitude: number; 19 | longitude: number; 20 | } 21 | 22 | export interface ISetDestinationData { 23 | title: string; 24 | description: string; 25 | campus_locations: ICampusLocations[]; 26 | trip: IListItem; 27 | } 28 | 29 | const LOCATIONS = require('../assets/campus_locations.json'); 30 | 31 | export class SetDestination extends BaseAdaptiveCardView< 32 | ICampusShuttleAdaptiveCardExtensionProps, 33 | ICampusShuttleAdaptiveCardExtensionState, 34 | ISetDestinationData 35 | > { 36 | public get data(): ISetDestinationData { 37 | return { 38 | title: 'Set trip destination location', 39 | description: 'Pick from a list of known locations, or set the destination by selecting it on the map.', 40 | campus_locations: sortBy(LOCATIONS, (l) => l.title), 41 | trip: this.state.currentTrip 42 | }; 43 | } 44 | 45 | public get template(): ISPFxAdaptiveCard { 46 | return require('./template/SetDestinationCard.json'); 47 | } 48 | 49 | public onAction(action: IActionArguments | IGetLocationActionArguments): void { 50 | const currentTrip = this.state.currentTrip; 51 | 52 | // if picked a location on the map... 53 | if (action.type === 'VivaAction.GetLocation') { 54 | currentTrip.DestinationLocation = { 55 | latitude: action.location.latitude, 56 | longitude: action.location.longitude 57 | }; 58 | this.setState({ currentTrip: currentTrip }); 59 | } else if (action.type === 'Submit' && action.id === 'save') { 60 | // else, check if picked location from dropdown & save it 61 | if (action.data.knownDestinationSelection) { 62 | currentTrip.DestinationLocation = { 63 | latitude: Number(action.data.knownDestinationSelection.split(',')[0]), 64 | longitude: Number(action.data.knownDestinationSelection.split(',')[1]) 65 | }; 66 | 67 | const selectedLocation = LOCATIONS.filter((knownLocation: any) => ( 68 | knownLocation.latitude === (currentTrip.DestinationLocation as ILocation).latitude 69 | && knownLocation.longitude === (currentTrip.DestinationLocation as ILocation).longitude 70 | ))[0]; 71 | currentTrip.DestinationName = selectedLocation.title; 72 | } 73 | this.setState({ currentTrip: currentTrip }); 74 | this.quickViewNavigator.pop(); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/README.md: -------------------------------------------------------------------------------- 1 | # ace-share-point-rest 2 | 3 | ## Summary 4 | 5 | Short summary on functionality and used technologies. 6 | 7 | [picture of the solution in action, if possible] 8 | 9 | ## Used SharePoint Framework Version 10 | 11 | ![version](https://img.shields.io/badge/version-1.17.1-green.svg) 12 | 13 | ## Applies to 14 | 15 | - [SharePoint Framework](https://aka.ms/spfx) 16 | - [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 17 | 18 | > Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram) 19 | 20 | ## Prerequisites 21 | 22 | > Any special pre-requisites? 23 | 24 | ## Solution 25 | 26 | | Solution | Author(s) | 27 | | ----------- | ------------------------------------------------------- | 28 | | folder name | Author details (name, company, twitter alias with link) | 29 | 30 | ## Version history 31 | 32 | | Version | Date | Comments | 33 | | ------- | ---------------- | --------------- | 34 | | 1.1 | March 10, 2021 | Update comment | 35 | | 1.0 | January 29, 2021 | Initial release | 36 | 37 | ## Disclaimer 38 | 39 | **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.** 40 | 41 | --- 42 | 43 | ## Minimal Path to Awesome 44 | 45 | - Clone this repository 46 | - Ensure that you are at the solution folder 47 | - in the command-line run: 48 | - **npm install** 49 | - **gulp serve** 50 | 51 | > Include any additional steps as needed. 52 | 53 | ## Features 54 | 55 | Description of the extension that expands upon high-level summary above. 56 | 57 | This extension illustrates the following concepts: 58 | 59 | - topic 1 60 | - topic 2 61 | - topic 3 62 | 63 | > Notice that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions advance. 64 | 65 | > Share your web part with others through Microsoft 365 Patterns and Practices program to get visibility and exposure. More details on the community, open-source projects and other activities from http://aka.ms/m365pnp. 66 | 67 | ## References 68 | 69 | - [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 70 | - [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview) 71 | - [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis) 72 | - [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview) 73 | - [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development 74 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/README.md: -------------------------------------------------------------------------------- 1 | # ace-image-viewer 2 | 3 | ## Summary 4 | 5 | Short summary on functionality and used technologies. 6 | 7 | [picture of the solution in action, if possible] 8 | 9 | ## Used SharePoint Framework Version 10 | 11 | ![version](https://img.shields.io/badge/version-1.17.1-green.svg) 12 | 13 | ## Applies to 14 | 15 | - [SharePoint Framework](https://aka.ms/spfx) 16 | - [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 17 | 18 | > Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram) 19 | 20 | ## Prerequisites 21 | 22 | > Any special pre-requisites? 23 | 24 | ## Solution 25 | 26 | | Solution | Author(s) | 27 | | ----------- | ------------------------------------------------------- | 28 | | folder name | Author details (name, company, twitter alias with link) | 29 | 30 | ## Version history 31 | 32 | | Version | Date | Comments | 33 | | ------- | ---------------- | --------------- | 34 | | 1.1 | March 10, 2021 | Update comment | 35 | | 1.0 | January 29, 2021 | Initial release | 36 | 37 | ## Disclaimer 38 | 39 | **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.** 40 | 41 | --- 42 | 43 | ## Minimal Path to Awesome 44 | 45 | - Clone this repository 46 | - Ensure that you are at the solution folder 47 | - in the command-line run: 48 | - **npm install** 49 | - **gulp serve** 50 | 51 | > Include any additional steps as needed. 52 | 53 | ## Features 54 | 55 | Description of the extension that expands upon high-level summary above. 56 | 57 | This extension illustrates the following concepts: 58 | 59 | - topic 1 60 | - topic 2 61 | - topic 3 62 | 63 | > Notice that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions advance. 64 | 65 | > Share your web part with others through Microsoft 365 Patterns and Practices program to get visibility and exposure. More details on the community, open-source projects and other activities from http://aka.ms/m365pnp. 66 | 67 | ## References 68 | 69 | - [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 70 | - [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview) 71 | - [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis) 72 | - [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview) 73 | - [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development 74 | -------------------------------------------------------------------------------- /demos/03-geo-location/README.md: -------------------------------------------------------------------------------- 1 | # ace-campus-shuttle 2 | 3 | ## Summary 4 | 5 | Short summary on functionality and used technologies. 6 | 7 | [picture of the solution in action, if possible] 8 | 9 | ## Used SharePoint Framework Version 10 | 11 | ![version](https://img.shields.io/badge/version-1.17.1-green.svg) 12 | 13 | ## Applies to 14 | 15 | - [SharePoint Framework](https://aka.ms/spfx) 16 | - [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 17 | 18 | > Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram) 19 | 20 | ## Prerequisites 21 | 22 | > Any special pre-requisites? 23 | 24 | ## Solution 25 | 26 | | Solution | Author(s) | 27 | | ----------- | ------------------------------------------------------- | 28 | | folder name | Author details (name, company, twitter alias with link) | 29 | 30 | ## Version history 31 | 32 | | Version | Date | Comments | 33 | | ------- | ---------------- | --------------- | 34 | | 1.1 | March 10, 2021 | Update comment | 35 | | 1.0 | January 29, 2021 | Initial release | 36 | 37 | ## Disclaimer 38 | 39 | **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.** 40 | 41 | --- 42 | 43 | ## Minimal Path to Awesome 44 | 45 | - Clone this repository 46 | - Ensure that you are at the solution folder 47 | - in the command-line run: 48 | - **npm install** 49 | - **gulp serve** 50 | 51 | > Include any additional steps as needed. 52 | 53 | ## Features 54 | 55 | Description of the extension that expands upon high-level summary above. 56 | 57 | This extension illustrates the following concepts: 58 | 59 | - topic 1 60 | - topic 2 61 | - topic 3 62 | 63 | > Notice that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions advance. 64 | 65 | > Share your web part with others through Microsoft 365 Patterns and Practices program to get visibility and exposure. More details on the community, open-source projects and other activities from http://aka.ms/m365pnp. 66 | 67 | ## References 68 | 69 | - [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant) 70 | - [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview) 71 | - [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis) 72 | - [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview) 73 | - [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development 74 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/quickView/template/QuickViewTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 3 | "type": "AdaptiveCard", 4 | "version": "1.5", 5 | "body": [ 6 | { 7 | "type": "ColumnSet", 8 | "columns": [ 9 | { 10 | "type": "Column", 11 | "width": "30px", 12 | "items": [ 13 | { 14 | "type": "Image", 15 | "url": "data:image/svg+xml;utf8,", 16 | "size": "Small", 17 | "width": "30px", 18 | "selectAction": { 19 | "type": "Action.Submit", 20 | "id": "-1", 21 | "title": "Previous" 22 | }, 23 | "isVisible": "${previousEnabled}", 24 | "horizontalAlignment": "Left" 25 | } 26 | ], 27 | "verticalContentAlignment": "Center" 28 | }, 29 | { 30 | "type": "Column", 31 | "width": "auto", 32 | "items": [ 33 | { 34 | "type": "Container", 35 | "items": [ 36 | { 37 | "type": "TextBlock", 38 | "text": "(${id}) ${title}", 39 | "horizontalAlignment": "Center", 40 | "size": "Medium", 41 | "weight": "Bolder", 42 | "wrap": true 43 | }, 44 | { 45 | "type": "TextBlock", 46 | "text": "${description}", 47 | "horizontalAlignment": "Center", 48 | "size": "Default", 49 | "wrap": true 50 | } 51 | ] 52 | } 53 | ] 54 | }, 55 | { 56 | "type": "Column", 57 | "width": "30px", 58 | "items": [ 59 | { 60 | "type": "Image", 61 | "url": "data:image/svg+xml;utf8,", 62 | "size": "Small", 63 | "width": "30px", 64 | "selectAction": { 65 | "type": "Action.Submit", 66 | "id": "1", 67 | "title": "Next" 68 | }, 69 | "isVisible": "${nextEnabled}", 70 | "horizontalAlignment": "Right" 71 | } 72 | ], 73 | "verticalContentAlignment": "Center" 74 | } 75 | ], 76 | "spacing": "None", 77 | "horizontalAlignment": "Center", 78 | "height": "stretch", 79 | "style": "emphasis", 80 | "bleed": true 81 | } 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/sp.service.ts: -------------------------------------------------------------------------------- 1 | import { AdaptiveCardExtensionContext } from '@microsoft/sp-adaptive-card-extension-base'; 2 | import { SPHttpClient } from '@microsoft/sp-http' 3 | 4 | export interface IListItem { 5 | id: string; 6 | title: string; 7 | description: string; 8 | index: number; 9 | } 10 | 11 | export const fetchListTitle = async (spContext: AdaptiveCardExtensionContext, listId: string): Promise => { 12 | if (!listId) { return Promise.reject('No listId specified.'); } 13 | 14 | const response = await (await spContext.spHttpClient.get( 15 | `${spContext.pageContext.web.absoluteUrl}/_api/web/lists/GetById(id='${listId}')/?$select=Title`, 16 | SPHttpClient.configurations.v1 17 | )).json(); 18 | 19 | return Promise.resolve(response.Title); 20 | } 21 | 22 | export const fetchListItems = async (spContext: AdaptiveCardExtensionContext, listId: string): Promise => { 23 | if (!listId) { return Promise.reject('No listId specified.'); } 24 | 25 | const response = await (await spContext.spHttpClient.get( 26 | `${spContext.pageContext.web.absoluteUrl}/_api/web/lists/GetById(id='${listId}')/items?$select=ID,Title,Description`, 27 | SPHttpClient.configurations.v1 28 | )).json(); 29 | 30 | if (response.value?.length > 0) { 31 | return Promise.resolve(response.value.map( 32 | (listItem: any, index: number) => { 33 | return { 34 | id: listItem.ID, 35 | title: listItem.Title, 36 | description: listItem.Description, 37 | index: index 38 | }; 39 | } 40 | )); 41 | } else { 42 | return Promise.resolve([]); 43 | } 44 | } 45 | 46 | const getItemEntityType = async (spContext: AdaptiveCardExtensionContext, listId: string): Promise => { 47 | const response: { ListItemEntityTypeFullName: string } = await (await spContext.spHttpClient.get( 48 | `${spContext.pageContext.web.absoluteUrl}/_api/web/lists/GetById(id='${listId}')?$select=ListItemEntityTypeFullName`, 49 | SPHttpClient.configurations.v1 50 | )).json(); 51 | 52 | return response.ListItemEntityTypeFullName; 53 | } 54 | 55 | export const addListItem = async ( 56 | spContext: AdaptiveCardExtensionContext, 57 | listId: string, 58 | listItemTitle: string, 59 | listItemDescription: string): Promise => { 60 | 61 | // get the entity type of list item 62 | const entityListItemType = await getItemEntityType(spContext, listId); 63 | 64 | // create item to send to SP REST API 65 | const newListItem: any = { 66 | '@odata.type': entityListItemType, 67 | Title: listItemTitle, 68 | Description: listItemDescription 69 | }; 70 | 71 | await spContext.spHttpClient.post( 72 | `${spContext.pageContext.web.absoluteUrl}/_api/web/lists/GetById(id='${listId}')/items`, 73 | SPHttpClient.configurations.v1, 74 | { 75 | headers: { 76 | 'ACCEPT': 'application/json; odata.metadata=none', 77 | 'CONTENT-TYPE': 'application/json' 78 | }, 79 | body: JSON.stringify(newListItem) 80 | } 81 | ); 82 | 83 | return Promise.resolve(); 84 | } 85 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/src/adaptiveCardExtensions/sharePointRest/SharePointRestAdaptiveCardExtension.ts: -------------------------------------------------------------------------------- 1 | import { IPropertyPaneConfiguration } from '@microsoft/sp-property-pane'; 2 | import { BaseAdaptiveCardExtension } from '@microsoft/sp-adaptive-card-extension-base'; 3 | import { CardView } from './cardView/CardView'; 4 | import { QuickView } from './quickView/QuickView'; 5 | import { SharePointRestPropertyPane } from './SharePointRestPropertyPane'; 6 | import { 7 | fetchListItems, 8 | fetchListTitle, 9 | IListItem 10 | } from './sp.service'; 11 | import { NewItemQuickView } from './quickView/NewItemQuickView'; 12 | 13 | export interface ISharePointRestAdaptiveCardExtensionProps { 14 | title: string; 15 | listId: string; 16 | } 17 | 18 | export interface ISharePointRestAdaptiveCardExtensionState { 19 | listTitle: string; 20 | listItems: IListItem[]; 21 | currentIndex: number; 22 | } 23 | 24 | const CARD_VIEW_REGISTRY_ID: string = 'SharePointRest_CARD_VIEW'; 25 | export const QUICK_VIEW_REGISTRY_ID: string = 'SharePointRest_QUICK_VIEW'; 26 | export const NEW_ITEM_QUICK_VIEW_REGISTRY_ID: string = 'SharePointRestCrud_NEW_ITEM_QUICK_VIEW'; 27 | 28 | export default class SharePointRestAdaptiveCardExtension extends BaseAdaptiveCardExtension< 29 | ISharePointRestAdaptiveCardExtensionProps, 30 | ISharePointRestAdaptiveCardExtensionState 31 | > { 32 | private _deferredPropertyPane: SharePointRestPropertyPane | undefined; 33 | 34 | public async onInit(): Promise { 35 | this.state = { 36 | currentIndex: 0, 37 | listTitle: '', 38 | listItems: [] 39 | }; 40 | 41 | this.cardNavigator.register(CARD_VIEW_REGISTRY_ID, () => new CardView()); 42 | this.quickViewNavigator.register(QUICK_VIEW_REGISTRY_ID, () => new QuickView()); 43 | this.quickViewNavigator.register(NEW_ITEM_QUICK_VIEW_REGISTRY_ID, () => new NewItemQuickView()); 44 | 45 | if (this.properties.listId) { 46 | Promise.all([ 47 | this.setState({ listTitle: await fetchListTitle(this.context, this.properties.listId) }), 48 | this.setState({ listItems: await fetchListItems(this.context, this.properties.listId) }) 49 | ]); 50 | } 51 | 52 | return Promise.resolve(); 53 | } 54 | 55 | protected loadPropertyPaneResources(): Promise { 56 | return import( 57 | /* webpackChunkName: 'SharePointRest-property-pane'*/ 58 | './SharePointRestPropertyPane' 59 | ) 60 | .then( 61 | (component) => { 62 | this._deferredPropertyPane = new component.SharePointRestPropertyPane(); 63 | } 64 | ); 65 | } 66 | 67 | protected renderCard(): string | undefined { 68 | return CARD_VIEW_REGISTRY_ID; 69 | } 70 | 71 | protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { 72 | return this._deferredPropertyPane?.getPropertyPaneConfiguration(); 73 | } 74 | 75 | protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void { 76 | if (propertyPath === 'listId' && newValue !== oldValue) { 77 | if (newValue) { 78 | (async () => { 79 | this.setState({ listTitle: await fetchListTitle(this.context, newValue) }); 80 | this.setState({ listItems: await fetchListItems(this.context, newValue) }); 81 | })(); 82 | } else { 83 | this.setState({ listTitle: '' }); 84 | this.setState({ listItems: [] }); 85 | } 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/src/adaptiveCardExtensions/aceImageViewer/AceImageViewerAdaptiveCardExtension.ts: -------------------------------------------------------------------------------- 1 | import { IPropertyPaneConfiguration } from '@microsoft/sp-property-pane'; 2 | import { BaseAdaptiveCardExtension } from '@microsoft/sp-adaptive-card-extension-base'; 3 | import { CardView } from './cardView/CardView'; 4 | import { QuickView } from './quickView/QuickView'; 5 | import { AceImageViewerPropertyPane } from './AceImageViewerPropertyPane'; 6 | import { isEmpty } from '@microsoft/sp-lodash-subset' 7 | import { 8 | fetchRoverPhotos, 9 | IMarsRoverPhoto 10 | } from './nasa.service'; 11 | 12 | export interface IAceImageViewerAdaptiveCardExtensionProps { 13 | title: string; 14 | nasa_api_key: string; 15 | nasa_rover: string; 16 | mars_sol: number; 17 | } 18 | 19 | export interface IAceImageViewerAdaptiveCardExtensionState { 20 | currentIndex: number; 21 | roverPhotos: IMarsRoverPhoto[]; 22 | } 23 | 24 | const CARD_VIEW_REGISTRY_ID: string = 'AceImageViewer_CARD_VIEW'; 25 | export const QUICK_VIEW_REGISTRY_ID: string = 'AceImageViewer_QUICK_VIEW'; 26 | 27 | export default class AceImageViewerAdaptiveCardExtension extends BaseAdaptiveCardExtension< 28 | IAceImageViewerAdaptiveCardExtensionProps, 29 | IAceImageViewerAdaptiveCardExtensionState 30 | > { 31 | private _deferredPropertyPane: AceImageViewerPropertyPane | undefined; 32 | 33 | public async onInit(): Promise { 34 | this.state = { 35 | currentIndex: 0, 36 | roverPhotos: [] 37 | }; 38 | 39 | this.cardNavigator.register(CARD_VIEW_REGISTRY_ID, () => new CardView()); 40 | this.quickViewNavigator.register(QUICK_VIEW_REGISTRY_ID, () => new QuickView()); 41 | 42 | if (!isEmpty(this.properties.nasa_api_key) && 43 | !isEmpty(this.properties.nasa_rover) && 44 | !isEmpty(this.properties.mars_sol)) { 45 | this.setState({ 46 | roverPhotos: await fetchRoverPhotos( 47 | this.context, 48 | this.properties.nasa_api_key, 49 | this.properties.nasa_rover, 50 | this.properties.mars_sol) 51 | }); 52 | } 53 | 54 | return Promise.resolve(); 55 | } 56 | 57 | protected loadPropertyPaneResources(): Promise { 58 | return import( 59 | /* webpackChunkName: 'AceImageViewer-property-pane'*/ 60 | './AceImageViewerPropertyPane' 61 | ) 62 | .then( 63 | (component) => { 64 | this._deferredPropertyPane = new component.AceImageViewerPropertyPane(); 65 | } 66 | ); 67 | } 68 | 69 | protected renderCard(): string | undefined { 70 | return CARD_VIEW_REGISTRY_ID; 71 | } 72 | 73 | protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { 74 | return this._deferredPropertyPane?.getPropertyPaneConfiguration(this.properties.nasa_rover); 75 | } 76 | 77 | protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void { 78 | if (propertyPath === 'nasa_rover' && newValue !== oldValue) { 79 | (async () => { 80 | this.setState({ roverPhotos: await fetchRoverPhotos( 81 | this.context, 82 | this.properties.nasa_api_key, 83 | newValue, 84 | this.properties.mars_sol) 85 | }); 86 | }) 87 | } 88 | 89 | if (propertyPath === 'mars_sol' && newValue !== oldValue) { 90 | (async () => { 91 | this.setState({ roverPhotos: await fetchRoverPhotos( 92 | this.context, 93 | this.properties.nasa_api_key, 94 | this.properties.nasa_rover, 95 | newValue) 96 | }); 97 | }) 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/cardView/CardView.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BasePrimaryTextCardView, 3 | IPrimaryTextCardParameters, 4 | ICardButton 5 | } from '@microsoft/sp-adaptive-card-extension-base'; 6 | import * as strings from 'CampusShuttleAdaptiveCardExtensionStrings'; 7 | import { 8 | ICampusShuttleAdaptiveCardExtensionProps, 9 | ICampusShuttleAdaptiveCardExtensionState, 10 | QUICK_VIEW_START_TRIP_REGISTRY_ID, 11 | QUICK_VIEW_UPDATE_TRIP_REGISTRY_ID, 12 | QUICK_VIEW_COMPLETE_TRIP_REGISTRY_ID 13 | } from '../CampusShuttleAdaptiveCardExtension'; 14 | import { 15 | ILocation, 16 | STATUS_AVAILABLE, 17 | STATUS_ENROUTE, 18 | STATUS_HIRED 19 | } from '../sp.service'; 20 | 21 | export class CardView extends BasePrimaryTextCardView { 22 | public get cardButtons(): [ICardButton] | [ICardButton, ICardButton] | undefined { 23 | switch (this.state.currentTrip.Status) { 24 | case STATUS_AVAILABLE: 25 | return [{ 26 | title: 'Book a Trip', 27 | action: { 28 | type: 'QuickView', 29 | parameters: { view: QUICK_VIEW_START_TRIP_REGISTRY_ID } 30 | } 31 | }]; 32 | break; 33 | case STATUS_ENROUTE: 34 | return [ 35 | { 36 | title: 'View pickup location', 37 | action: { 38 | type: 'VivaAction.ShowLocation', 39 | parameters: { 40 | locationCoordinates: { 41 | latitude: (this.state.currentTrip.OriginLocation as ILocation).latitude, 42 | longitude: (this.state.currentTrip.OriginLocation as ILocation).longitude 43 | } 44 | } 45 | } 46 | }, 47 | { 48 | title: 'Update Trip', 49 | action: { 50 | type: 'QuickView', 51 | parameters: { view: QUICK_VIEW_UPDATE_TRIP_REGISTRY_ID } 52 | } 53 | } 54 | ]; 55 | break; 56 | case STATUS_HIRED: 57 | return [ 58 | { 59 | title: 'View dropoff location', 60 | action: { 61 | type: 'VivaAction.ShowLocation', 62 | parameters: { 63 | locationCoordinates: { 64 | latitude: (this.state.currentTrip.DestinationLocation as ILocation).latitude, 65 | longitude: (this.state.currentTrip.DestinationLocation as ILocation).longitude 66 | } 67 | } 68 | } 69 | }, 70 | { 71 | title: 'Complete Trip', 72 | action: { 73 | type: 'QuickView', 74 | parameters: { view: QUICK_VIEW_COMPLETE_TRIP_REGISTRY_ID } 75 | } 76 | } 77 | 78 | ]; 79 | break; 80 | default: 81 | return undefined; 82 | break; 83 | } 84 | } 85 | 86 | public get data(): IPrimaryTextCardParameters { 87 | return { 88 | primaryText: strings.PrimaryText, 89 | description: (this.state.currentTrip.Status === STATUS_AVAILABLE) 90 | ? `available for hire` 91 | : (this.state.currentTrip.Status === STATUS_ENROUTE) 92 | ? `Booked - ${STATUS_ENROUTE} to pickup...` 93 | : (this.state.currentTrip.DestinationName) 94 | ? `Hired - driving passenger to ${this.state.currentTrip.DestinationName}...` 95 | : `Hired - driving passenger to destination...`, 96 | title: this.properties.title 97 | }; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/src/adaptiveCardExtensions/aceImageViewer/cardView/CardView.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseImageCardView, 3 | IImageCardParameters, 4 | IExternalLinkCardAction, 5 | IQuickViewCardAction, 6 | ICardButton, 7 | IActionArguments // << add 8 | } from '@microsoft/sp-adaptive-card-extension-base'; 9 | // import * as strings from 'AceImageViewerAdaptiveCardExtensionStrings'; 10 | import { IAceImageViewerAdaptiveCardExtensionProps, IAceImageViewerAdaptiveCardExtensionState, QUICK_VIEW_REGISTRY_ID } from '../AceImageViewerAdaptiveCardExtension'; 11 | 12 | export class CardView extends BaseImageCardView { 13 | /** 14 | * Buttons will not be visible if card size is 'Medium' with Image Card View. 15 | * It will support up to two buttons for 'Large' card size. 16 | */ 17 | public get cardButtons(): [ICardButton] | [ICardButton, ICardButton] | undefined { 18 | const cardButtons: ICardButton[] = []; 19 | 20 | if (this.state.currentIndex !== 0) { 21 | cardButtons.push({ 22 | title: '<', 23 | id: '-1', 24 | action: { 25 | type: 'Submit', 26 | parameters: {} 27 | } 28 | }); 29 | } 30 | if (this.state.currentIndex !== (this.state.roverPhotos.length - 1)) { 31 | cardButtons.push({ 32 | title: '>', 33 | id: '1', 34 | action: { 35 | type: 'Submit', 36 | parameters: {} 37 | } 38 | }); 39 | } 40 | 41 | return (cardButtons.length === 0) 42 | ? undefined 43 | : (cardButtons.length === 1) 44 | ? [cardButtons[0]] 45 | : [cardButtons[0], cardButtons[1]]; 46 | } 47 | public get data(): IImageCardParameters { 48 | if (!this.properties.nasa_rover || !this.properties.mars_sol) { 49 | return { 50 | primaryText: `Select Mars rover & sol to display photos...`, 51 | imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/Tharsis_and_Valles_Marineris_-_Mars_Orbiter_Mission_%2830055660701%29.png/240px-Tharsis_and_Valles_Marineris_-_Mars_Orbiter_Mission_%2830055660701%29.png', 52 | imageAltText: `Select Mars rover & sol to display photos...`, 53 | title: this.properties.title 54 | } 55 | } else { 56 | const rover = `${this.properties.nasa_rover.substring(0, 1).toUpperCase()}${this.properties.nasa_rover.substring(1)}`; 57 | const roverImage = this.state.roverPhotos[this.state.currentIndex]; 58 | if (roverImage) { 59 | return { 60 | primaryText: `Photos from the Mars rover ${rover} on sol ${this.properties.mars_sol}`, 61 | imageUrl: roverImage.img_src, 62 | imageAltText: `Image ${roverImage.id} taken on ${roverImage.earth_date} from ${rover}'s ${roverImage.camera.full_name} camera.`, 63 | title: this.properties.title 64 | }; 65 | } else { 66 | return { 67 | primaryText: `Please refresh the page to reload the rover photos`, 68 | imageUrl: '', 69 | imageAltText: '', 70 | title: this.properties.title 71 | } 72 | } 73 | } 74 | } 75 | 76 | public get onCardSelection(): IQuickViewCardAction | IExternalLinkCardAction | undefined { 77 | return { 78 | type: 'QuickView', 79 | parameters: { 80 | view: QUICK_VIEW_REGISTRY_ID 81 | } 82 | }; 83 | } 84 | 85 | public onAction(action: IActionArguments): void { 86 | if (action.type !== 'Submit') { return; } 87 | 88 | let currentIndex = this.state.currentIndex; 89 | this.setState({ currentIndex: currentIndex + Number(action.id) }); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/CampusShuttleAdaptiveCardExtension.ts: -------------------------------------------------------------------------------- 1 | import { IPropertyPaneConfiguration } from '@microsoft/sp-property-pane'; 2 | import { BaseAdaptiveCardExtension } from '@microsoft/sp-adaptive-card-extension-base'; 3 | import { CardView } from './cardView/CardView'; 4 | import { CampusShuttlePropertyPane } from './CampusShuttlePropertyPane'; 5 | import { 6 | IListItem, 7 | fetchListItem, 8 | STATUS_AVAILABLE 9 | } from './sp.service'; 10 | import { 11 | StartTrip, 12 | SetOrigin, 13 | SetDestination, 14 | SaveTrip, 15 | UpdateTrip, 16 | ConfirmationQuickView 17 | } from './quickView'; 18 | 19 | export interface ICampusShuttleAdaptiveCardExtensionProps { 20 | title: string; 21 | listId: string; 22 | } 23 | 24 | export interface ICampusShuttleAdaptiveCardExtensionState { 25 | currentTrip: IListItem; 26 | } 27 | 28 | const CARD_VIEW_REGISTRY_ID: string = 'CampusShuttle_CARD_VIEW'; 29 | export const QUICK_VIEW_START_TRIP_REGISTRY_ID: string = 'CampusShuttle_StartTrip_QUICK_VIEW'; 30 | export const QUICK_VIEW_SET_ORIGIN_REGISTRY_ID: string = 'CampusShuttle_SetOrigin_QUICK_VIEW'; 31 | export const QUICK_VIEW_SET_DESTINATION_REGISTRY_ID: string = 'CampusShuttle_SetDestination_QUICK_VIEW'; 32 | export const QUICK_VIEW_SAVE_TRIP_REGISTRY_ID: string = 'CampusShuttle_SaveTrip_QUICK_VIEW'; 33 | export const QUICK_VIEW_CANCEL_TRIP_REGISTRY_ID: string = 'CampusShuttleCopilot_CancelTrip_QUICK_VIEW'; 34 | export const QUICK_VIEW_COMPLETE_TRIP_REGISTRY_ID: string = 'CampusShuttleCopilot_CompleteTrip_QUICK_VIEW'; 35 | export const QUICK_VIEW_UPDATE_TRIP_REGISTRY_ID: string = 'CampusShuttleCopilot_UpdateTrip_QUICK_VIEW'; 36 | 37 | export default class CampusShuttleAdaptiveCardExtension extends BaseAdaptiveCardExtension< 38 | ICampusShuttleAdaptiveCardExtensionProps, 39 | ICampusShuttleAdaptiveCardExtensionState 40 | > { 41 | private _deferredPropertyPane: CampusShuttlePropertyPane | undefined; 42 | 43 | public async onInit(): Promise { 44 | this.state = { 45 | currentTrip: { 46 | Title: this.context.pageContext.user.loginName, 47 | Status: STATUS_AVAILABLE 48 | } 49 | }; 50 | 51 | this.cardNavigator.register(CARD_VIEW_REGISTRY_ID, () => new CardView()); 52 | this.quickViewNavigator.register(QUICK_VIEW_START_TRIP_REGISTRY_ID, () => new StartTrip()); 53 | this.quickViewNavigator.register(QUICK_VIEW_SET_ORIGIN_REGISTRY_ID, () => new SetOrigin()); 54 | this.quickViewNavigator.register(QUICK_VIEW_SET_DESTINATION_REGISTRY_ID, () => new SetDestination()); 55 | this.quickViewNavigator.register(QUICK_VIEW_SAVE_TRIP_REGISTRY_ID, () => new SaveTrip()); 56 | this.quickViewNavigator.register(QUICK_VIEW_CANCEL_TRIP_REGISTRY_ID, () => new ConfirmationQuickView('cancel')); 57 | this.quickViewNavigator.register(QUICK_VIEW_COMPLETE_TRIP_REGISTRY_ID, () => new ConfirmationQuickView('complete')); 58 | this.quickViewNavigator.register(QUICK_VIEW_UPDATE_TRIP_REGISTRY_ID, () => new UpdateTrip()); 59 | 60 | if (this.properties.listId) { 61 | const trip = await fetchListItem(this.context, this.properties.listId); 62 | if (trip) { this.setState({ currentTrip: trip }); } 63 | } 64 | 65 | return Promise.resolve(); 66 | } 67 | 68 | protected loadPropertyPaneResources(): Promise { 69 | return import( 70 | /* webpackChunkName: 'CampusShuttle-property-pane'*/ 71 | './CampusShuttlePropertyPane' 72 | ) 73 | .then( 74 | (component) => { 75 | this._deferredPropertyPane = new component.CampusShuttlePropertyPane(); 76 | } 77 | ); 78 | } 79 | 80 | protected renderCard(): string | undefined { 81 | return CARD_VIEW_REGISTRY_ID; 82 | } 83 | 84 | protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { 85 | return this._deferredPropertyPane?.getPropertyPaneConfiguration(); 86 | } 87 | 88 | protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void { 89 | if (propertyPath === 'listId' && newValue !== oldValue) { 90 | if (newValue) { 91 | (async () => { 92 | const trip = await fetchListItem(this.context, this.properties.listId); 93 | if (trip) { this.setState({ currentTrip: trip }); } 94 | })(); 95 | } 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft SharePoint Framework Training Module - Create Adaptive Card Extensions (ACE) for Microsoft Viva Connections 2 | 3 | This module will introduce you to creating custom Adaptive Card Extensions (ACEs) with the SharePoint Framework (SPFx) that can be used in Viva Connections dashboards. These ACEs will work within all Viva Connections clients including the desktop and mobile apps. 4 | 5 | > This module is also published as a Microsoft Learn module: [Create Adaptive Card Extensions (ACE) for Microsoft Viva Connections](https://learn.microsoft.com/training/modules/sharpeoint-spfx-adaptive-card-extension-card-types) 6 | 7 | ## Lab - Create Adaptive Card Extensions (ACE) for Microsoft Viva Connections 8 | 9 | The lab for this module is available in multiple units within the associated Microsoft Learn module. Use the following links to jump to the specific unit. Each Microsoft Learn unit represents a different lab exercise & demo in the presentation. 10 | 11 | - [Exercise - Create SPFx Basic Card ACE showing SharePoint list data](https://learn.microsoft.com/training/modules/sharpeoint-spfx-adaptive-card-extension-card-types/3-exercise-ace-basic-card-rest) 12 | 13 | In this exercise, you'll create a SharePoint Framework (SPFx) Adaptive Card Extension (ACE) with the Basic Card template that displays items in a SharePoint list and enables the user to add items to the list. 14 | 15 | - [Exercise - Create an SPFx Image Card ACE displaying image carousel](https://learn.microsoft.com/training/modules/sharpeoint-spfx-adaptive-card-extension-card-types/5-exercise-ace-image-card) 16 | 17 | In this exercise, you'll create a SharePoint Framework (SPFx) Adaptive Card Extension (ACE) with the Image Card template that displays images taken by one of the cameras on the selected Mars Rover. 18 | 19 | - [Exercise - Create an SPFx ACE with geo-location capabilities](https://learn.microsoft.com/training/modules/sharpeoint-spfx-adaptive-card-extension-card-types/7-exercise-ace-geo-location-actions) 20 | 21 | In this exercise, you'll create a SharePoint Framework (SPFx) Adaptive Card Extension (ACE) with the Primary Text Card template that uses the geo location capabilities in Viva Connections. 22 | 23 | ## Demos 24 | 25 | - [Create SPFx Basic Card ACE showing SharePoint list data](./demos/01-ace-sp-rest) 26 | - [Create an SPFx Image Card ACE displaying image carousel](./demos/02-ace-image-viewer) 27 | - [Create an SPFx ACE with geo-location capabilities](./demos/03-geo-location) 28 | 29 | ## Contributors 30 | 31 | | Roles | Author(s) | 32 | | -------------------- | -------------------------------------------------------------------------------------------------------------- | 33 | | Lab Manuals / Slides | Andrew Connell (Microsoft MVP, [Voitanos](//github.com/voitanos)) [@andrewconnell](//github.com/andrewconnell) | 34 | | Sponsor / Support | Vesa Juvonen (Microsoft) [@VesaJuvonen](//github.com/VesaJuvonen) | 35 | 36 | ## Version history 37 | 38 | | Version | Date | Comments | 39 | | ------- | ----------------- | ------------------------------------------------ | 40 | | 1.2 | May 5, 2023 | FY2023Q4 content refresh; update to SPFx v1.17.1 | 41 | | 1.1 | February 28, 2023 | FY2023Q3 content refresh; update to SPFx v1.16.1 | 42 | | 1.0 | December 2022 | Initial release | 43 | 44 | ## Disclaimer 45 | 46 | **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.** 47 | 48 | # Contributing 49 | 50 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 51 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 52 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 53 | 54 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 55 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 56 | provided by the bot. You will only need to do this once across all repos using our CLA. 57 | 58 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 59 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 60 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 61 | 62 | 63 | -------------------------------------------------------------------------------- /demos/03-geo-location/src/adaptiveCardExtensions/campusShuttle/sp.service.ts: -------------------------------------------------------------------------------- 1 | import { AdaptiveCardExtensionContext } from '@microsoft/sp-adaptive-card-extension-base'; 2 | import { SPHttpClient } from '@microsoft/sp-http' 3 | 4 | export const STATUS_HIRED = 'hired'; 5 | export const STATUS_ENROUTE = 'en route'; 6 | export const STATUS_AVAILABLE = 'available'; 7 | 8 | export interface ILocation { 9 | latitude: number; 10 | longitude: number; 11 | } 12 | 13 | export interface IListItem { 14 | ['@odata.type']?: string; 15 | Id?: string; 16 | Title: string; 17 | Status: string; 18 | OriginLocation?: string | ILocation; 19 | DestinationName?: string; 20 | DestinationLocation?: string | ILocation; 21 | } 22 | 23 | export const fetchListItem = async (spContext: AdaptiveCardExtensionContext, listId: string): Promise => { 24 | if (!listId) { return Promise.reject('No listId specified.'); } 25 | 26 | const listApiUrl = `${spContext.pageContext.web.absoluteUrl}/_api/web/lists/GetById(id='${listId}')`; 27 | const user = spContext.pageContext.user.loginName; 28 | 29 | const response: { value: IListItem[] } = await (await spContext.spHttpClient.get( 30 | `${listApiUrl}/items/?$select=Id,Title,Status,OriginLocation,DestinationName,DestinationLocation&$filter=Title eq '${user}'&$top=1`, 31 | SPHttpClient.configurations.v1 32 | )).json(); 33 | 34 | if (response.value.length === 0) { return Promise.resolve(undefined); } 35 | 36 | const convertedTrip = response.value[0]; 37 | 38 | if (convertedTrip) { 39 | const origin = convertedTrip.OriginLocation as string; 40 | convertedTrip.OriginLocation = { 41 | latitude: Number(origin.split(',')[0]), 42 | longitude: Number(origin.split(',')[1]) 43 | }; 44 | } 45 | if (convertedTrip) { 46 | const destination = convertedTrip.DestinationLocation as string; 47 | convertedTrip.DestinationLocation = { 48 | latitude: Number(destination.split(',')[0]), 49 | longitude: Number(destination.split(',')[1]) 50 | }; 51 | } 52 | 53 | return Promise.resolve(convertedTrip); 54 | } 55 | 56 | const getItemEntityType = async (spContext: AdaptiveCardExtensionContext, listApiUrl: string): Promise => { 57 | const response: { ListItemEntityTypeFullName: string } = await (await spContext.spHttpClient.get( 58 | `${listApiUrl}?$select=ListItemEntityTypeFullName`, 59 | SPHttpClient.configurations.v1 60 | )).json(); 61 | 62 | return response.ListItemEntityTypeFullName; 63 | } 64 | 65 | const createListItem = async ( 66 | spContext: AdaptiveCardExtensionContext, 67 | listApiUrl: string, 68 | listItem: IListItem): Promise => { 69 | 70 | listItem['@odata.type'] = await getItemEntityType(spContext, listApiUrl); 71 | 72 | await spContext.spHttpClient.post( 73 | `${listApiUrl}/items`, 74 | SPHttpClient.configurations.v1, 75 | { 76 | headers: { 77 | 'ACCEPT': 'application/json; odata.metadata=none', 78 | 'CONTENT-TYPE': 'application/json' 79 | }, 80 | body: JSON.stringify(listItem) 81 | } 82 | ); 83 | 84 | return Promise.resolve(); 85 | } 86 | 87 | export const upsertListItem = async (spContext: AdaptiveCardExtensionContext, listId: string, listItem: IListItem): Promise => { 88 | if (!listId) { return Promise.reject('No listId specified.'); } 89 | 90 | const listApiUrl = `${spContext.pageContext.web.absoluteUrl}/_api/web/lists/GetById(id='${listId}')`; 91 | 92 | const originLocationObj = (listItem.OriginLocation as ILocation); 93 | listItem.OriginLocation = `${originLocationObj.latitude},${originLocationObj.longitude}`; 94 | const destinationLocationObj = (listItem.DestinationLocation as ILocation); 95 | listItem.DestinationLocation = `${destinationLocationObj.latitude},${destinationLocationObj.longitude}`; 96 | 97 | if (!listItem['@odata.type']) { return createListItem(spContext, listApiUrl, listItem); } 98 | 99 | await spContext.spHttpClient.post( 100 | `${listApiUrl}/items(${listItem.Id})`, 101 | SPHttpClient.configurations.v1, 102 | { 103 | headers: { 'IF-MATCH': '*', 'X-HTTP-METHOD': 'MERGE' }, 104 | body: JSON.stringify({ 105 | Title: listItem.Title, 106 | Status: listItem.Status, 107 | OriginLocation: listItem.OriginLocation, 108 | DestinationName: listItem.DestinationName, 109 | DestinationLocation: listItem.DestinationLocation 110 | }) 111 | } 112 | ); 113 | 114 | return Promise.resolve(); 115 | } 116 | 117 | export const deleteListItem = async (spContext: AdaptiveCardExtensionContext, listId: string, listItemId: number): Promise => { 118 | if (!listId) { return Promise.reject('No listId specified.'); } 119 | if (!listItemId) { return Promise.reject('No listItemId specified.'); } 120 | 121 | const listApiUrl = `${spContext.pageContext.web.absoluteUrl}/_api/web/lists/GetById(id='${listId}')`; 122 | 123 | await spContext.spHttpClient.post( 124 | `${listApiUrl}/items(${listItemId})`, 125 | SPHttpClient.configurations.v1, 126 | { 127 | headers: { 'IF-MATCH': '*', 'X-HTTP-METHOD': 'DELETE' } 128 | } 129 | ); 130 | } 131 | -------------------------------------------------------------------------------- /demos/01-ace-sp-rest/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require('@rushstack/eslint-config/patch/modern-module-resolution'); 2 | module.exports = { 3 | extends: ['@microsoft/eslint-config-spfx/lib/profiles/default'], 4 | parserOptions: { tsconfigRootDir: __dirname }, 5 | overrides: [ 6 | { 7 | files: ['*.ts', '*.tsx'], 8 | parser: '@typescript-eslint/parser', 9 | 'parserOptions': { 10 | 'project': './tsconfig.json', 11 | 'ecmaVersion': 2018, 12 | 'sourceType': 'module' 13 | }, 14 | rules: { 15 | // Prevent usage of the JavaScript null value, while allowing code to access existing APIs that may require null. https://www.npmjs.com/package/@rushstack/eslint-plugin 16 | '@rushstack/no-new-null': 1, 17 | // Require Jest module mocking APIs to be called before any other statements in their code block. https://www.npmjs.com/package/@rushstack/eslint-plugin 18 | '@rushstack/hoist-jest-mock': 1, 19 | // Require regular expressions to be constructed from string constants rather than dynamically building strings at runtime. https://www.npmjs.com/package/@rushstack/eslint-plugin-security 20 | '@rushstack/security/no-unsafe-regexp': 1, 21 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 22 | '@typescript-eslint/adjacent-overload-signatures': 1, 23 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 24 | // 25 | // CONFIGURATION: By default, these are banned: String, Boolean, Number, Object, Symbol 26 | '@typescript-eslint/ban-types': [ 27 | 1, 28 | { 29 | 'extendDefaults': false, 30 | 'types': { 31 | 'String': { 32 | 'message': 'Use \'string\' instead', 33 | 'fixWith': 'string' 34 | }, 35 | 'Boolean': { 36 | 'message': 'Use \'boolean\' instead', 37 | 'fixWith': 'boolean' 38 | }, 39 | 'Number': { 40 | 'message': 'Use \'number\' instead', 41 | 'fixWith': 'number' 42 | }, 43 | 'Object': { 44 | 'message': 'Use \'object\' instead, or else define a proper TypeScript type:' 45 | }, 46 | 'Symbol': { 47 | 'message': 'Use \'symbol\' instead', 48 | 'fixWith': 'symbol' 49 | }, 50 | 'Function': { 51 | 'message': 'The \'Function\' type accepts any function-like value.\nIt provides no type safety when calling the function, which can be a common source of bugs.\nIt also accepts things like class declarations, which will throw at runtime as they will not be called with \'new\'.\nIf you are expecting the function to accept certain arguments, you should explicitly define the function shape.' 52 | } 53 | } 54 | } 55 | ], 56 | // RATIONALE: Code is more readable when the type of every variable is immediately obvious. 57 | // Even if the compiler may be able to infer a type, this inference will be unavailable 58 | // to a person who is reviewing a GitHub diff. This rule makes writing code harder, 59 | // but writing code is a much less important activity than reading it. 60 | // 61 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 62 | '@typescript-eslint/explicit-function-return-type': [ 63 | 1, 64 | { 65 | 'allowExpressions': true, 66 | 'allowTypedFunctionExpressions': true, 67 | 'allowHigherOrderFunctions': false 68 | } 69 | ], 70 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 71 | // Rationale to disable: although this is a recommended rule, it is up to dev to select coding style. 72 | // Set to 1 (warning) or 2 (error) to enable. 73 | '@typescript-eslint/explicit-member-accessibility': 0, 74 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 75 | '@typescript-eslint/no-array-constructor': 1, 76 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 77 | // 78 | // RATIONALE: The "any" keyword disables static type checking, the main benefit of using TypeScript. 79 | // This rule should be suppressed only in very special cases such as JSON.stringify() 80 | // where the type really can be anything. Even if the type is flexible, another type 81 | // may be more appropriate such as "unknown", "{}", or "Record". 82 | '@typescript-eslint/no-explicit-any': 1, 83 | // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() 84 | // handler. Thus wherever a Promise arises, the code must either append a catch handler, 85 | // or else return the object to a caller (who assumes this responsibility). Unterminated 86 | // promise chains are a serious issue. Besides causing errors to be silently ignored, 87 | // they can also cause a NodeJS process to terminate unexpectedly. 88 | '@typescript-eslint/no-floating-promises': 2, 89 | // RATIONALE: Catches a common coding mistake. 90 | '@typescript-eslint/no-for-in-array': 2, 91 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 92 | '@typescript-eslint/no-misused-new': 2, 93 | // RATIONALE: The "namespace" keyword is not recommended for organizing code because JavaScript lacks 94 | // a "using" statement to traverse namespaces. Nested namespaces prevent certain bundler 95 | // optimizations. If you are declaring loose functions/variables, it's better to make them 96 | // static members of a class, since classes support property getters and their private 97 | // members are accessible by unit tests. Also, the exercise of choosing a meaningful 98 | // class name tends to produce more discoverable APIs: for example, search+replacing 99 | // the function "reverse()" is likely to return many false matches, whereas if we always 100 | // write "Text.reverse()" is more unique. For large scale organization, it's recommended 101 | // to decompose your code into separate NPM packages, which ensures that component 102 | // dependencies are tracked more conscientiously. 103 | // 104 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 105 | '@typescript-eslint/no-namespace': [ 106 | 1, 107 | { 108 | 'allowDeclarations': false, 109 | 'allowDefinitionFiles': false 110 | } 111 | ], 112 | // RATIONALE: Parameter properties provide a shorthand such as "constructor(public title: string)" 113 | // that avoids the effort of declaring "title" as a field. This TypeScript feature makes 114 | // code easier to write, but arguably sacrifices readability: In the notes for 115 | // "@typescript-eslint/member-ordering" we pointed out that fields are central to 116 | // a class's design, so we wouldn't want to bury them in a constructor signature 117 | // just to save some typing. 118 | // 119 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 120 | // Set to 1 (warning) or 2 (error) to enable the rule 121 | '@typescript-eslint/parameter-properties': 0, 122 | // RATIONALE: When left in shipping code, unused variables often indicate a mistake. Dead code 123 | // may impact performance. 124 | // 125 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 126 | '@typescript-eslint/no-unused-vars': [ 127 | 1, 128 | { 129 | 'vars': 'all', 130 | // Unused function arguments often indicate a mistake in JavaScript code. However in TypeScript code, 131 | // the compiler catches most of those mistakes, and unused arguments are fairly common for type signatures 132 | // that are overriding a base class method or implementing an interface. 133 | 'args': 'none' 134 | } 135 | ], 136 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 137 | '@typescript-eslint/no-use-before-define': [ 138 | 2, 139 | { 140 | 'functions': false, 141 | 'classes': true, 142 | 'variables': true, 143 | 'enums': true, 144 | 'typedefs': true 145 | } 146 | ], 147 | // Disallows require statements except in import statements. 148 | // In other words, the use of forms such as var foo = require("foo") are banned. Instead use ES6 style imports or import foo = require("foo") imports. 149 | '@typescript-eslint/no-var-requires': 'error', 150 | // RATIONALE: The "module" keyword is deprecated except when describing legacy libraries. 151 | // 152 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 153 | '@typescript-eslint/prefer-namespace-keyword': 1, 154 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 155 | // Rationale to disable: it's up to developer to decide if he wants to add type annotations 156 | // Set to 1 (warning) or 2 (error) to enable the rule 157 | '@typescript-eslint/no-inferrable-types': 0, 158 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 159 | // Rationale to disable: declaration of empty interfaces may be helpful for generic types scenarios 160 | '@typescript-eslint/no-empty-interface': 0, 161 | // RATIONALE: This rule warns if setters are defined without getters, which is probably a mistake. 162 | 'accessor-pairs': 1, 163 | // RATIONALE: In TypeScript, if you write x["y"] instead of x.y, it disables type checking. 164 | 'dot-notation': [ 165 | 1, 166 | { 167 | 'allowPattern': '^_' 168 | } 169 | ], 170 | // RATIONALE: Catches code that is likely to be incorrect 171 | 'eqeqeq': 1, 172 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 173 | 'for-direction': 1, 174 | // RATIONALE: Catches a common coding mistake. 175 | 'guard-for-in': 2, 176 | // RATIONALE: If you have more than 2,000 lines in a single source file, it's probably time 177 | // to split up your code. 178 | 'max-lines': ['warn', { max: 2000 }], 179 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 180 | 'no-async-promise-executor': 2, 181 | // RATIONALE: Deprecated language feature. 182 | 'no-caller': 2, 183 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 184 | 'no-compare-neg-zero': 2, 185 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 186 | 'no-cond-assign': 2, 187 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 188 | 'no-constant-condition': 1, 189 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 190 | 'no-control-regex': 2, 191 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 192 | 'no-debugger': 1, 193 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 194 | 'no-delete-var': 2, 195 | // RATIONALE: Catches code that is likely to be incorrect 196 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 197 | 'no-duplicate-case': 2, 198 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 199 | 'no-empty': 1, 200 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 201 | 'no-empty-character-class': 2, 202 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 203 | 'no-empty-pattern': 1, 204 | // RATIONALE: Eval is a security concern and a performance concern. 205 | 'no-eval': 1, 206 | // RATIONALE: Catches code that is likely to be incorrect 207 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 208 | 'no-ex-assign': 2, 209 | // RATIONALE: System types are global and should not be tampered with in a scalable code base. 210 | // If two different libraries (or two versions of the same library) both try to modify 211 | // a type, only one of them can win. Polyfills are acceptable because they implement 212 | // a standardized interoperable contract, but polyfills are generally coded in plain 213 | // JavaScript. 214 | 'no-extend-native': 1, 215 | // Disallow unnecessary labels 216 | 'no-extra-label': 1, 217 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 218 | 'no-fallthrough': 2, 219 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 220 | 'no-func-assign': 1, 221 | // RATIONALE: Catches a common coding mistake. 222 | 'no-implied-eval': 2, 223 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 224 | 'no-invalid-regexp': 2, 225 | // RATIONALE: Catches a common coding mistake. 226 | 'no-label-var': 2, 227 | // RATIONALE: Eliminates redundant code. 228 | 'no-lone-blocks': 1, 229 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 230 | 'no-misleading-character-class': 2, 231 | // RATIONALE: Catches a common coding mistake. 232 | 'no-multi-str': 2, 233 | // RATIONALE: It's generally a bad practice to call "new Thing()" without assigning the result to 234 | // a variable. Either it's part of an awkward expression like "(new Thing()).doSomething()", 235 | // or else implies that the constructor is doing nontrivial computations, which is often 236 | // a poor class design. 237 | 'no-new': 1, 238 | // RATIONALE: Obsolete language feature that is deprecated. 239 | 'no-new-func': 2, 240 | // RATIONALE: Obsolete language feature that is deprecated. 241 | 'no-new-object': 2, 242 | // RATIONALE: Obsolete notation. 243 | 'no-new-wrappers': 1, 244 | // RATIONALE: Catches code that is likely to be incorrect 245 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 246 | 'no-octal': 2, 247 | // RATIONALE: Catches code that is likely to be incorrect 248 | 'no-octal-escape': 2, 249 | // RATIONALE: Catches code that is likely to be incorrect 250 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 251 | 'no-regex-spaces': 2, 252 | // RATIONALE: Catches a common coding mistake. 253 | 'no-return-assign': 2, 254 | // RATIONALE: Security risk. 255 | 'no-script-url': 1, 256 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 257 | 'no-self-assign': 2, 258 | // RATIONALE: Catches a common coding mistake. 259 | 'no-self-compare': 2, 260 | // RATIONALE: This avoids statements such as "while (a = next(), a && a.length);" that use 261 | // commas to create compound expressions. In general code is more readable if each 262 | // step is split onto a separate line. This also makes it easier to set breakpoints 263 | // in the debugger. 264 | 'no-sequences': 1, 265 | // RATIONALE: Catches code that is likely to be incorrect 266 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 267 | 'no-shadow-restricted-names': 2, 268 | // RATIONALE: Obsolete language feature that is deprecated. 269 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 270 | 'no-sparse-arrays': 2, 271 | // RATIONALE: Although in theory JavaScript allows any possible data type to be thrown as an exception, 272 | // such flexibility adds pointless complexity, by requiring every catch block to test 273 | // the type of the object that it receives. Whereas if catch blocks can always assume 274 | // that their object implements the "Error" contract, then the code is simpler, and 275 | // we generally get useful additional information like a call stack. 276 | 'no-throw-literal': 2, 277 | // RATIONALE: Catches a common coding mistake. 278 | 'no-unmodified-loop-condition': 1, 279 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 280 | 'no-unsafe-finally': 2, 281 | // RATIONALE: Catches a common coding mistake. 282 | 'no-unused-expressions': 1, 283 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 284 | 'no-unused-labels': 1, 285 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 286 | 'no-useless-catch': 1, 287 | // RATIONALE: Avoids a potential performance problem. 288 | 'no-useless-concat': 1, 289 | // RATIONALE: The "var" keyword is deprecated because of its confusing "hoisting" behavior. 290 | // Always use "let" or "const" instead. 291 | // 292 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 293 | 'no-var': 2, 294 | // RATIONALE: Generally not needed in modern code. 295 | 'no-void': 1, 296 | // RATIONALE: Obsolete language feature that is deprecated. 297 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 298 | 'no-with': 2, 299 | // RATIONALE: Makes logic easier to understand, since constants always have a known value 300 | // @typescript-eslint\eslint-plugin\dist\configs\eslint-recommended.js 301 | 'prefer-const': 1, 302 | // RATIONALE: Catches a common coding mistake where "resolve" and "reject" are confused. 303 | 'promise/param-names': 2, 304 | // RATIONALE: Catches code that is likely to be incorrect 305 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 306 | 'require-atomic-updates': 2, 307 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 308 | 'require-yield': 1, 309 | // "Use strict" is redundant when using the TypeScript compiler. 310 | 'strict': [ 311 | 2, 312 | 'never' 313 | ], 314 | // RATIONALE: Catches code that is likely to be incorrect 315 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 316 | 'use-isnan': 2, 317 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 318 | // Set to 1 (warning) or 2 (error) to enable. 319 | // Rationale to disable: !!{} 320 | 'no-extra-boolean-cast': 0, 321 | // ==================================================================== 322 | // @microsoft/eslint-plugin-spfx 323 | // ==================================================================== 324 | '@microsoft/spfx/import-requires-chunk-name': 1, 325 | '@microsoft/spfx/no-require-ensure': 2, 326 | '@microsoft/spfx/pair-react-dom-render-unmount': 1 327 | } 328 | }, 329 | { 330 | // For unit tests, we can be a little bit less strict. The settings below revise the 331 | // defaults specified in the extended configurations, as well as above. 332 | files: [ 333 | // Test files 334 | '*.test.ts', 335 | '*.test.tsx', 336 | '*.spec.ts', 337 | '*.spec.tsx', 338 | 339 | // Facebook convention 340 | '**/__mocks__/*.ts', 341 | '**/__mocks__/*.tsx', 342 | '**/__tests__/*.ts', 343 | '**/__tests__/*.tsx', 344 | 345 | // Microsoft convention 346 | '**/test/*.ts', 347 | '**/test/*.tsx' 348 | ], 349 | rules: {} 350 | } 351 | ] 352 | }; -------------------------------------------------------------------------------- /demos/03-geo-location/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require('@rushstack/eslint-config/patch/modern-module-resolution'); 2 | module.exports = { 3 | extends: ['@microsoft/eslint-config-spfx/lib/profiles/default'], 4 | parserOptions: { tsconfigRootDir: __dirname }, 5 | overrides: [ 6 | { 7 | files: ['*.ts', '*.tsx'], 8 | parser: '@typescript-eslint/parser', 9 | 'parserOptions': { 10 | 'project': './tsconfig.json', 11 | 'ecmaVersion': 2018, 12 | 'sourceType': 'module' 13 | }, 14 | rules: { 15 | // Prevent usage of the JavaScript null value, while allowing code to access existing APIs that may require null. https://www.npmjs.com/package/@rushstack/eslint-plugin 16 | '@rushstack/no-new-null': 1, 17 | // Require Jest module mocking APIs to be called before any other statements in their code block. https://www.npmjs.com/package/@rushstack/eslint-plugin 18 | '@rushstack/hoist-jest-mock': 1, 19 | // Require regular expressions to be constructed from string constants rather than dynamically building strings at runtime. https://www.npmjs.com/package/@rushstack/eslint-plugin-security 20 | '@rushstack/security/no-unsafe-regexp': 1, 21 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 22 | '@typescript-eslint/adjacent-overload-signatures': 1, 23 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 24 | // 25 | // CONFIGURATION: By default, these are banned: String, Boolean, Number, Object, Symbol 26 | '@typescript-eslint/ban-types': [ 27 | 1, 28 | { 29 | 'extendDefaults': false, 30 | 'types': { 31 | 'String': { 32 | 'message': 'Use \'string\' instead', 33 | 'fixWith': 'string' 34 | }, 35 | 'Boolean': { 36 | 'message': 'Use \'boolean\' instead', 37 | 'fixWith': 'boolean' 38 | }, 39 | 'Number': { 40 | 'message': 'Use \'number\' instead', 41 | 'fixWith': 'number' 42 | }, 43 | 'Object': { 44 | 'message': 'Use \'object\' instead, or else define a proper TypeScript type:' 45 | }, 46 | 'Symbol': { 47 | 'message': 'Use \'symbol\' instead', 48 | 'fixWith': 'symbol' 49 | }, 50 | 'Function': { 51 | 'message': 'The \'Function\' type accepts any function-like value.\nIt provides no type safety when calling the function, which can be a common source of bugs.\nIt also accepts things like class declarations, which will throw at runtime as they will not be called with \'new\'.\nIf you are expecting the function to accept certain arguments, you should explicitly define the function shape.' 52 | } 53 | } 54 | } 55 | ], 56 | // RATIONALE: Code is more readable when the type of every variable is immediately obvious. 57 | // Even if the compiler may be able to infer a type, this inference will be unavailable 58 | // to a person who is reviewing a GitHub diff. This rule makes writing code harder, 59 | // but writing code is a much less important activity than reading it. 60 | // 61 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 62 | '@typescript-eslint/explicit-function-return-type': [ 63 | 1, 64 | { 65 | 'allowExpressions': true, 66 | 'allowTypedFunctionExpressions': true, 67 | 'allowHigherOrderFunctions': false 68 | } 69 | ], 70 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 71 | // Rationale to disable: although this is a recommended rule, it is up to dev to select coding style. 72 | // Set to 1 (warning) or 2 (error) to enable. 73 | '@typescript-eslint/explicit-member-accessibility': 0, 74 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 75 | '@typescript-eslint/no-array-constructor': 1, 76 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 77 | // 78 | // RATIONALE: The "any" keyword disables static type checking, the main benefit of using TypeScript. 79 | // This rule should be suppressed only in very special cases such as JSON.stringify() 80 | // where the type really can be anything. Even if the type is flexible, another type 81 | // may be more appropriate such as "unknown", "{}", or "Record". 82 | '@typescript-eslint/no-explicit-any': 1, 83 | // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() 84 | // handler. Thus wherever a Promise arises, the code must either append a catch handler, 85 | // or else return the object to a caller (who assumes this responsibility). Unterminated 86 | // promise chains are a serious issue. Besides causing errors to be silently ignored, 87 | // they can also cause a NodeJS process to terminate unexpectedly. 88 | '@typescript-eslint/no-floating-promises': 2, 89 | // RATIONALE: Catches a common coding mistake. 90 | '@typescript-eslint/no-for-in-array': 2, 91 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 92 | '@typescript-eslint/no-misused-new': 2, 93 | // RATIONALE: The "namespace" keyword is not recommended for organizing code because JavaScript lacks 94 | // a "using" statement to traverse namespaces. Nested namespaces prevent certain bundler 95 | // optimizations. If you are declaring loose functions/variables, it's better to make them 96 | // static members of a class, since classes support property getters and their private 97 | // members are accessible by unit tests. Also, the exercise of choosing a meaningful 98 | // class name tends to produce more discoverable APIs: for example, search+replacing 99 | // the function "reverse()" is likely to return many false matches, whereas if we always 100 | // write "Text.reverse()" is more unique. For large scale organization, it's recommended 101 | // to decompose your code into separate NPM packages, which ensures that component 102 | // dependencies are tracked more conscientiously. 103 | // 104 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 105 | '@typescript-eslint/no-namespace': [ 106 | 1, 107 | { 108 | 'allowDeclarations': false, 109 | 'allowDefinitionFiles': false 110 | } 111 | ], 112 | // RATIONALE: Parameter properties provide a shorthand such as "constructor(public title: string)" 113 | // that avoids the effort of declaring "title" as a field. This TypeScript feature makes 114 | // code easier to write, but arguably sacrifices readability: In the notes for 115 | // "@typescript-eslint/member-ordering" we pointed out that fields are central to 116 | // a class's design, so we wouldn't want to bury them in a constructor signature 117 | // just to save some typing. 118 | // 119 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 120 | // Set to 1 (warning) or 2 (error) to enable the rule 121 | '@typescript-eslint/parameter-properties': 0, 122 | // RATIONALE: When left in shipping code, unused variables often indicate a mistake. Dead code 123 | // may impact performance. 124 | // 125 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 126 | '@typescript-eslint/no-unused-vars': [ 127 | 1, 128 | { 129 | 'vars': 'all', 130 | // Unused function arguments often indicate a mistake in JavaScript code. However in TypeScript code, 131 | // the compiler catches most of those mistakes, and unused arguments are fairly common for type signatures 132 | // that are overriding a base class method or implementing an interface. 133 | 'args': 'none' 134 | } 135 | ], 136 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 137 | '@typescript-eslint/no-use-before-define': [ 138 | 2, 139 | { 140 | 'functions': false, 141 | 'classes': true, 142 | 'variables': true, 143 | 'enums': true, 144 | 'typedefs': true 145 | } 146 | ], 147 | // Disallows require statements except in import statements. 148 | // In other words, the use of forms such as var foo = require("foo") are banned. Instead use ES6 style imports or import foo = require("foo") imports. 149 | '@typescript-eslint/no-var-requires': 'error', 150 | // RATIONALE: The "module" keyword is deprecated except when describing legacy libraries. 151 | // 152 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 153 | '@typescript-eslint/prefer-namespace-keyword': 1, 154 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 155 | // Rationale to disable: it's up to developer to decide if he wants to add type annotations 156 | // Set to 1 (warning) or 2 (error) to enable the rule 157 | '@typescript-eslint/no-inferrable-types': 0, 158 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 159 | // Rationale to disable: declaration of empty interfaces may be helpful for generic types scenarios 160 | '@typescript-eslint/no-empty-interface': 0, 161 | // RATIONALE: This rule warns if setters are defined without getters, which is probably a mistake. 162 | 'accessor-pairs': 1, 163 | // RATIONALE: In TypeScript, if you write x["y"] instead of x.y, it disables type checking. 164 | 'dot-notation': [ 165 | 1, 166 | { 167 | 'allowPattern': '^_' 168 | } 169 | ], 170 | // RATIONALE: Catches code that is likely to be incorrect 171 | 'eqeqeq': 1, 172 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 173 | 'for-direction': 1, 174 | // RATIONALE: Catches a common coding mistake. 175 | 'guard-for-in': 2, 176 | // RATIONALE: If you have more than 2,000 lines in a single source file, it's probably time 177 | // to split up your code. 178 | 'max-lines': ['warn', { max: 2000 }], 179 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 180 | 'no-async-promise-executor': 2, 181 | // RATIONALE: Deprecated language feature. 182 | 'no-caller': 2, 183 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 184 | 'no-compare-neg-zero': 2, 185 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 186 | 'no-cond-assign': 2, 187 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 188 | 'no-constant-condition': 1, 189 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 190 | 'no-control-regex': 2, 191 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 192 | 'no-debugger': 1, 193 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 194 | 'no-delete-var': 2, 195 | // RATIONALE: Catches code that is likely to be incorrect 196 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 197 | 'no-duplicate-case': 2, 198 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 199 | 'no-empty': 1, 200 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 201 | 'no-empty-character-class': 2, 202 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 203 | 'no-empty-pattern': 1, 204 | // RATIONALE: Eval is a security concern and a performance concern. 205 | 'no-eval': 1, 206 | // RATIONALE: Catches code that is likely to be incorrect 207 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 208 | 'no-ex-assign': 2, 209 | // RATIONALE: System types are global and should not be tampered with in a scalable code base. 210 | // If two different libraries (or two versions of the same library) both try to modify 211 | // a type, only one of them can win. Polyfills are acceptable because they implement 212 | // a standardized interoperable contract, but polyfills are generally coded in plain 213 | // JavaScript. 214 | 'no-extend-native': 1, 215 | // Disallow unnecessary labels 216 | 'no-extra-label': 1, 217 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 218 | 'no-fallthrough': 2, 219 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 220 | 'no-func-assign': 1, 221 | // RATIONALE: Catches a common coding mistake. 222 | 'no-implied-eval': 2, 223 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 224 | 'no-invalid-regexp': 2, 225 | // RATIONALE: Catches a common coding mistake. 226 | 'no-label-var': 2, 227 | // RATIONALE: Eliminates redundant code. 228 | 'no-lone-blocks': 1, 229 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 230 | 'no-misleading-character-class': 2, 231 | // RATIONALE: Catches a common coding mistake. 232 | 'no-multi-str': 2, 233 | // RATIONALE: It's generally a bad practice to call "new Thing()" without assigning the result to 234 | // a variable. Either it's part of an awkward expression like "(new Thing()).doSomething()", 235 | // or else implies that the constructor is doing nontrivial computations, which is often 236 | // a poor class design. 237 | 'no-new': 1, 238 | // RATIONALE: Obsolete language feature that is deprecated. 239 | 'no-new-func': 2, 240 | // RATIONALE: Obsolete language feature that is deprecated. 241 | 'no-new-object': 2, 242 | // RATIONALE: Obsolete notation. 243 | 'no-new-wrappers': 1, 244 | // RATIONALE: Catches code that is likely to be incorrect 245 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 246 | 'no-octal': 2, 247 | // RATIONALE: Catches code that is likely to be incorrect 248 | 'no-octal-escape': 2, 249 | // RATIONALE: Catches code that is likely to be incorrect 250 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 251 | 'no-regex-spaces': 2, 252 | // RATIONALE: Catches a common coding mistake. 253 | 'no-return-assign': 2, 254 | // RATIONALE: Security risk. 255 | 'no-script-url': 1, 256 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 257 | 'no-self-assign': 2, 258 | // RATIONALE: Catches a common coding mistake. 259 | 'no-self-compare': 2, 260 | // RATIONALE: This avoids statements such as "while (a = next(), a && a.length);" that use 261 | // commas to create compound expressions. In general code is more readable if each 262 | // step is split onto a separate line. This also makes it easier to set breakpoints 263 | // in the debugger. 264 | 'no-sequences': 1, 265 | // RATIONALE: Catches code that is likely to be incorrect 266 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 267 | 'no-shadow-restricted-names': 2, 268 | // RATIONALE: Obsolete language feature that is deprecated. 269 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 270 | 'no-sparse-arrays': 2, 271 | // RATIONALE: Although in theory JavaScript allows any possible data type to be thrown as an exception, 272 | // such flexibility adds pointless complexity, by requiring every catch block to test 273 | // the type of the object that it receives. Whereas if catch blocks can always assume 274 | // that their object implements the "Error" contract, then the code is simpler, and 275 | // we generally get useful additional information like a call stack. 276 | 'no-throw-literal': 2, 277 | // RATIONALE: Catches a common coding mistake. 278 | 'no-unmodified-loop-condition': 1, 279 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 280 | 'no-unsafe-finally': 2, 281 | // RATIONALE: Catches a common coding mistake. 282 | 'no-unused-expressions': 1, 283 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 284 | 'no-unused-labels': 1, 285 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 286 | 'no-useless-catch': 1, 287 | // RATIONALE: Avoids a potential performance problem. 288 | 'no-useless-concat': 1, 289 | // RATIONALE: The "var" keyword is deprecated because of its confusing "hoisting" behavior. 290 | // Always use "let" or "const" instead. 291 | // 292 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 293 | 'no-var': 2, 294 | // RATIONALE: Generally not needed in modern code. 295 | 'no-void': 1, 296 | // RATIONALE: Obsolete language feature that is deprecated. 297 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 298 | 'no-with': 2, 299 | // RATIONALE: Makes logic easier to understand, since constants always have a known value 300 | // @typescript-eslint\eslint-plugin\dist\configs\eslint-recommended.js 301 | 'prefer-const': 1, 302 | // RATIONALE: Catches a common coding mistake where "resolve" and "reject" are confused. 303 | 'promise/param-names': 2, 304 | // RATIONALE: Catches code that is likely to be incorrect 305 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 306 | 'require-atomic-updates': 2, 307 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 308 | 'require-yield': 1, 309 | // "Use strict" is redundant when using the TypeScript compiler. 310 | 'strict': [ 311 | 2, 312 | 'never' 313 | ], 314 | // RATIONALE: Catches code that is likely to be incorrect 315 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 316 | 'use-isnan': 2, 317 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 318 | // Set to 1 (warning) or 2 (error) to enable. 319 | // Rationale to disable: !!{} 320 | 'no-extra-boolean-cast': 0, 321 | // ==================================================================== 322 | // @microsoft/eslint-plugin-spfx 323 | // ==================================================================== 324 | '@microsoft/spfx/import-requires-chunk-name': 1, 325 | '@microsoft/spfx/no-require-ensure': 2, 326 | '@microsoft/spfx/pair-react-dom-render-unmount': 1 327 | } 328 | }, 329 | { 330 | // For unit tests, we can be a little bit less strict. The settings below revise the 331 | // defaults specified in the extended configurations, as well as above. 332 | files: [ 333 | // Test files 334 | '*.test.ts', 335 | '*.test.tsx', 336 | '*.spec.ts', 337 | '*.spec.tsx', 338 | 339 | // Facebook convention 340 | '**/__mocks__/*.ts', 341 | '**/__mocks__/*.tsx', 342 | '**/__tests__/*.ts', 343 | '**/__tests__/*.tsx', 344 | 345 | // Microsoft convention 346 | '**/test/*.ts', 347 | '**/test/*.tsx' 348 | ], 349 | rules: {} 350 | } 351 | ] 352 | }; -------------------------------------------------------------------------------- /demos/02-ace-image-viewer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | require('@rushstack/eslint-config/patch/modern-module-resolution'); 2 | module.exports = { 3 | extends: ['@microsoft/eslint-config-spfx/lib/profiles/default'], 4 | parserOptions: { tsconfigRootDir: __dirname }, 5 | overrides: [ 6 | { 7 | files: ['*.ts', '*.tsx'], 8 | parser: '@typescript-eslint/parser', 9 | 'parserOptions': { 10 | 'project': './tsconfig.json', 11 | 'ecmaVersion': 2018, 12 | 'sourceType': 'module' 13 | }, 14 | rules: { 15 | // Prevent usage of the JavaScript null value, while allowing code to access existing APIs that may require null. https://www.npmjs.com/package/@rushstack/eslint-plugin 16 | '@rushstack/no-new-null': 1, 17 | // Require Jest module mocking APIs to be called before any other statements in their code block. https://www.npmjs.com/package/@rushstack/eslint-plugin 18 | '@rushstack/hoist-jest-mock': 1, 19 | // Require regular expressions to be constructed from string constants rather than dynamically building strings at runtime. https://www.npmjs.com/package/@rushstack/eslint-plugin-security 20 | '@rushstack/security/no-unsafe-regexp': 1, 21 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 22 | '@typescript-eslint/adjacent-overload-signatures': 1, 23 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 24 | // 25 | // CONFIGURATION: By default, these are banned: String, Boolean, Number, Object, Symbol 26 | '@typescript-eslint/ban-types': [ 27 | 1, 28 | { 29 | 'extendDefaults': false, 30 | 'types': { 31 | 'String': { 32 | 'message': 'Use \'string\' instead', 33 | 'fixWith': 'string' 34 | }, 35 | 'Boolean': { 36 | 'message': 'Use \'boolean\' instead', 37 | 'fixWith': 'boolean' 38 | }, 39 | 'Number': { 40 | 'message': 'Use \'number\' instead', 41 | 'fixWith': 'number' 42 | }, 43 | 'Object': { 44 | 'message': 'Use \'object\' instead, or else define a proper TypeScript type:' 45 | }, 46 | 'Symbol': { 47 | 'message': 'Use \'symbol\' instead', 48 | 'fixWith': 'symbol' 49 | }, 50 | 'Function': { 51 | 'message': 'The \'Function\' type accepts any function-like value.\nIt provides no type safety when calling the function, which can be a common source of bugs.\nIt also accepts things like class declarations, which will throw at runtime as they will not be called with \'new\'.\nIf you are expecting the function to accept certain arguments, you should explicitly define the function shape.' 52 | } 53 | } 54 | } 55 | ], 56 | // RATIONALE: Code is more readable when the type of every variable is immediately obvious. 57 | // Even if the compiler may be able to infer a type, this inference will be unavailable 58 | // to a person who is reviewing a GitHub diff. This rule makes writing code harder, 59 | // but writing code is a much less important activity than reading it. 60 | // 61 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 62 | '@typescript-eslint/explicit-function-return-type': [ 63 | 1, 64 | { 65 | 'allowExpressions': true, 66 | 'allowTypedFunctionExpressions': true, 67 | 'allowHigherOrderFunctions': false 68 | } 69 | ], 70 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 71 | // Rationale to disable: although this is a recommended rule, it is up to dev to select coding style. 72 | // Set to 1 (warning) or 2 (error) to enable. 73 | '@typescript-eslint/explicit-member-accessibility': 0, 74 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 75 | '@typescript-eslint/no-array-constructor': 1, 76 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 77 | // 78 | // RATIONALE: The "any" keyword disables static type checking, the main benefit of using TypeScript. 79 | // This rule should be suppressed only in very special cases such as JSON.stringify() 80 | // where the type really can be anything. Even if the type is flexible, another type 81 | // may be more appropriate such as "unknown", "{}", or "Record". 82 | '@typescript-eslint/no-explicit-any': 1, 83 | // RATIONALE: The #1 rule of promises is that every promise chain must be terminated by a catch() 84 | // handler. Thus wherever a Promise arises, the code must either append a catch handler, 85 | // or else return the object to a caller (who assumes this responsibility). Unterminated 86 | // promise chains are a serious issue. Besides causing errors to be silently ignored, 87 | // they can also cause a NodeJS process to terminate unexpectedly. 88 | '@typescript-eslint/no-floating-promises': 2, 89 | // RATIONALE: Catches a common coding mistake. 90 | '@typescript-eslint/no-for-in-array': 2, 91 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 92 | '@typescript-eslint/no-misused-new': 2, 93 | // RATIONALE: The "namespace" keyword is not recommended for organizing code because JavaScript lacks 94 | // a "using" statement to traverse namespaces. Nested namespaces prevent certain bundler 95 | // optimizations. If you are declaring loose functions/variables, it's better to make them 96 | // static members of a class, since classes support property getters and their private 97 | // members are accessible by unit tests. Also, the exercise of choosing a meaningful 98 | // class name tends to produce more discoverable APIs: for example, search+replacing 99 | // the function "reverse()" is likely to return many false matches, whereas if we always 100 | // write "Text.reverse()" is more unique. For large scale organization, it's recommended 101 | // to decompose your code into separate NPM packages, which ensures that component 102 | // dependencies are tracked more conscientiously. 103 | // 104 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 105 | '@typescript-eslint/no-namespace': [ 106 | 1, 107 | { 108 | 'allowDeclarations': false, 109 | 'allowDefinitionFiles': false 110 | } 111 | ], 112 | // RATIONALE: Parameter properties provide a shorthand such as "constructor(public title: string)" 113 | // that avoids the effort of declaring "title" as a field. This TypeScript feature makes 114 | // code easier to write, but arguably sacrifices readability: In the notes for 115 | // "@typescript-eslint/member-ordering" we pointed out that fields are central to 116 | // a class's design, so we wouldn't want to bury them in a constructor signature 117 | // just to save some typing. 118 | // 119 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 120 | // Set to 1 (warning) or 2 (error) to enable the rule 121 | '@typescript-eslint/parameter-properties': 0, 122 | // RATIONALE: When left in shipping code, unused variables often indicate a mistake. Dead code 123 | // may impact performance. 124 | // 125 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 126 | '@typescript-eslint/no-unused-vars': [ 127 | 1, 128 | { 129 | 'vars': 'all', 130 | // Unused function arguments often indicate a mistake in JavaScript code. However in TypeScript code, 131 | // the compiler catches most of those mistakes, and unused arguments are fairly common for type signatures 132 | // that are overriding a base class method or implementing an interface. 133 | 'args': 'none' 134 | } 135 | ], 136 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 137 | '@typescript-eslint/no-use-before-define': [ 138 | 2, 139 | { 140 | 'functions': false, 141 | 'classes': true, 142 | 'variables': true, 143 | 'enums': true, 144 | 'typedefs': true 145 | } 146 | ], 147 | // Disallows require statements except in import statements. 148 | // In other words, the use of forms such as var foo = require("foo") are banned. Instead use ES6 style imports or import foo = require("foo") imports. 149 | '@typescript-eslint/no-var-requires': 'error', 150 | // RATIONALE: The "module" keyword is deprecated except when describing legacy libraries. 151 | // 152 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 153 | '@typescript-eslint/prefer-namespace-keyword': 1, 154 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 155 | // Rationale to disable: it's up to developer to decide if he wants to add type annotations 156 | // Set to 1 (warning) or 2 (error) to enable the rule 157 | '@typescript-eslint/no-inferrable-types': 0, 158 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 159 | // Rationale to disable: declaration of empty interfaces may be helpful for generic types scenarios 160 | '@typescript-eslint/no-empty-interface': 0, 161 | // RATIONALE: This rule warns if setters are defined without getters, which is probably a mistake. 162 | 'accessor-pairs': 1, 163 | // RATIONALE: In TypeScript, if you write x["y"] instead of x.y, it disables type checking. 164 | 'dot-notation': [ 165 | 1, 166 | { 167 | 'allowPattern': '^_' 168 | } 169 | ], 170 | // RATIONALE: Catches code that is likely to be incorrect 171 | 'eqeqeq': 1, 172 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 173 | 'for-direction': 1, 174 | // RATIONALE: Catches a common coding mistake. 175 | 'guard-for-in': 2, 176 | // RATIONALE: If you have more than 2,000 lines in a single source file, it's probably time 177 | // to split up your code. 178 | 'max-lines': ['warn', { max: 2000 }], 179 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 180 | 'no-async-promise-executor': 2, 181 | // RATIONALE: Deprecated language feature. 182 | 'no-caller': 2, 183 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 184 | 'no-compare-neg-zero': 2, 185 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 186 | 'no-cond-assign': 2, 187 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 188 | 'no-constant-condition': 1, 189 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 190 | 'no-control-regex': 2, 191 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 192 | 'no-debugger': 1, 193 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 194 | 'no-delete-var': 2, 195 | // RATIONALE: Catches code that is likely to be incorrect 196 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 197 | 'no-duplicate-case': 2, 198 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 199 | 'no-empty': 1, 200 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 201 | 'no-empty-character-class': 2, 202 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 203 | 'no-empty-pattern': 1, 204 | // RATIONALE: Eval is a security concern and a performance concern. 205 | 'no-eval': 1, 206 | // RATIONALE: Catches code that is likely to be incorrect 207 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 208 | 'no-ex-assign': 2, 209 | // RATIONALE: System types are global and should not be tampered with in a scalable code base. 210 | // If two different libraries (or two versions of the same library) both try to modify 211 | // a type, only one of them can win. Polyfills are acceptable because they implement 212 | // a standardized interoperable contract, but polyfills are generally coded in plain 213 | // JavaScript. 214 | 'no-extend-native': 1, 215 | // Disallow unnecessary labels 216 | 'no-extra-label': 1, 217 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 218 | 'no-fallthrough': 2, 219 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 220 | 'no-func-assign': 1, 221 | // RATIONALE: Catches a common coding mistake. 222 | 'no-implied-eval': 2, 223 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 224 | 'no-invalid-regexp': 2, 225 | // RATIONALE: Catches a common coding mistake. 226 | 'no-label-var': 2, 227 | // RATIONALE: Eliminates redundant code. 228 | 'no-lone-blocks': 1, 229 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 230 | 'no-misleading-character-class': 2, 231 | // RATIONALE: Catches a common coding mistake. 232 | 'no-multi-str': 2, 233 | // RATIONALE: It's generally a bad practice to call "new Thing()" without assigning the result to 234 | // a variable. Either it's part of an awkward expression like "(new Thing()).doSomething()", 235 | // or else implies that the constructor is doing nontrivial computations, which is often 236 | // a poor class design. 237 | 'no-new': 1, 238 | // RATIONALE: Obsolete language feature that is deprecated. 239 | 'no-new-func': 2, 240 | // RATIONALE: Obsolete language feature that is deprecated. 241 | 'no-new-object': 2, 242 | // RATIONALE: Obsolete notation. 243 | 'no-new-wrappers': 1, 244 | // RATIONALE: Catches code that is likely to be incorrect 245 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 246 | 'no-octal': 2, 247 | // RATIONALE: Catches code that is likely to be incorrect 248 | 'no-octal-escape': 2, 249 | // RATIONALE: Catches code that is likely to be incorrect 250 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 251 | 'no-regex-spaces': 2, 252 | // RATIONALE: Catches a common coding mistake. 253 | 'no-return-assign': 2, 254 | // RATIONALE: Security risk. 255 | 'no-script-url': 1, 256 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 257 | 'no-self-assign': 2, 258 | // RATIONALE: Catches a common coding mistake. 259 | 'no-self-compare': 2, 260 | // RATIONALE: This avoids statements such as "while (a = next(), a && a.length);" that use 261 | // commas to create compound expressions. In general code is more readable if each 262 | // step is split onto a separate line. This also makes it easier to set breakpoints 263 | // in the debugger. 264 | 'no-sequences': 1, 265 | // RATIONALE: Catches code that is likely to be incorrect 266 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 267 | 'no-shadow-restricted-names': 2, 268 | // RATIONALE: Obsolete language feature that is deprecated. 269 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 270 | 'no-sparse-arrays': 2, 271 | // RATIONALE: Although in theory JavaScript allows any possible data type to be thrown as an exception, 272 | // such flexibility adds pointless complexity, by requiring every catch block to test 273 | // the type of the object that it receives. Whereas if catch blocks can always assume 274 | // that their object implements the "Error" contract, then the code is simpler, and 275 | // we generally get useful additional information like a call stack. 276 | 'no-throw-literal': 2, 277 | // RATIONALE: Catches a common coding mistake. 278 | 'no-unmodified-loop-condition': 1, 279 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 280 | 'no-unsafe-finally': 2, 281 | // RATIONALE: Catches a common coding mistake. 282 | 'no-unused-expressions': 1, 283 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 284 | 'no-unused-labels': 1, 285 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 286 | 'no-useless-catch': 1, 287 | // RATIONALE: Avoids a potential performance problem. 288 | 'no-useless-concat': 1, 289 | // RATIONALE: The "var" keyword is deprecated because of its confusing "hoisting" behavior. 290 | // Always use "let" or "const" instead. 291 | // 292 | // STANDARDIZED BY: @typescript-eslint\eslint-plugin\dist\configs\recommended.json 293 | 'no-var': 2, 294 | // RATIONALE: Generally not needed in modern code. 295 | 'no-void': 1, 296 | // RATIONALE: Obsolete language feature that is deprecated. 297 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 298 | 'no-with': 2, 299 | // RATIONALE: Makes logic easier to understand, since constants always have a known value 300 | // @typescript-eslint\eslint-plugin\dist\configs\eslint-recommended.js 301 | 'prefer-const': 1, 302 | // RATIONALE: Catches a common coding mistake where "resolve" and "reject" are confused. 303 | 'promise/param-names': 2, 304 | // RATIONALE: Catches code that is likely to be incorrect 305 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 306 | 'require-atomic-updates': 2, 307 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 308 | 'require-yield': 1, 309 | // "Use strict" is redundant when using the TypeScript compiler. 310 | 'strict': [ 311 | 2, 312 | 'never' 313 | ], 314 | // RATIONALE: Catches code that is likely to be incorrect 315 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 316 | 'use-isnan': 2, 317 | // STANDARDIZED BY: eslint\conf\eslint-recommended.js 318 | // Set to 1 (warning) or 2 (error) to enable. 319 | // Rationale to disable: !!{} 320 | 'no-extra-boolean-cast': 0, 321 | // ==================================================================== 322 | // @microsoft/eslint-plugin-spfx 323 | // ==================================================================== 324 | '@microsoft/spfx/import-requires-chunk-name': 1, 325 | '@microsoft/spfx/no-require-ensure': 2, 326 | '@microsoft/spfx/pair-react-dom-render-unmount': 1 327 | } 328 | }, 329 | { 330 | // For unit tests, we can be a little bit less strict. The settings below revise the 331 | // defaults specified in the extended configurations, as well as above. 332 | files: [ 333 | // Test files 334 | '*.test.ts', 335 | '*.test.tsx', 336 | '*.spec.ts', 337 | '*.spec.tsx', 338 | 339 | // Facebook convention 340 | '**/__mocks__/*.ts', 341 | '**/__mocks__/*.tsx', 342 | '**/__tests__/*.ts', 343 | '**/__tests__/*.tsx', 344 | 345 | // Microsoft convention 346 | '**/test/*.ts', 347 | '**/test/*.tsx' 348 | ], 349 | rules: {} 350 | } 351 | ] 352 | }; --------------------------------------------------------------------------------