first commit
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.10.0",
|
||||
"libraryName": "personal-apps",
|
||||
"libraryId": "ab3683c6-0388-47a2-84f5-90d01259d0ed",
|
||||
"environment": "spo",
|
||||
"packageManager": "npm",
|
||||
"framework": "react",
|
||||
"isCreatingSolution": true,
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
parameters:
|
||||
# unique name of the job
|
||||
job_name: deploy_sppkg
|
||||
# friendly name of the job
|
||||
display_name: Upload & deploy *.sppkg to SharePoint app catalog
|
||||
# name of target enviroment deploying to
|
||||
target_environment: ''
|
||||
# app catalog scope (tenant|sitecollection)
|
||||
o365cli_app_catalog_scope: 'tenant'
|
||||
variable_group_name: ''
|
||||
jobs:
|
||||
- deployment: ${{ parameters.job_name }}
|
||||
displayName: ${{ parameters.display_name }}
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
environment: ${{ parameters.target_environment }}
|
||||
variables:
|
||||
- group: ${{parameters.variable_group_name}} #o365_user_login, o365_user_password, o365_app_catalog_site_url
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- checkout: none
|
||||
- download: current
|
||||
artifact: drop
|
||||
patterns: '**/*.sppkg'
|
||||
- script: sudo npm install --global @pnp/office365-cli
|
||||
displayName: Install Office365 CLI
|
||||
- script: o365 login $(o365_app_catalog_site_url) --authType password --userName $(o365_user_login) --password $(o365_user_password)
|
||||
displayName: Login to Office365
|
||||
- script: |
|
||||
CMD_GET_SPPKG_NAME=$(find $(Pipeline.Workspace)/drop -name '*.sppkg' -exec basename {} \;)
|
||||
echo "##vso[task.setvariable variable=SpPkgFileName;isOutput=true]${CMD_GET_SPPKG_NAME}"
|
||||
displayName: Get generated *.sppkg filename
|
||||
name: GetSharePointPackage
|
||||
- script: o365 spo app add --filePath "$(Pipeline.Workspace)/drop/sharepoint/solution/$(GetSharePointPackage.SpPkgFileName)" --appCatalogUrl $(o365_app_catalog_site_url) --scope ${{ parameters.o365cli_app_catalog_scope }} --overwrite
|
||||
displayName: Upload SharePoint package to Site Collection App Catalog
|
||||
- script: o365 spo app deploy --name $(GetSharePointPackage.SpPkgFileName) --appCatalogUrl $(o365_app_catalog_site_url) --scope ${{ parameters.o365cli_app_catalog_scope }}
|
||||
displayName: Deploy SharePoint package
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"personal-apps-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/personalApps/PersonalAppsWebPart.js",
|
||||
"manifest": "./src/webparts/personalApps/PersonalAppsWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"PersonalAppsWebPartStrings": "lib/webparts/personalApps/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
|
||||
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "personal-apps",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"preset": "@voitanos/jest-preset-spfx-react16",
|
||||
"rootDir": "../src",
|
||||
"collectCoverageFrom": [
|
||||
"<rootDir>/**/*.{ts,tsx}",
|
||||
"!<rootDir>/**/*.scss.*",
|
||||
"!<rootDir>/loc/**/*.*"
|
||||
],
|
||||
"coverageReporters": [
|
||||
"text",
|
||||
"json",
|
||||
"lcov",
|
||||
"text-summary",
|
||||
"cobertura"
|
||||
],
|
||||
"reporters": [
|
||||
"default",
|
||||
["jest-junit", {
|
||||
"suiteName": "jest tests",
|
||||
"outputDirectory": "temp/test/junit",
|
||||
"outputName": "junit.xml"
|
||||
}]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "personal-apps-client-side-solution",
|
||||
"id": "ab3683c6-0388-47a2-84f5-90d01259d0ed",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"webApiPermissionRequests": [{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.ReadWrite.All"
|
||||
}]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/personal-apps.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* This script updates the package-solution version analogue to the
|
||||
* the package.json file.
|
||||
*/
|
||||
|
||||
if (process.env.npm_package_version === undefined) {
|
||||
|
||||
throw 'Package version cannot be evaluated';
|
||||
|
||||
}
|
||||
|
||||
// define path to package-solution file
|
||||
const solution = './config/package-solution.json',
|
||||
teams = './teams/manifest.json';
|
||||
|
||||
// require filesystem instance
|
||||
const fs = require('fs');
|
||||
|
||||
// get next automated package version from process variable
|
||||
const nextPkgVersion = process.env.npm_package_version;
|
||||
|
||||
// make sure next build version match
|
||||
const nextVersion = nextPkgVersion.indexOf('-') === -1 ?
|
||||
nextPkgVersion : nextPkgVersion.split('-')[0];
|
||||
|
||||
// Update version in SPFx package-solution if exists
|
||||
if (fs.existsSync(solution)) {
|
||||
|
||||
// read package-solution file
|
||||
const solutionFileContent = fs.readFileSync(solution, 'UTF-8');
|
||||
// parse file as json
|
||||
const solutionContents = JSON.parse(solutionFileContent);
|
||||
|
||||
// set property of version to next version
|
||||
solutionContents.solution.version = nextVersion + '.0';
|
||||
|
||||
// save file
|
||||
fs.writeFileSync(
|
||||
solution,
|
||||
// convert file back to proper json
|
||||
JSON.stringify(solutionContents, null, 2),
|
||||
'UTF-8');
|
||||
|
||||
}
|
||||
|
||||
// Update version in teams manifest if exists
|
||||
if (fs.existsSync(teams)) {
|
||||
|
||||
// read package-solution file
|
||||
const teamsManifestContent = fs.readFileSync(teams, 'UTF-8');
|
||||
// parse file as json
|
||||
const teamsContent = JSON.parse(teamsManifestContent);
|
||||
|
||||
// set property of version to next version
|
||||
teamsContent.version = nextVersion;
|
||||
|
||||
// save file
|
||||
fs.writeFileSync(
|
||||
teams,
|
||||
// convert file back to proper json
|
||||
JSON.stringify(teamsContent, null, 2),
|
||||
'UTF-8');
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
# React My Personal Apps (Links)
|
||||
|
||||
## Summary
|
||||
The Web Part My Personal Apps allows the user define links to Aplications or Sites for quick access.
|
||||
|
||||
This Web Part use MSgraph Open Extension to save the personal information on user object.
|
||||
|
||||
|
||||
|
||||
|
||||
![PersonalApps](./assets/image1.png)
|
||||
|
||||
![PersonalApps](./assets/image11.png)
|
||||
|
||||
![PersonalApps](./assets/image2.png)
|
||||
|
||||
![PersonalApps](./assets/image3.png)
|
||||
|
||||
![PersonalApps](./assets/image4.png)
|
||||
|
||||
![PersonalApps](./assets/image5.png)
|
||||
|
||||
![PersonalApps](./assets/image06.png)
|
||||
|
||||
![PersonalApps](./assets/image7.png)
|
||||
|
||||
![PersonalApps](./assets/image8.png)
|
||||
|
||||
![PersonalApps](./assets/image9.png)
|
||||
|
||||
![PersonalApps](./assets/image10.png)
|
||||
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/version-1.10.0-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react My Personal Apps|João Mendes
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0.0|April 9, 2020|Initial release
|
||||
|
||||
|
||||
## Disclaimer
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome - please follow all the steps.
|
||||
|
||||
- Clone this repository
|
||||
- in the command line run:
|
||||
- `npm install`
|
||||
- `gulp build`
|
||||
- `gulp bundle --ship`
|
||||
- `gulp package-solution --ship`
|
||||
- `Add and Deploy Package to AppCatalog `
|
||||
- `Go to API Management - from SharePoint Admin Center new experience, and Approve the Permission Require to Use Graph API SCOPE User.ReadWrite.All`
|
||||
|
||||
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-my-personal-apps" />
|
||||
|
After Width: | Height: | Size: 426 KiB |
After Width: | Height: | Size: 2.9 MiB |
After Width: | Height: | Size: 344 KiB |
After Width: | Height: | Size: 1.9 MiB |
After Width: | Height: | Size: 1.9 MiB |
After Width: | Height: | Size: 1.9 MiB |
After Width: | Height: | Size: 2.9 MiB |
After Width: | Height: | Size: 594 KiB |
After Width: | Height: | Size: 471 KiB |
After Width: | Height: | Size: 506 KiB |
After Width: | Height: | Size: 1.9 MiB |
|
@ -0,0 +1,67 @@
|
|||
parameters:
|
||||
name: ''
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
demands:
|
||||
- npm
|
||||
- node.js
|
||||
- java
|
||||
variables:
|
||||
npm_config_cache: $(Pipeline.Workspace)/.npm
|
||||
|
||||
steps:
|
||||
- checkout: self
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: 'Use Node 10.x'
|
||||
inputs:
|
||||
versionSpec: 10.x
|
||||
checkLatest: true
|
||||
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
key: npm | $(Agent.OS) | package-lock.json
|
||||
path: $(npm_config_cache)
|
||||
cacheHitVar: CACHE_RESTORED
|
||||
- script: npm ci
|
||||
displayName: 'npm ci'
|
||||
|
||||
- task: Gulp@0
|
||||
displayName: 'Bundle project'
|
||||
inputs:
|
||||
targets: bundle
|
||||
arguments: '--ship'
|
||||
|
||||
- script: npm test
|
||||
displayName: 'npm test'
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish test results
|
||||
inputs:
|
||||
testResultsFormat: JUnit
|
||||
testResultsFiles: '**/junit.xml'
|
||||
#failTaskOnFailedTests: true #if we want to fail the build on failed unit tests
|
||||
|
||||
- task: PublishCodeCoverageResults@1
|
||||
displayName: 'Publish code coverage results'
|
||||
inputs:
|
||||
codeCoverageTool: Cobertura
|
||||
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml'
|
||||
|
||||
- task: Gulp@0
|
||||
displayName: 'Package Solution'
|
||||
inputs:
|
||||
targets: 'package-solution'
|
||||
arguments: '--ship'
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)'
|
||||
inputs:
|
||||
Contents: |
|
||||
sharepoint/**/*.sppkg
|
||||
TargetFolder: '$(Build.ArtifactStagingDirectory)'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Artifact: drop'
|
|
@ -0,0 +1,39 @@
|
|||
parameters:
|
||||
# unique name of the job
|
||||
job_name: deploy_sppkg
|
||||
# friendly name of the job
|
||||
display_name: Upload & deploy *.sppkg to SharePoint app catalog
|
||||
# name of target enviroment deploying to
|
||||
target_environment: ''
|
||||
# app catalog scope (tenant|sitecollection)
|
||||
o365cli_app_catalog_scope: 'tenant'
|
||||
variable_group_name: ''
|
||||
jobs:
|
||||
- deployment: ${{ parameters.job_name }}
|
||||
displayName: ${{ parameters.display_name }}
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
environment: ${{ parameters.target_environment }}
|
||||
variables:
|
||||
- group: ${{parameters.variable_group_name}} #o365_user_login, o365_user_password, o365_app_catalog_site_url
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- checkout: none
|
||||
- download: current
|
||||
artifact: drop
|
||||
patterns: '**/*.sppkg'
|
||||
- script: sudo npm install --global @pnp/office365-cli
|
||||
displayName: Install Office365 CLI
|
||||
- script: o365 login $(o365_app_catalog_site_url) --authType password --userName $(o365_user_login) --password $(o365_user_password)
|
||||
displayName: Login to Office365
|
||||
- script: |
|
||||
CMD_GET_SPPKG_NAME=$(find $(Pipeline.Workspace)/drop -name '*.sppkg' -exec basename {} \;)
|
||||
echo "##vso[task.setvariable variable=SpPkgFileName;isOutput=true]${CMD_GET_SPPKG_NAME}"
|
||||
displayName: Get generated *.sppkg filename
|
||||
name: GetSharePointPackage
|
||||
- script: o365 spo app add --filePath "$(Pipeline.Workspace)/drop/sharepoint/solution/$(GetSharePointPackage.SpPkgFileName)" --appCatalogUrl $(o365_app_catalog_site_url) --scope ${{ parameters.o365cli_app_catalog_scope }} --overwrite
|
||||
displayName: Upload SharePoint package to Site Collection App Catalog
|
||||
- script: o365 spo app deploy --name $(GetSharePointPackage.SpPkgFileName) --appCatalogUrl $(o365_app_catalog_site_url) --scope ${{ parameters.o365cli_app_catalog_scope }}
|
||||
displayName: Deploy SharePoint package
|
|
@ -0,0 +1,26 @@
|
|||
name: $(TeamProject)_$(BuildDefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)
|
||||
resources:
|
||||
- repo: self
|
||||
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
- develop
|
||||
|
||||
stages:
|
||||
- stage: build
|
||||
displayName: build
|
||||
jobs:
|
||||
- template: ./azure-pipelines-build-template.yml
|
||||
parameters:
|
||||
name: 'buildsolution'
|
||||
- stage: 'deployqa'
|
||||
# uncomment if you want deployments to occur only for a specific branch
|
||||
#condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
|
||||
jobs:
|
||||
- template: ./azure-pipelines-deploy-template.yml
|
||||
parameters:
|
||||
job_name: deploy_solution
|
||||
target_environment: 'qa'
|
||||
variable_group_name: qa_configuration
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"personal-apps-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/personalApps/PersonalAppsWebPart.js",
|
||||
"manifest": "./src/webparts/personalApps/PersonalAppsWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"PersonalAppsWebPartStrings": "lib/webparts/personalApps/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
|
||||
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "personal-apps",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"preset": "@voitanos/jest-preset-spfx-react16",
|
||||
"rootDir": "../src",
|
||||
"collectCoverageFrom": [
|
||||
"<rootDir>/**/*.{ts,tsx}",
|
||||
"!<rootDir>/**/*.scss.*",
|
||||
"!<rootDir>/loc/**/*.*"
|
||||
],
|
||||
"coverageReporters": [
|
||||
"text",
|
||||
"json",
|
||||
"lcov",
|
||||
"text-summary",
|
||||
"cobertura"
|
||||
],
|
||||
"reporters": [
|
||||
"default",
|
||||
["jest-junit", {
|
||||
"suiteName": "jest tests",
|
||||
"outputDirectory": "temp/test/junit",
|
||||
"outputName": "junit.xml"
|
||||
}]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "personal-apps-client-side-solution",
|
||||
"id": "ab3683c6-0388-47a2-84f5-90d01259d0ed",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"webApiPermissionRequests": [{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.ReadWrite.All"
|
||||
}]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/personal-apps.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
'use strict';
|
||||
|
||||
// check if gulp dist was called
|
||||
if (process.argv.indexOf('dist') !== -1) {
|
||||
// add ship options to command call
|
||||
process.argv.push('--ship');
|
||||
}
|
||||
|
||||
const path = require('path');
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
const gulpSequence = require('gulp-sequence');
|
||||
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
// Create clean distrubution package
|
||||
gulp.task('dist', gulpSequence('clean', 'bundle', 'package-solution'));
|
||||
// Create clean development package
|
||||
gulp.task('dev', gulpSequence('clean', 'bundle', 'package-solution'));
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Webpack Bundle Anlayzer
|
||||
* Reference and gulp task
|
||||
*/
|
||||
const bundleAnalyzer = require('webpack-bundle-analyzer');
|
||||
|
||||
build.configureWebpack.mergeConfig({
|
||||
|
||||
additionalConfiguration: (generatedConfiguration) => {
|
||||
const lastDirName = path.basename(__dirname);
|
||||
const dropPath = path.join(__dirname, 'temp', 'stats');
|
||||
generatedConfiguration.plugins.push(new bundleAnalyzer.BundleAnalyzerPlugin({
|
||||
openAnalyzer: false,
|
||||
analyzerMode: 'static',
|
||||
reportFilename: path.join(dropPath, `${lastDirName}.stats.html`),
|
||||
generateStatsFile: true,
|
||||
statsFilename: path.join(dropPath, `${lastDirName}.stats.json`),
|
||||
logLevel: 'error'
|
||||
}));
|
||||
|
||||
return generatedConfiguration;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Custom Framework Specific gulp tasks
|
||||
*/
|
||||
|
||||
|
||||
build.initialize(gulp);
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"name": "personal-apps",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"preversion": "node ./tools/pre-version.js",
|
||||
"postversion": "gulp dist",
|
||||
"test": "./node_modules/.bin/jest --config ./config/jest.config.json",
|
||||
"test:watch": "./node_modules/.bin/jest --config ./config/jest.config.json --watchAll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.9.9",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@microsoft/sp-core-library": "1.10.0",
|
||||
"@microsoft/sp-lodash-subset": "1.10.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
|
||||
"@microsoft/sp-property-pane": "1.10.0",
|
||||
"@microsoft/sp-webpart-base": "1.10.0",
|
||||
"@pnp/pnpjs": "^2.0.3",
|
||||
"@pnp/spfx-controls-react": "^1.17.0",
|
||||
"@pnp/spfx-property-controls": "1.16.0",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/jquery": "^3.3.33",
|
||||
"@types/react": "16.8.8",
|
||||
"@types/react-dom": "16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"jquery": "^3.4.1",
|
||||
"material-table": "^1.57.2",
|
||||
"office-ui-fabric-react": "^7.83.1",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.8.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/microsoft-graph-types": "^1.12.0",
|
||||
"@microsoft/rush-stack-compiler-3.7": "^0.2.x",
|
||||
"@microsoft/sp-build-web": "1.10.0",
|
||||
"@microsoft/sp-module-interfaces": "1.10.0",
|
||||
"@microsoft/sp-tslint-rules": "1.10.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.10.0",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"@types/react": "^16.9.17",
|
||||
"@voitanos/jest-preset-spfx-react16": "^1.3.2",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1",
|
||||
"gulp-sequence": "1.0.0",
|
||||
"jest": "^23.6.0",
|
||||
"jest-junit": "^10.0.0",
|
||||
"lodash": "^4.17.15",
|
||||
"spfx-uifabric-themes": "^0.8.0",
|
||||
"typescript": "~3.7.x",
|
||||
"webpack-bundle-analyzer": "^3.6.0"
|
||||
},
|
||||
"jest-junit": {
|
||||
"output": "temp/test/junit/junit.xml",
|
||||
"usePathForSuiteName": "true"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"themePrimary": "#6264a7",
|
||||
"themeLighterAlt": "#f7f7fb",
|
||||
"themeLighter": "#e1e1f1",
|
||||
"themeLight": "#c8c9e4",
|
||||
"themeTertiary": "#989ac9",
|
||||
"themeSecondary": "#7173b0",
|
||||
"themeDarkAlt": "#585a95",
|
||||
"themeDark": "#4a4c7e",
|
||||
"themeDarker": "#37385d",
|
||||
"neutralLighterAlt": "#0b0b0b",
|
||||
"neutralLighter": "#151515",
|
||||
"neutralLight": "#252525",
|
||||
"neutralQuaternaryAlt": "#2f2f2f",
|
||||
"neutralQuaternary": "#373737",
|
||||
"neutralTertiaryAlt": "#595959",
|
||||
"neutralTertiary": "#c8c8c8",
|
||||
"neutralSecondary": "#d0d0d0",
|
||||
"neutralPrimaryAlt": "#dadada",
|
||||
"neutralPrimary": "#ffffff",
|
||||
"neutralDark": "#f4f4f4",
|
||||
"black": "#f8f8f8",
|
||||
"white": "#000000"
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"themePrimary": "#6264a7",
|
||||
"themeLighterAlt": "#f7f7fb",
|
||||
"themeLighter": "#e1e1f1",
|
||||
"themeLight": "#c8c9e4",
|
||||
"themeTertiary": "#989ac9",
|
||||
"themeSecondary": "#7173b0",
|
||||
"themeDarkAlt": "#585a95",
|
||||
"themeDark": "#4a4c7e",
|
||||
"themeDarker": "#37385d",
|
||||
"neutralLighterAlt": "#2d2c2c",
|
||||
"neutralLighter": "#2c2b2b",
|
||||
"neutralLight": "#2a2929",
|
||||
"neutralQuaternaryAlt": "#272626",
|
||||
"neutralQuaternary": "#252525",
|
||||
"neutralTertiaryAlt": "#242323",
|
||||
"neutralTertiary": "#c8c8c8",
|
||||
"neutralSecondary": "#d0d0d0",
|
||||
"neutralPrimaryAlt": "#dadada",
|
||||
"neutralPrimary": "#ffffff",
|
||||
"neutralDark": "#f4f4f4",
|
||||
"black": "#f8f8f8",
|
||||
"white": "#2d2c2c"
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"themePrimary": "#6264a7",
|
||||
"themeLighterAlt": "#f7f7fb",
|
||||
"themeLighter": "#e1e1f1",
|
||||
"themeLight": "#c8c9e4",
|
||||
"themeTertiary": "#989ac9",
|
||||
"themeSecondary": "#7173b0",
|
||||
"themeDarkAlt": "#585a95",
|
||||
"themeDark": "#4a4c7e",
|
||||
"themeDarker": "#37385d",
|
||||
"neutralLighterAlt": "#ecebe9",
|
||||
"neutralLighter": "#e8e7e6",
|
||||
"neutralLight": "#dedddc",
|
||||
"neutralQuaternaryAlt": "#cfcecd",
|
||||
"neutralQuaternary": "#c6c5c4",
|
||||
"neutralTertiaryAlt": "#bebdbc",
|
||||
"neutralTertiary": "#b5b4b2",
|
||||
"neutralSecondary": "#9d9c9a",
|
||||
"neutralPrimaryAlt": "#868482",
|
||||
"neutralPrimary": "#252423",
|
||||
"neutralDark": "#565453",
|
||||
"black": "#3e3d3b",
|
||||
"white": "#f3f2f1"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
$default-background: #f3f2f1;
|
||||
$default-color: #252423;
|
||||
$default-button-background: #6264a7;
|
||||
$default-Button-color: #f3f2f1;
|
||||
|
||||
// dark theme
|
||||
$dark-background: #2d2c2c;
|
||||
$dark-color: #ffffff;
|
||||
$dark-button-background: #6264a7;
|
||||
$dark-button-color: #2d2c2c;
|
||||
|
||||
// contrast theme
|
||||
$contrast-background: #000000;
|
||||
$contrast-color: #ffffff;
|
||||
$contrast-button-background: #b5c01c;
|
||||
$contrast-Button-color: #000000;
|
|
@ -0,0 +1,30 @@
|
|||
export interface IIconPickerProps {
|
||||
/**
|
||||
* call-back function when icon selection has been confirmed
|
||||
*/
|
||||
onSave(iconName: string): void;
|
||||
/**
|
||||
* call-back function when icon has changed
|
||||
*/
|
||||
onChange?(iconName: string): void;
|
||||
/**
|
||||
* Specifies the label of the icon picker button
|
||||
*/
|
||||
buttonLabel?: string;
|
||||
/**
|
||||
* Specifies if the picker button is disabled
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* Specifies a custom className for the picker button
|
||||
*/
|
||||
buttonClassName?: string;
|
||||
/**
|
||||
* Specifies a custom className for the panel element
|
||||
*/
|
||||
panelClassName?: string;
|
||||
/**
|
||||
* initially selected icon
|
||||
*/
|
||||
currentIcon?: string;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export interface IIconPickerState {
|
||||
items: string[];
|
||||
currentIcon?: string;
|
||||
isPanelOpen: boolean;
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.navArea {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding-left: 11px;
|
||||
}
|
||||
.headTitle {
|
||||
display: inline-block;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 2;
|
||||
font-size: 28px;
|
||||
font-weight: 300;
|
||||
margin: auto 8px 5px 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.searchBox {
|
||||
flex-grow: 5;
|
||||
flex-shrink: 1;
|
||||
margin: 5px 0;
|
||||
}
|
||||
.closeBtnContainer {
|
||||
flex: 0 0 54px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.iconList {
|
||||
list-style-type: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0;
|
||||
margin: 10px -5px;
|
||||
}
|
||||
.iconItem {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0 2px 4px;
|
||||
list-style-type: none;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.iconRadio {
|
||||
position: absolute;
|
||||
left: -1000px;
|
||||
opacity: 0;
|
||||
}
|
||||
.iconLabel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
width: 78px;
|
||||
padding: 5px;
|
||||
height: 70px;
|
||||
border-radius: 3px;
|
||||
background-color: "[theme:neutralLighterAlt, default:#f8f8f8]";
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border: 2px solid transparent;
|
||||
border-color: "[theme:neutralQuarternary, default:#d0d0d0]";
|
||||
border-radius: 3px;
|
||||
opacity: 0;
|
||||
will-change: opacity, border-color;
|
||||
transition: opacity ease-out .05s;
|
||||
}
|
||||
&:hover {
|
||||
&:after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.iconRadio:checked + .iconLabel {
|
||||
&:after {
|
||||
opacity: 1;
|
||||
border-color: "[theme: themePrimary, default: #0078d7]";
|
||||
}
|
||||
color: "[theme: themePrimary, default: #0078d7]";
|
||||
}
|
||||
.iconRadio:focus + .iconLabel {
|
||||
outline: 1px dashed;
|
||||
outline-color: "[theme: themePrimary, default: #0078d7]";
|
||||
outline-offset: -5px;
|
||||
}
|
||||
.iconGlyph {
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-bottom: 5px;
|
||||
color: inherit;
|
||||
}
|
||||
.iconName {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
.selectionDisplay {
|
||||
order: 2;
|
||||
display: flex;
|
||||
flex: 1 0 32px;
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:global {
|
||||
&.noSelection {
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
}
|
||||
.selectionLabel {
|
||||
display: inline-block;
|
||||
}
|
||||
.selectionIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
min-width: 32px;
|
||||
}
|
||||
.btnCancel {
|
||||
order: 1;
|
||||
}
|
||||
.btnSave {
|
||||
order: 3;
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
import * as React from 'react';
|
||||
import { IIconPickerProps } from '.';
|
||||
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { Icon } from 'office-ui-fabric-react/lib/Icon';
|
||||
import { SearchBox } from 'office-ui-fabric-react/lib/SearchBox';
|
||||
import { IRenderFunction, getId } from 'office-ui-fabric-react/lib/Utilities';
|
||||
import styles from './IconPicker.module.scss';
|
||||
import * as strings from "PersonalAppsWebPartStrings";
|
||||
import { IconNames } from './IconNames';
|
||||
import { Panel, PanelType, IPanelProps } from 'office-ui-fabric-react/lib/Panel';
|
||||
import { debounce } from 'lodash';
|
||||
import { IIconPickerState } from './IIconPickerState';
|
||||
|
||||
|
||||
|
||||
export class IconPicker extends React.Component<IIconPickerProps, IIconPickerState> {
|
||||
private radioIdBase: string = getId("radio");
|
||||
|
||||
constructor(props: IIconPickerProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
items: IconNames.Icons,
|
||||
isPanelOpen: false,
|
||||
currentIcon: this.props.currentIcon || null
|
||||
};
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IIconPickerProps> {
|
||||
return <div>
|
||||
<PrimaryButton
|
||||
text={this.props.buttonLabel}
|
||||
onClick={this.iconPickerOnClick}
|
||||
className={this.props.buttonClassName}
|
||||
disabled={this.props.disabled}
|
||||
data-automation-id={`icon-picker-open`}
|
||||
/>
|
||||
<Panel
|
||||
isOpen={this.state.isPanelOpen}
|
||||
onDismiss={this.closePanel}
|
||||
type={PanelType.medium}
|
||||
data-automation-id={`icon-picker-panel`}
|
||||
closeButtonAriaLabel={strings.IconPickerCloseLabel}
|
||||
className={this.props.panelClassName}
|
||||
onRenderNavigation={this.renderPanelNav}
|
||||
onRenderFooterContent={this.renderPanelFooter}
|
||||
>
|
||||
{this.renderPanelContent()}
|
||||
</Panel>
|
||||
</div>;
|
||||
}
|
||||
|
||||
private closePanel = (): void => {
|
||||
this.setState({
|
||||
currentIcon: null,
|
||||
isPanelOpen: false
|
||||
});
|
||||
}
|
||||
|
||||
private iconPickerOnClick = (): void => {
|
||||
this.setState({
|
||||
isPanelOpen: true
|
||||
});
|
||||
}
|
||||
|
||||
private iconOnClick = (iconName: string): void => {
|
||||
if (this.props.onChange) this.props.onChange(iconName);
|
||||
this.setState({
|
||||
currentIcon: iconName,
|
||||
});
|
||||
}
|
||||
|
||||
private onAbort = (): void => {
|
||||
this.setState({ items: IconNames.Icons });
|
||||
}
|
||||
|
||||
private onChange = (event?: React.ChangeEvent<HTMLInputElement>, newValue?: string): void => {
|
||||
let items: string[];
|
||||
if (newValue && newValue.trim().length > 2) {
|
||||
items = IconNames.Icons.filter(item => {
|
||||
return item.toLocaleLowerCase().indexOf(newValue.toLocaleLowerCase()) !== -1;
|
||||
});
|
||||
} else {
|
||||
items = IconNames.Icons;
|
||||
}
|
||||
this.setState({
|
||||
items: items
|
||||
});
|
||||
}
|
||||
|
||||
private confirmSelection = (): void => {
|
||||
if (this.props.onSave) this.props.onSave(this.state.currentIcon);
|
||||
this.setState({
|
||||
isPanelOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
private renderPanelNav: IRenderFunction<IPanelProps> = (props: IPanelProps, defaultRender: IRenderFunction<IPanelProps>) => {
|
||||
return <div className={styles.navArea}>
|
||||
<h2 className={styles.headTitle}>{strings.IconPickerSelectLabel}</h2>
|
||||
<SearchBox className={styles.searchBox}
|
||||
onAbort={this.onAbort}
|
||||
data-automation-id={`icon-picker-search`}
|
||||
|
||||
onChange={this.onChange} />
|
||||
<div className={styles.closeBtnContainer}>{defaultRender!(props)}</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
private renderPanelContent = () => {
|
||||
return <div>
|
||||
{this.renderIcons()}
|
||||
</div>;
|
||||
}
|
||||
|
||||
private renderPanelFooter: IRenderFunction<IPanelProps> = () => {
|
||||
return <div className={styles.footer} data-automation-id={`icon-picker-footer`}>
|
||||
<PrimaryButton text={strings.IconPickerSaveLabel} onClick={this.confirmSelection} disabled={!this.state.currentIcon} className={styles.btnSave} data-automation-id={`icon-picker-save`} />
|
||||
<div className={`${styles.selectionDisplay} ${!this.state.currentIcon ? 'noSelection' : ''}`}>
|
||||
<span className={styles.selectionLabel}>{strings.IconPickerSelectedLabel}:</span>
|
||||
<Icon iconName={this.state.currentIcon} className={styles.selectionIcon} />
|
||||
</div>
|
||||
<DefaultButton text={strings.IconPickerCancelLabel} onClick={this.closePanel} className={styles.btnCancel} data-automation-id={`icon-picker-close`} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
private renderIcons = (): React.ReactElement<IIconPickerProps> => {
|
||||
return (<ul className={styles.iconList}>
|
||||
{this.state.items.map(this.renderIcon)}
|
||||
</ul>);
|
||||
}
|
||||
|
||||
private renderIcon = (item: string): JSX.Element => {
|
||||
const radioId: string = `${this.radioIdBase}-${item}`;
|
||||
return <li className={styles.iconItem}>
|
||||
<input type="radio" name={this.radioIdBase} id={radioId} className={styles.iconRadio}
|
||||
data-automation-id={`icon-picker-${item}`}
|
||||
checked={item == this.state.currentIcon}
|
||||
onChange={() => this.iconOnClick(item)} />
|
||||
<label className={styles.iconLabel} htmlFor={radioId} title={item}>
|
||||
<Icon iconName={item} className={styles.iconGlyph} />
|
||||
<span className={styles.iconName}>{item}</span>
|
||||
</label>
|
||||
</li>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from './IIconPickerProps';
|
||||
export * from './IconPicker';
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,6 @@
|
|||
export interface IAppItem {
|
||||
title:string;
|
||||
name:string;
|
||||
description: string;
|
||||
iconName: string;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export interface ITenantProperty {
|
||||
key: string;
|
||||
Comment?: string;
|
||||
Description?: string;
|
||||
Value: string;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
import {
|
||||
MSGraphClient,
|
||||
} from "@microsoft/sp-http";
|
||||
|
||||
import * as MicrosoftGraph from "@microsoft/microsoft-graph-types";
|
||||
import { IListItem } from "../webparts/personalApps/components/ManageApps/IListItem";
|
||||
|
||||
export default class dataservices {
|
||||
private static _MSGraphClient: MSGraphClient;
|
||||
private static _hasExtension: boolean = false;
|
||||
|
||||
/*
|
||||
initialize the static class
|
||||
*/
|
||||
public static async init(context: WebPartContext) {
|
||||
//obtain the httpClient from the webpart context
|
||||
this._MSGraphClient = await context.msGraphClientFactory.getClient();
|
||||
|
||||
}
|
||||
|
||||
// Get Sources
|
||||
public static async getUserApps(): Promise<IListItem[]> {
|
||||
try {
|
||||
let _myApps = await this._MSGraphClient
|
||||
.api(`/me/extensions/MyApps`)
|
||||
.get();
|
||||
this._hasExtension = true;
|
||||
return _myApps ? _myApps.Apps : [];
|
||||
} catch (error) {
|
||||
console.log (error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public static async createOrUpdateUserApps(
|
||||
listApps: IListItem[]
|
||||
): Promise<microsoftgraph.OpenTypeExtension> {
|
||||
try {
|
||||
let _extensionResult: any;
|
||||
let extentionData: Object = {};
|
||||
|
||||
// User has extention created ?
|
||||
if (this._hasExtension) {
|
||||
extentionData = {
|
||||
Apps: listApps
|
||||
};
|
||||
// Call the REST API
|
||||
_extensionResult = await this._MSGraphClient
|
||||
.api(`/me/extensions/MyApps`)
|
||||
.patch(extentionData);
|
||||
} else {
|
||||
// Create Extention with Data
|
||||
extentionData = {
|
||||
"@odata.type": "#microsoft.graph.openTypeExtension",
|
||||
extensionName: "MyApps",
|
||||
Apps: listApps
|
||||
};
|
||||
// Call the Rest API
|
||||
_extensionResult = await this._MSGraphClient
|
||||
.api(`/me/extensions`)
|
||||
.post(extentionData);
|
||||
// Flag user has Extention crerated
|
||||
this._hasExtension = true;
|
||||
}
|
||||
|
||||
return _extensionResult;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw new Error("Error create or Update Extention");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "4dfcaee3-8a85-4d80-9fa3-23bcb1e08923",
|
||||
"alias": "PersonalAppsWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
"supportsThemeVariants": true,
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": ["SharePointWebPart","TeamsTab","TeamsPersonalApp","SharePointFullPage"],
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "My Personal Apps and Links" },
|
||||
"description": { "default": "My Personal Apps and Links " },
|
||||
"officeFabricIconFontName": "AppIconDefaultList",
|
||||
"properties": {
|
||||
"title": "My Personal Apps",
|
||||
"view": "List"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
import { Version } from "@microsoft/sp-core-library";
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
PropertyPaneChoiceGroup
|
||||
} from "@microsoft/sp-property-pane";
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import { DisplayMode } from "@microsoft/sp-core-library";
|
||||
import * as strings from "PersonalAppsWebPartStrings";
|
||||
import PersonalApps from "./components/PersonalApps";
|
||||
import { IPersonalAppsProps } from "./components/IPersonalAppsProps";
|
||||
import {
|
||||
PropertyFieldCollectionData,
|
||||
CustomCollectionFieldType
|
||||
} from "@pnp/spfx-property-controls/lib/PropertyFieldCollectionData";
|
||||
import { ThemeProvider, ThemeChangedEventArgs, IReadonlyTheme } from '@microsoft/sp-component-base';
|
||||
import dataservices from "../../services/dataservices";
|
||||
import { loadTheme } from "office-ui-fabric-react";
|
||||
const teamsDefaultTheme = require("../../common/TeamsDefaultTheme.json");
|
||||
const teamsDarkTheme = require("../../common/TeamsDarkTheme.json");
|
||||
const teamsContrastTheme = require("../../common/TeamsContrastTheme.json");
|
||||
|
||||
export interface IPersonalAppsWebPartProps {
|
||||
title: string;
|
||||
view: string | number;
|
||||
|
||||
}
|
||||
|
||||
export default class PersonalAppsWebPart extends BaseClientSideWebPart<
|
||||
IPersonalAppsWebPartProps
|
||||
> {
|
||||
private _themeProvider: ThemeProvider;
|
||||
private _themeVariant: IReadonlyTheme | undefined;
|
||||
protected async onInit<T>(): Promise<T> {
|
||||
await dataservices.init(this.context);
|
||||
|
||||
this._themeProvider = this.context.serviceScope.consume(ThemeProvider.serviceKey);
|
||||
|
||||
// If it exists, get the theme variant
|
||||
this._themeVariant = this._themeProvider.tryGetTheme();
|
||||
|
||||
// Register a handler to be notified if the theme variant changes
|
||||
this._themeProvider.themeChangedEvent.add(this, this._handleThemeChangedEvent);
|
||||
|
||||
if (this.context.sdks.microsoftTeams) {
|
||||
// in teams ?
|
||||
const context = this.context.sdks.microsoftTeams!.context;
|
||||
this._applyTheme(context.theme || "default");
|
||||
this.context.sdks.microsoftTeams.teamsJs.registerOnThemeChangeHandler(
|
||||
this._applyTheme
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current theme variant reference and re-render.
|
||||
*
|
||||
* @param args The new theme
|
||||
*/
|
||||
private _handleThemeChangedEvent(args: ThemeChangedEventArgs): void {
|
||||
this._themeVariant = args.theme;
|
||||
this.render();
|
||||
}
|
||||
|
||||
// Apply btheme id in Teams
|
||||
private _applyTheme = (theme: string): void => {
|
||||
this.context.domElement.setAttribute("data-theme", theme);
|
||||
document.body.setAttribute("data-theme", theme);
|
||||
|
||||
if (theme == "dark") {
|
||||
loadTheme({
|
||||
palette: teamsDarkTheme
|
||||
});
|
||||
}
|
||||
|
||||
if (theme == "default") {
|
||||
loadTheme({
|
||||
palette: teamsDefaultTheme
|
||||
});
|
||||
}
|
||||
|
||||
if (theme == "contrast") {
|
||||
loadTheme({
|
||||
palette: teamsContrastTheme
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IPersonalAppsProps> = React.createElement(
|
||||
PersonalApps,
|
||||
{
|
||||
title: this.properties.title,
|
||||
view: this.properties.view,
|
||||
displayMode: this.displayMode,
|
||||
themeVariant: this._themeVariant,
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse("1.0");
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
const { view } = this.properties;
|
||||
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField("title", {
|
||||
label: strings.DescriptionFieldLabel
|
||||
}),
|
||||
PropertyPaneChoiceGroup("view", {
|
||||
label: "view option",
|
||||
|
||||
options: [
|
||||
{
|
||||
key: "List",
|
||||
text: "List",
|
||||
iconProps: { officeFabricIconFontName: "List" },
|
||||
checked: view === "List" ? true : false
|
||||
},
|
||||
{
|
||||
key: "Tiles",
|
||||
text: "Tiles",
|
||||
iconProps: { officeFabricIconFontName: "Tiles" },
|
||||
checked: view === "Tiles" ? true : false
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
@import "~office-ui-fabric-react/dist/sass/References.scss";
|
||||
@import "~office-ui-fabric-react/dist/sass/semanticSlots";
|
||||
@import './node_modules/spfx-uifabric-themes/office.theme';
|
||||
@import "./../../../../Common/themeColors.module.scss";
|
||||
@media screen and (min-width: 40em) {
|
||||
.card {
|
||||
max-width: calc(50% - 1em);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 60em) {
|
||||
.card {
|
||||
max-width: calc(25% - 1em);
|
||||
}
|
||||
}
|
||||
@media all and (min-width: 480px) and (max-width: 768px) {
|
||||
.card {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
|
||||
min-height: 60px;
|
||||
max-height: 60px;
|
||||
min-width: 300px;
|
||||
justify-content: start;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 5px;
|
||||
flex-direction: row;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: $ms-color-neutralTertiaryAlt;
|
||||
padding: 20px;
|
||||
align-items: flex-start;
|
||||
background-color:$bodyBackgroundColor;
|
||||
color: $bodyTextColor;
|
||||
flex: 0 1 365px;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
background-color: $ms-themePrimary;
|
||||
color: $ms-color-white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.image {
|
||||
font-size: 40px;
|
||||
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
display: flex;
|
||||
margin-right: 30px;
|
||||
align-items: center;
|
||||
width: 45px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 300px;
|
||||
line-height: 25px;
|
||||
line-break: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: 'ellipsis';
|
||||
}
|
||||
|
||||
.separator {
|
||||
border-color: $ms-color-themePrimary;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
height: 0px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
|
||||
color: $ms-color-neutralTertiaryAlt;
|
||||
}
|
||||
|
||||
[data-theme="contrast"] {
|
||||
|
||||
// dark theme
|
||||
color: $contrast-button-background;
|
||||
.card:hover{
|
||||
color: $ms-color-black;
|
||||
background-color: $contrast-button-background;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="default"] {
|
||||
.card{
|
||||
background-color: $ms-color-white;
|
||||
}
|
||||
.card:hover {
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
background-color: $ms-themePrimary;
|
||||
color: $ms-color-white;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
[data-theme="default"] {
|
||||
$default-background: #f3f2f1;
|
||||
$default-color: #252423;
|
||||
$default-button-background: #6264a7;
|
||||
$default-Button-color: #f3f2f1;
|
||||
|
||||
color: $default-color;
|
||||
}*/
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import {
|
||||
FontIcon,
|
||||
Text,
|
||||
} from "office-ui-fabric-react";
|
||||
import { IAppItemProps } from "./IAppItemProps";
|
||||
import * as React from "react";
|
||||
import styles from "./AppItem.module.scss";
|
||||
|
||||
export const AppItem = (props: IAppItemProps) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={styles.card}
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
window.open(props.url, "_blank");
|
||||
}}
|
||||
>
|
||||
<div className={styles.imageContainer}>
|
||||
<FontIcon
|
||||
iconName={props.iconName}
|
||||
className={styles.image}
|
||||
></FontIcon>
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.title}>{props.title}</div>
|
||||
<Text variant="small" block>
|
||||
{props.description}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
export interface IAppItemProps {
|
||||
title:string;
|
||||
description: string;
|
||||
iconName: string;
|
||||
url:string;
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
@import "~office-ui-fabric-react/dist/sass/References.scss";
|
||||
@import "./node_modules/spfx-uifabric-themes/office.theme";
|
||||
@import "./../../../../Common/themeColors.module.scss";
|
||||
|
||||
.tile {
|
||||
display: flex;
|
||||
max-width: 120px;
|
||||
min-height: 120px;
|
||||
max-height: 120px;
|
||||
min-width: 120px;
|
||||
justify-content: start;
|
||||
margin: 5px;
|
||||
flex-direction: column;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: $ms-color-neutralTertiaryAlt;
|
||||
padding: 10px;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
.tile:hover {
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
background-color: $ms-themePrimary;
|
||||
color: $ms-color-white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.image {
|
||||
font-size: 48px;
|
||||
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600px;
|
||||
line-height: 25px;
|
||||
line-break: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: "ellipsis";
|
||||
}
|
||||
|
||||
.separator {
|
||||
border-color: $ms-color-themePrimary;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
height: 0px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
[data-theme="default"] {
|
||||
.tile {
|
||||
background-color: $ms-color-white;
|
||||
}
|
||||
.tile:hover {
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
background-color: $ms-themePrimary;
|
||||
color: $ms-color-white;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
color: $ms-color-neutralTertiaryAlt;
|
||||
}
|
||||
|
||||
[data-theme="contrast"] {
|
||||
// dark theme
|
||||
color: $contrast-button-background;
|
||||
.tile:hover {
|
||||
color: $ms-color-black;
|
||||
background-color: $contrast-button-background;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { FontIcon, Text } from "office-ui-fabric-react";
|
||||
import { IAppTileProps } from "./IAppTileProps";
|
||||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
import styles from "./AppTile.module.scss";
|
||||
|
||||
export const AppTile = (props: IAppTileProps) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={styles.tile}
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
window.open(props.url, "_blank");
|
||||
}}
|
||||
>
|
||||
<div className={styles.imageContainer}>
|
||||
<FontIcon
|
||||
iconName={props.iconName}
|
||||
className={styles.image}
|
||||
></FontIcon>
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.title}>{props.title}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
export interface IAppTileProps {
|
||||
title:string;
|
||||
description: string;
|
||||
iconName: string;
|
||||
url:string;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { DisplayMode } from "@microsoft/sp-core-library";
|
||||
import { IReadonlyTheme } from '@microsoft/sp-component-base';
|
||||
export interface IPersonalAppsProps {
|
||||
title: string;
|
||||
view:string | number;
|
||||
displayMode: DisplayMode;
|
||||
themeVariant: IReadonlyTheme | undefined;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { IListItem } from "./ManageApps/IListItem";
|
||||
|
||||
export interface IPersonalAppsState {
|
||||
showPanel: boolean;
|
||||
apps : IListItem[];
|
||||
isLoading: boolean;
|
||||
hasError: boolean;
|
||||
errorMessage: string;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export interface IListItem {
|
||||
name:string;
|
||||
description:string;
|
||||
url:string;
|
||||
iconName:string;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { IListItem } from "./IListItem";
|
||||
|
||||
export interface IManageAppsProps {
|
||||
showPanel: boolean;
|
||||
onDismiss: (list: IListItem[], changed:boolean) => void;
|
||||
Apps: IListItem[];
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { IManageAppsProps } from "./IManageAppsProps";
|
||||
|
||||
export interface IManageAppsState {
|
||||
columns: any[];
|
||||
data: IManageAppsProps[];
|
||||
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
@import "~office-ui-fabric-react/dist/sass/References.scss";
|
||||
@import "./node_modules/spfx-uifabric-themes/office.theme";
|
||||
@import "./../../../../Common/themeColors.module.scss";
|
||||
@import "~office-ui-fabric-react/dist/sass/semanticSlots";
|
||||
|
||||
.MuiPaperRoot {
|
||||
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||
min-height: 100%;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: "100%";
|
||||
}
|
||||
|
||||
:global {
|
||||
|
||||
[data-theme="default"] {
|
||||
.MuiTableCell-head {
|
||||
background-color: inherit !important;
|
||||
}
|
||||
.MuiTable-root {
|
||||
background-color: inherit!important;
|
||||
}
|
||||
.MuiIconButton-root:hover {
|
||||
background-color: inherit;
|
||||
color: inherit !important;
|
||||
}
|
||||
.MuiTableCell-body {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
[data-theme="dark"] {
|
||||
.MuiTableCell-head {
|
||||
background-color: $dark-background !important;
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiButtonBase-root {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiTextField-root {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiTablePagination-toolbar {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.ms-Button--default {
|
||||
background-color: $ms-color-neutralTertiary;
|
||||
}
|
||||
.MuiInput-underline:before {
|
||||
border-bottom-color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiSelect-root {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiTypography-caption {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiSelect-icon {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiInputBase-root {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiTable-root {
|
||||
background-color: $dark-background !important;
|
||||
}
|
||||
.MuiIconButton-root:hover {
|
||||
background-color: $ms-themePrimary;
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiTableCell-body {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="contrast"] {
|
||||
.MuiButtonBase-root {
|
||||
color: $contrast-button-background !important;
|
||||
}
|
||||
.ms-Button--default {
|
||||
background-color: $ms-color-black;
|
||||
}
|
||||
.ms-Button--Primary {
|
||||
color: $ms-color-black;
|
||||
background-color: $contrast-button-background;
|
||||
}
|
||||
|
||||
.MuiTableCell-head {
|
||||
background-color: $contrast-background !important;
|
||||
color: $contrast-button-background !important;
|
||||
}
|
||||
.MuiButtonBase-root {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiTextField-root {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiTablePagination-toolbar {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.ms-Button--default {
|
||||
background-color: $ms-color-neutralTertiary;
|
||||
}
|
||||
.MuiInput-underline:before {
|
||||
border-bottom-color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiSelect-root {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiTypography-caption {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiSelect-icon {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiInputBase-root {
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.MuiTable-root {
|
||||
background-color: $contrast-background !important;
|
||||
}
|
||||
.MuiIconButton-root:hover {
|
||||
background-color: $contrast-button-background;
|
||||
color: $contrast-background !important;
|
||||
}
|
||||
.MuiTableCell-body {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
.MuiPaperRoot {
|
||||
background-color: $dark-background !important;
|
||||
color: $ms-color-neutralTertiaryAlt !important;
|
||||
}
|
||||
.overlay {
|
||||
background-color: $dark-background !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: "100%";
|
||||
}
|
||||
}
|
||||
[data-theme="contrast"] {
|
||||
.MuiPaperRoot {
|
||||
// dark theme
|
||||
background-color: $contrast-background !important;
|
||||
color: $contrast-button-background !important;
|
||||
.overlay {
|
||||
background-color: $contrast-background !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
[data-theme="default"] {
|
||||
.MuiPaperRoot {
|
||||
// dark theme
|
||||
background-color: $default-background !important;
|
||||
}
|
||||
.overlay {
|
||||
background-color: $default-background !important;
|
||||
opacity: 0.6;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: "100%";
|
||||
height: "100%";
|
||||
align-items: center´;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,331 @@
|
|||
import * as React from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { IManageAppsProps } from "../ManageApps/IManageAppsProps";
|
||||
import { IManageAppsState } from "../ManageApps/IManageAppsState";
|
||||
import MaterialTable, { Icons, MTableHeader } from "material-table";
|
||||
import dataservices from "../../../../services/dataservices";
|
||||
import * as strings from "PersonalAppsWebPartStrings";
|
||||
import { IconPicker } from "../../../../controls/iconPicker";
|
||||
import {
|
||||
FontIcon,
|
||||
TextField,
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
SpinnerType,
|
||||
Panel,
|
||||
PanelType,
|
||||
Text,
|
||||
Label,
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
PrimaryButton,
|
||||
DefaultButton
|
||||
} from "office-ui-fabric-react";
|
||||
|
||||
import Check from "@material-ui/icons/Check";
|
||||
import ChevronLeft from "@material-ui/icons/ChevronLeft";
|
||||
import ChevronRight from "@material-ui/icons/ChevronRight";
|
||||
import Clear from "@material-ui/icons/Clear";
|
||||
import DeleteOutline from "@material-ui/icons/DeleteOutline";
|
||||
import Edit from "@material-ui/icons/Edit";
|
||||
import FilterList from "@material-ui/icons/FilterList";
|
||||
import FirstPage from "@material-ui/icons/FirstPage";
|
||||
import LastPage from "@material-ui/icons/LastPage";
|
||||
import Remove from "@material-ui/icons/Remove";
|
||||
import SaveAlt from "@material-ui/icons/SaveAlt";
|
||||
import Search from "@material-ui/icons/Search";
|
||||
import ViewColumn from "@material-ui/icons/ViewColumn";
|
||||
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
|
||||
import { IListItem } from "./IListItem";
|
||||
import { Paper, CircularProgress } from "@material-ui/core";
|
||||
import styles from "./ManageApps.module.scss";
|
||||
|
||||
initializeIcons();
|
||||
|
||||
const tableIcons: Icons = {
|
||||
Add: React.forwardRef((props, ref) => (
|
||||
<FontIcon iconName="AppIconDefaultAdd" style={{ fontWeight: 700 }} />
|
||||
)),
|
||||
Check: React.forwardRef((props, ref) => <Check />),
|
||||
Clear: React.forwardRef((props, ref) => <Clear />),
|
||||
Delete: React.forwardRef((props, ref) => <FontIcon iconName="Delete" style={{fontSize: 20}} />),
|
||||
DetailPanel: React.forwardRef((props, ref) => <ChevronRight />),
|
||||
Edit: React.forwardRef((props, ref) => <FontIcon iconName="Edit" style={{fontSize: 20}} />),
|
||||
Export: React.forwardRef((props, ref) => <SaveAlt />),
|
||||
Filter: React.forwardRef((props, ref) => <FilterList />),
|
||||
FirstPage: React.forwardRef((props, ref) => <FirstPage />),
|
||||
LastPage: React.forwardRef((props, ref) => <LastPage />),
|
||||
NextPage: React.forwardRef((props, ref) => <ChevronRight />),
|
||||
PreviousPage: React.forwardRef((props, ref) => <ChevronLeft />),
|
||||
|
||||
ResetSearch: React.forwardRef((props, ref) => <Clear />),
|
||||
Search: React.forwardRef((props, ref) => <Search />),
|
||||
SortArrow: React.forwardRef((props, ref) => (
|
||||
<FontIcon iconName="Sort" style={{ marginLeft: 5 }} />
|
||||
)),
|
||||
ThirdStateCheck: React.forwardRef((props, ref) => <Remove />),
|
||||
ViewColumn: React.forwardRef((props, ref) => <ViewColumn />)
|
||||
};
|
||||
|
||||
export function ManageApps(appProps: IManageAppsProps) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [changeData, setChangeData] = useState(false);
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [showPanel, setShowPanel] = useState(appProps.showPanel);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const [state, setState] = useState({
|
||||
columns: [
|
||||
{
|
||||
title: "Name ",
|
||||
field: "name",
|
||||
editComponent: props => (
|
||||
<TextField
|
||||
underlined
|
||||
required
|
||||
placeholder="Enter Name here"
|
||||
onGetErrorMessage={(newValue: string) => {
|
||||
return newValue.trim().length > 0 ? "" : "Please enter Name";
|
||||
}}
|
||||
validateOnFocusOut
|
||||
validateOnLoad={false}
|
||||
value={props.value}
|
||||
onChange={(event: React.FormEvent<HTMLInputElement>, newValue) => {
|
||||
props.onChange(newValue);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "Derscription",
|
||||
field: "description",
|
||||
editComponent: props => (
|
||||
<TextField
|
||||
underlined
|
||||
required
|
||||
placeholder="Enter description here"
|
||||
validateOnFocusOut
|
||||
validateOnLoad={false}
|
||||
onGetErrorMessage={(newValue: string) => {
|
||||
return newValue.trim().length > 0
|
||||
? ""
|
||||
: "Please enter Description";
|
||||
}}
|
||||
value={props.value}
|
||||
onChange={(event: React.FormEvent<HTMLInputElement>, newValue) => {
|
||||
props.onChange(newValue);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "Url",
|
||||
field: "Url",
|
||||
editComponent: props => (
|
||||
<TextField
|
||||
underlined
|
||||
required
|
||||
placeholder="Enter URL here"
|
||||
onGetErrorMessage={(newValue: string) => {
|
||||
try {
|
||||
const _URL = new URL(newValue);
|
||||
return "";
|
||||
} catch (error) {
|
||||
return "Please enter valid Url";
|
||||
}
|
||||
}}
|
||||
validateOnFocusOut
|
||||
validateOnLoad={false}
|
||||
value={props.value}
|
||||
onChange={(event: React.FormEvent<HTMLInputElement>, newValue) => {
|
||||
props.onChange(newValue);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "Icon",
|
||||
field: "iconName",
|
||||
render: rowData => (
|
||||
<FontIcon
|
||||
iconName={rowData.iconName}
|
||||
style={{ width: 24, height: 24, fontSize: 24 }}
|
||||
/>
|
||||
),
|
||||
editComponent: props => (
|
||||
<div style={{ display: "Flex", flexDirection: "row" }}>
|
||||
{" "}
|
||||
<FontIcon
|
||||
iconName={props.value}
|
||||
style={{ width: 24, height: 24, fontSize: 24, marginRight: 7 }}
|
||||
/>
|
||||
<IconPicker
|
||||
buttonLabel={" select Icon"}
|
||||
currentIcon={props.value}
|
||||
onSave={(iconName: string) => {
|
||||
props.onChange(iconName);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
],
|
||||
data: appProps.Apps
|
||||
});
|
||||
|
||||
// Load Schema Extension Data
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
// Get Tenant Property with id of Extension Id to check if exists or needs to create
|
||||
})();
|
||||
});
|
||||
|
||||
// Cancel command
|
||||
const _onDismiss = async () => {
|
||||
appProps.onDismiss(state.data, false);
|
||||
};
|
||||
|
||||
// Save command
|
||||
const _onSave = async () => {
|
||||
try {
|
||||
setIsSaving(true);
|
||||
const _result: microsoftgraph.OpenTypeExtension = await dataservices.createOrUpdateUserApps(
|
||||
state.data
|
||||
);
|
||||
console.log("extention created or updated", _result);
|
||||
appProps.onDismiss(state.data, true);
|
||||
} catch (error) {
|
||||
setHasError(true);
|
||||
setErrorMessage(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
// Render Panel commands
|
||||
const _onRenderFooterContent = () => (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
width: "100%",
|
||||
marginBottom: 35
|
||||
}}
|
||||
>
|
||||
<PrimaryButton
|
||||
onClick={_onSave}
|
||||
disabled={isSaving}
|
||||
|
||||
style={{ marginRight: 7, width: 100 }}
|
||||
>
|
||||
{isSaving ? (
|
||||
<Spinner size={SpinnerSize.xSmall}></Spinner>
|
||||
) : (
|
||||
strings.SaveLabelButtom
|
||||
)}
|
||||
</PrimaryButton>
|
||||
<DefaultButton style={{ width: 100 }} onClick={_onDismiss}>
|
||||
{strings.CancelLabelButton}
|
||||
</DefaultButton>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
isOpen={showPanel}
|
||||
onDismiss={_onDismiss}
|
||||
type={PanelType.custom}
|
||||
customWidth="888px"
|
||||
closeButtonAriaLabel="Close"
|
||||
headerText="My Apps"
|
||||
onRenderFooterContent={_onRenderFooterContent}
|
||||
isFooterAtBottom={true}
|
||||
>
|
||||
<div style={{ marginTop: 20, marginBottom: 25 }}>
|
||||
<Text variant="large" block>
|
||||
Please add links for your favorite Apps
|
||||
</Text>
|
||||
</div>
|
||||
{hasError && (
|
||||
<MessageBar messageBarType={MessageBarType.error}>
|
||||
{errorMessage}
|
||||
</MessageBar>
|
||||
)}
|
||||
{isLoading ? (
|
||||
<Spinner size={SpinnerSize.medium} />
|
||||
) : (
|
||||
<div style={{ height: "100%" }}>
|
||||
<MaterialTable
|
||||
title="My Apps"
|
||||
isLoading={false}
|
||||
columns={state.columns}
|
||||
components={{
|
||||
OverlayLoading: props => (
|
||||
<div className={styles.overlay}><CircularProgress /></div>
|
||||
),
|
||||
Container: props => (
|
||||
<Paper
|
||||
{...props}
|
||||
elevation={0}
|
||||
classes={{ root: styles.MuiPaperRoot }}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
|
||||
data={state.data}
|
||||
icons={tableIcons}
|
||||
options={{
|
||||
paging: true,
|
||||
showTitle:false,
|
||||
searchFieldAlignment:'left',
|
||||
pageSize: 7,
|
||||
search: true,
|
||||
minBodyHeight: "100%"
|
||||
}}
|
||||
editable={{
|
||||
onRowAdd: (newData: IListItem) =>
|
||||
new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
setChangeData(true);
|
||||
setState(prevState => {
|
||||
const data = [...prevState.data];
|
||||
data.push(newData);
|
||||
return { ...prevState, data };
|
||||
});
|
||||
}, 600);
|
||||
}),
|
||||
onRowUpdate: (newData, oldData) =>
|
||||
new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
if (oldData) {
|
||||
setChangeData(true);
|
||||
setState(prevState => {
|
||||
const data = [...prevState.data];
|
||||
data[data.indexOf(oldData)] = newData;
|
||||
|
||||
return { ...prevState, data };
|
||||
});
|
||||
}
|
||||
}, 600);
|
||||
}),
|
||||
onRowDelete: oldData =>
|
||||
new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
setChangeData(true);
|
||||
setState(prevState => {
|
||||
const data = [...prevState.data];
|
||||
data.splice(data.indexOf(oldData), 1);
|
||||
return { ...prevState, data };
|
||||
});
|
||||
}, 600);
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
@import "~office-ui-fabric-react/dist/sass/References.scss";
|
||||
@import "./node_modules/spfx-uifabric-themes/office.theme";
|
||||
@import "~office-ui-fabric-react/dist/sass/semanticSlots";
|
||||
|
||||
.imageSetting {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.imageSetting:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
|
||||
}
|
||||
|
||||
.personalApps {
|
||||
padding-top: 15px;
|
||||
.container {
|
||||
margin-top:15px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: $ms-color-themePrimary;
|
||||
border-color: $ms-color-themePrimary;
|
||||
color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system,
|
||||
BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: $ms-font-size-m;
|
||||
font-weight: $ms-font-weight-regular;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
import * as React from "react";
|
||||
import styles from "./PersonalApps.module.scss";
|
||||
import { IPersonalAppsProps } from "./IPersonalAppsProps";
|
||||
import { IPersonalAppsState } from "./IPersonalAppsState";
|
||||
import { escape } from "@microsoft/sp-lodash-subset";
|
||||
import { AppItem } from "../components/AppItem/AppItem";
|
||||
import { AppTile } from "../components/AppTile/AppTile";
|
||||
import { FontIcon, Label } from "office-ui-fabric-react";
|
||||
import { ManageApps } from "../components/ManageApps/ManageApps";
|
||||
import { IListItem } from "./ManageApps/IListItem";
|
||||
import dataservices from "../../../services/dataservices";
|
||||
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
|
||||
import { DisplayMode } from "@microsoft/sp-core-library";
|
||||
import { Customizer } from '@uifabric/utilities/lib/';
|
||||
import {
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
MessageBar,
|
||||
MessageBarType
|
||||
} from "office-ui-fabric-react";
|
||||
import { FormHelperText } from "@material-ui/core";
|
||||
import strings from "PersonalAppsWebPartStrings";
|
||||
|
||||
export default class PersonalApps extends React.Component<
|
||||
IPersonalAppsProps,
|
||||
IPersonalAppsState
|
||||
> {
|
||||
constructor(props: IPersonalAppsProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showPanel: false,
|
||||
apps: [],
|
||||
isLoading: false,
|
||||
hasError: false,
|
||||
errorMessage: ""
|
||||
};
|
||||
}
|
||||
|
||||
public async componentDidMount(): Promise<void> {
|
||||
this.setState({ isLoading: true });
|
||||
try {
|
||||
const _listApps = await dataservices.getUserApps();
|
||||
this.setState({
|
||||
apps: _listApps,
|
||||
isLoading: false,
|
||||
hasError: false,
|
||||
errorMessage: ""
|
||||
});
|
||||
} catch (error) {
|
||||
this.setState({ hasError: true, errorMessage: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
private _onPanelDismiss = (apps: IListItem[], changed: boolean) => {
|
||||
if (changed) {
|
||||
this.setState({
|
||||
apps: apps,
|
||||
showPanel: false
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
showPanel: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IPersonalAppsProps> {
|
||||
const { apps, isLoading, hasError, errorMessage } = this.state;
|
||||
const { view } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Customizer settings={{ theme: this.props.themeVariant }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
>
|
||||
<Label className={styles.title}>{this.props.title}</Label>
|
||||
<FontIcon
|
||||
iconName="PlayerSettings"
|
||||
title={"My Apps Settings"}
|
||||
className={styles.imageSetting}
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
this.setState({ showPanel: true });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.personalApps}>
|
||||
{apps && apps.length == 0 && !isLoading && (
|
||||
<Placeholder
|
||||
iconName="AppIconDefaultList"
|
||||
iconText={strings.PlaceholderIconText}
|
||||
description={strings.PlaceHolderDescription}
|
||||
buttonLabel={strings.PlaceHolderButtonLabel}
|
||||
onConfigure={() => {
|
||||
this.setState({ showPanel: true });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className={styles.container}>
|
||||
{isLoading && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Spinner size={SpinnerSize.medium} />
|
||||
</div>
|
||||
)}
|
||||
{hasError && (
|
||||
<MessageBar messageBarType={MessageBarType.error}>
|
||||
{errorMessage}
|
||||
</MessageBar>
|
||||
)}
|
||||
|
||||
{apps &&
|
||||
apps.length > 0 &&
|
||||
apps.map(item => {
|
||||
return (
|
||||
<>
|
||||
{view == "Tiles" ? (
|
||||
<AppTile
|
||||
title={item.name}
|
||||
description={item.description}
|
||||
iconName={item.iconName}
|
||||
url={item.url}
|
||||
/>
|
||||
) : (
|
||||
<AppItem
|
||||
title={item.name}
|
||||
description={item.description}
|
||||
iconName={item.iconName}
|
||||
url={item.url}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
{this.state.showPanel && (
|
||||
<ManageApps
|
||||
onDismiss={this._onPanelDismiss}
|
||||
showPanel={this.state.showPanel}
|
||||
Apps={this.state.apps}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Customizer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
define([], function() {
|
||||
return {
|
||||
IconPickerCancelLabel: "Cancel",
|
||||
IconPickerSelectedLabel: "Selected",
|
||||
IconPickerSaveLabel: "Save\"",
|
||||
IconPickerSelectLabel: "Select an Icon",
|
||||
IconPickerCloseLabel: "Close",
|
||||
PlaceHolderButtonLabel: "Add",
|
||||
PlaceHolderDescription: "Please add your favorite Apps ",
|
||||
PlaceholderIconText: "You don't have any favorite Apps",
|
||||
CancelLabelButton: "Cancel",
|
||||
SaveLabelButtom: "Save",
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
declare interface IPersonalAppsWebPartStrings {
|
||||
IconPickerCancelLabel: string;
|
||||
IconPickerSelectedLabel: string;
|
||||
IconPickerSaveLabel: string;
|
||||
IconPickerSelectLabel: string;
|
||||
IconPickerCloseLabel: string;
|
||||
PlaceHolderButtonLabel: string;
|
||||
PlaceHolderDescription: string;
|
||||
PlaceholderIconText: string;
|
||||
CancelLabelButton: string;
|
||||
SaveLabelButtom: string;
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'PersonalAppsWebPartStrings' {
|
||||
const strings: IPersonalAppsWebPartStrings;
|
||||
export = strings;
|
||||
}
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.2/MicrosoftTeams.schema.json",
|
||||
"manifestVersion": "1.2",
|
||||
"packageName": "personal-apps",
|
||||
"id": "ab3683c6-0388-47a2-84f5-90d01259d0ed",
|
||||
"version": "0.1",
|
||||
"developer": {
|
||||
"name": "Dev",
|
||||
"websiteUrl": "https://products.office.com/en-us/sharepoint/collaboration",
|
||||
"privacyUrl": "https://privacy.microsoft.com/en-us/privacystatement",
|
||||
"termsOfUseUrl": "https://www.microsoft.com/en-us/servicesagreement"
|
||||
},
|
||||
"name": {
|
||||
"short": "My Personal Apps"
|
||||
},
|
||||
"description": {
|
||||
"short": "My Personal Apps",
|
||||
"full": "My Personal Apps "
|
||||
},
|
||||
"icons": {
|
||||
"outline": "4dfcaee3-8a85-4d80-9fa3-23bcb1e08923_outline.png",
|
||||
"color": "4dfcaee3-8a85-4d80-9fa3-23bcb1e08923_color.png"
|
||||
},
|
||||
"accentColor": "#004578",
|
||||
"configurableTabs": [
|
||||
{
|
||||
"configurationUrl": "https://{teamSiteDomain}{teamSitePath}/_layouts/15/TeamsLogon.aspx?SPFX=true&dest={teamSitePath}/_layouts/15/teamshostedapp.aspx%3FopenPropertyPane=true%26teams%26componentId=ab3683c6-0388-47a2-84f5-90d01259d0ed",
|
||||
"canUpdateConfiguration": false,
|
||||
"scopes": [
|
||||
"team"
|
||||
]
|
||||
}
|
||||
],
|
||||
"validDomains": [
|
||||
"*.login.microsoftonline.com",
|
||||
"*.sharepoint.com",
|
||||
"*.sharepoint-df.com",
|
||||
"spoppe-a.akamaihd.net",
|
||||
"spoprod-a.akamaihd.net",
|
||||
"resourceseng.blob.core.windows.net",
|
||||
"msft.spoppe.com"
|
||||
],
|
||||
"webApplicationInfo": {
|
||||
"resource": "https://{teamSiteDomain}",
|
||||
"id": "00000003-0000-0ff1-ce00-000000000000"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* This script updates the package-solution version analogue to the
|
||||
* the package.json file.
|
||||
*/
|
||||
|
||||
if (process.env.npm_package_version === undefined) {
|
||||
|
||||
throw 'Package version cannot be evaluated';
|
||||
|
||||
}
|
||||
|
||||
// define path to package-solution file
|
||||
const solution = './config/package-solution.json',
|
||||
teams = './teams/manifest.json';
|
||||
|
||||
// require filesystem instance
|
||||
const fs = require('fs');
|
||||
|
||||
// get next automated package version from process variable
|
||||
const nextPkgVersion = process.env.npm_package_version;
|
||||
|
||||
// make sure next build version match
|
||||
const nextVersion = nextPkgVersion.indexOf('-') === -1 ?
|
||||
nextPkgVersion : nextPkgVersion.split('-')[0];
|
||||
|
||||
// Update version in SPFx package-solution if exists
|
||||
if (fs.existsSync(solution)) {
|
||||
|
||||
// read package-solution file
|
||||
const solutionFileContent = fs.readFileSync(solution, 'UTF-8');
|
||||
// parse file as json
|
||||
const solutionContents = JSON.parse(solutionFileContent);
|
||||
|
||||
// set property of version to next version
|
||||
solutionContents.solution.version = nextVersion + '.0';
|
||||
|
||||
// save file
|
||||
fs.writeFileSync(
|
||||
solution,
|
||||
// convert file back to proper json
|
||||
JSON.stringify(solutionContents, null, 2),
|
||||
'UTF-8');
|
||||
|
||||
}
|
||||
|
||||
// Update version in teams manifest if exists
|
||||
if (fs.existsSync(teams)) {
|
||||
|
||||
// read package-solution file
|
||||
const teamsManifestContent = fs.readFileSync(teams, 'UTF-8');
|
||||
// parse file as json
|
||||
const teamsContent = JSON.parse(teamsManifestContent);
|
||||
|
||||
// set property of version to next version
|
||||
teamsContent.version = nextVersion;
|
||||
|
||||
// save file
|
||||
fs.writeFileSync(
|
||||
teams,
|
||||
// convert file back to proper json
|
||||
JSON.stringify(teamsContent, null, 2),
|
||||
'UTF-8');
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.7/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
],
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts", "src/webparts/personalApps/components/AppItem/IAppItem.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|