Merge pull request #1207 from joaojmendes/react-my-personal-apps-update
React My Personal Apps Update - Improve responsive Grid Tiles
BIN
samples/assets/Image06.png
Normal file
After Width: | Height: | Size: 426 KiB |
BIN
samples/assets/Image1.png
Normal file
After Width: | Height: | Size: 2.9 MiB |
BIN
samples/assets/Image10.png
Normal file
After Width: | Height: | Size: 344 KiB |
BIN
samples/assets/Image2.png
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
samples/assets/Image3.png
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
samples/assets/Image4.png
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
samples/assets/Image5.png
Normal file
After Width: | Height: | Size: 2.9 MiB |
BIN
samples/assets/Image7.png
Normal file
After Width: | Height: | Size: 594 KiB |
BIN
samples/assets/Image8.png
Normal file
After Width: | Height: | Size: 471 KiB |
BIN
samples/assets/Image9.png
Normal file
After Width: | Height: | Size: 506 KiB |
BIN
samples/assets/image11.png
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
samples/assets/image12.png
Normal file
After Width: | Height: | Size: 2.7 MiB |
BIN
samples/assets/image13.png
Normal file
After Width: | Height: | Size: 929 KiB |
BIN
samples/assets/image14.png
Normal file
After Width: | Height: | Size: 2.7 MiB |
67
samples/azure-pipelines-build-template.yml
Normal file
@ -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'
|
39
samples/azure-pipelines-deploy-template.yml
Normal file
@ -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
|
26
samples/azure-pipelines.yml
Normal file
@ -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
|
20
samples/config/config.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
4
samples/config/copy-assets.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
7
samples/config/deploy-azure-storage.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "personal-apps",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
24
samples/config/jest.config.json
Normal file
@ -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"
|
||||
}]
|
||||
]
|
||||
}
|
18
samples/config/package-solution.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
10
samples/config/serve.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
4
samples/config/write-manifests.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
54
samples/gulpfile.js
Normal file
@ -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);
|
20434
samples/package-lock.json
generated
Normal file
67
samples/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
@ -24,7 +24,11 @@ This Web Part uses MSgraph Open Extension to save the personal information on us
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
BIN
samples/react-my-personal-apps/assets/image12.png
Normal file
After Width: | Height: | Size: 2.7 MiB |
BIN
samples/react-my-personal-apps/assets/image13.png
Normal file
After Width: | Height: | Size: 929 KiB |
BIN
samples/react-my-personal-apps/assets/image14.png
Normal file
After Width: | Height: | Size: 2.7 MiB |
@ -2,22 +2,7 @@
|
||||
@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 {
|
||||
@ -25,7 +10,7 @@
|
||||
|
||||
min-height: 60px;
|
||||
max-height: 60px;
|
||||
min-width: 300px;
|
||||
|
||||
justify-content: start;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 5px;
|
||||
@ -33,11 +18,14 @@
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: $ms-color-neutralTertiaryAlt;
|
||||
padding: 20px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
align-items: flex-start;
|
||||
background-color:$bodyBackgroundColor;
|
||||
color: $bodyTextColor;
|
||||
flex: 0 1 365px;
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
|
@ -4,17 +4,16 @@
|
||||
|
||||
.tile {
|
||||
display: flex;
|
||||
max-width: 120px;
|
||||
min-height: 120px;
|
||||
max-height: 120px;
|
||||
min-width: 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;
|
||||
/* padding: 10px; */
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
justify-content: center;
|
||||
|
@ -3,12 +3,14 @@ import { IAppTileProps } from "./IAppTileProps";
|
||||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
import styles from "./AppTile.module.scss";
|
||||
import { PropertyFieldCollectionDataHost } from "@pnp/spfx-property-controls/lib/PropertyFieldCollectionData";
|
||||
|
||||
export const AppTile = (props: IAppTileProps) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={styles.tile}
|
||||
title={props.description}
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
window.open(props.url, "_blank");
|
||||
|
@ -20,12 +20,20 @@
|
||||
|
||||
.personalApps {
|
||||
padding-top: 15px;
|
||||
.container {
|
||||
.containerTiles {
|
||||
margin-top:15px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat( auto-fit, minmax(120px, 1fr) );
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
.containerItems {
|
||||
margin-top:15px;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat( auto-fit, minmax(220px, 1fr) );
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
|
@ -102,7 +102,7 @@ export default class PersonalApps extends React.Component<
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className={styles.container}>
|
||||
|
||||
{isLoading && (
|
||||
<div
|
||||
style={{
|
||||
@ -119,7 +119,7 @@ export default class PersonalApps extends React.Component<
|
||||
{errorMessage}
|
||||
</MessageBar>
|
||||
)}
|
||||
|
||||
<div className={view == 'Tiles' ? styles.containerTiles : styles.containerItems }>
|
||||
{apps &&
|
||||
apps.length > 0 &&
|
||||
apps.map(item => {
|
||||
|
@ -1,13 +1,75 @@
|
||||
# SharePoint FrameWork client-side web part samples
|
||||
|
||||
Samples around the SharePoint Framework client-side web parts to demonstrate different capabilities and possibilities on the framework. Each sample has it's own dedicated readme file to explain setup instructions and demonstrated capability.
|
||||
# React My Personal Apps (Links)
|
||||
|
||||
You can head directly to the folders below and start looking around if you'd like. But if you're looking for something specific, we've grouped our samples into various categories on our [
|
||||
SharePoint Framework Client-Side Web Part Samples site](https://sharepoint.github.io/sp-dev-fx-webparts):
|
||||
## Summary
|
||||
The Web Part My Personal Apps allows the user to define links to Applications or Sites for quick access.
|
||||
|
||||
- [By Framework](https://sharepoint.github.io/sp-dev-fx-webparts/samples/framework/)
|
||||
- [By SPFx Version](https://sharepoint.github.io/sp-dev-fx-webparts/samples/spfx/)
|
||||
- [By Compatibility](https://sharepoint.github.io/sp-dev-fx-webparts/samples/compatibility/)
|
||||
- [By Year](https://sharepoint.github.io/sp-dev-fx-webparts/samples/year/)
|
||||
- [By Author](https://sharepoint.github.io/sp-dev-fx-webparts/samples/author/)
|
||||
- [All](https://sharepoint.github.io/sp-dev-fx-webparts/samples/all/)
|
||||
This Web Part uses MSgraph Open Extension to save the personal information on user object.
|
||||
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||

|
||||
|
||||
## 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 your tenant's App Catalog
|
||||
- Go to **API Access** - from **SharePoint Admin Center** new experience, and **Approve** the permission to use Microsoft Graph scope **User.ReadWrite.All**
|
||||
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-my-personal-apps" />
|
||||
|
24
samples/src/Common/TeamsContrastTheme.json
Executable file
@ -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"
|
||||
}
|
24
samples/src/Common/TeamsDarkTheme.json
Executable file
@ -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"
|
||||
}
|
24
samples/src/Common/TeamsDefaultTheme.json
Executable file
@ -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"
|
||||
}
|
16
samples/src/Common/themeColors.module.scss
Normal file
@ -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;
|
30
samples/src/controls/iconPicker/IIconPickerProps.ts
Normal file
@ -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;
|
||||
}
|
5
samples/src/controls/iconPicker/IIconPickerState.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface IIconPickerState {
|
||||
items: string[];
|
||||
currentIcon?: string;
|
||||
isPanelOpen: boolean;
|
||||
}
|
1466
samples/src/controls/iconPicker/IconNames.ts
Normal file
138
samples/src/controls/iconPicker/IconPicker.module.scss
Normal file
@ -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;
|
||||
}
|
146
samples/src/controls/iconPicker/IconPicker.tsx
Normal file
@ -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>;
|
||||
}
|
||||
}
|
2
samples/src/controls/iconPicker/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './IIconPickerProps';
|
||||
export * from './IconPicker';
|
1
samples/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
6
samples/src/services/IAppItem.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface IAppItem {
|
||||
title:string;
|
||||
name:string;
|
||||
description: string;
|
||||
iconName: string;
|
||||
}
|
6
samples/src/services/ITenantProperty.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface ITenantProperty {
|
||||
key: string;
|
||||
Comment?: string;
|
||||
Description?: string;
|
||||
Value: string;
|
||||
}
|
74
samples/src/services/dataservices.ts
Executable file
@ -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"
|
||||
}
|
||||
}]
|
||||
}
|
156
samples/src/webparts/personalApps/PersonalAppsWebPart.ts
Normal file
@ -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,108 @@
|
||||
@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";
|
||||
|
||||
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
|
||||
min-height: 60px;
|
||||
max-height: 60px;
|
||||
|
||||
justify-content: start;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 5px;
|
||||
flex-direction: row;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: $ms-color-neutralTertiaryAlt;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
align-items: flex-start;
|
||||
background-color:$bodyBackgroundColor;
|
||||
color: $bodyTextColor;
|
||||
max-width:100%;
|
||||
}
|
||||
|
||||
.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,87 @@
|
||||
@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;
|
||||
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,31 @@
|
||||
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";
|
||||
import { PropertyFieldCollectionDataHost } from "@pnp/spfx-property-controls/lib/PropertyFieldCollectionData";
|
||||
|
||||
export const AppTile = (props: IAppTileProps) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={styles.tile}
|
||||
title={props.description}
|
||||
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,98 @@
|
||||
@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;
|
||||
.containerTiles {
|
||||
margin-top:15px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat( auto-fit, minmax(120px, 1fr) );
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
.containerItems {
|
||||
margin-top:15px;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat( auto-fit, minmax(220px, 1fr) );
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
159
samples/src/webparts/personalApps/components/PersonalApps.tsx
Normal file
@ -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 });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isLoading && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Spinner size={SpinnerSize.medium} />
|
||||
</div>
|
||||
)}
|
||||
{hasError && (
|
||||
<MessageBar messageBarType={MessageBarType.error}>
|
||||
{errorMessage}
|
||||
</MessageBar>
|
||||
)}
|
||||
<div className={view == 'Tiles' ? styles.containerTiles : styles.containerItems }>
|
||||
{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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
17
samples/src/webparts/personalApps/loc/en-us.js
Normal file
@ -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"
|
||||
}
|
||||
});
|
20
samples/src/webparts/personalApps/loc/mystrings.d.ts
vendored
Normal file
@ -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;
|
||||
}
|
BIN
samples/teams/4dfcaee3-8a85-4d80-9fa3-23bcb1e08923_color.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
samples/teams/4dfcaee3-8a85-4d80-9fa3-23bcb1e08923_outline.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
47
samples/teams/manifest.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
64
samples/tools/pre-version.js
Normal file
@ -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');
|
||||
|
||||
}
|
40
samples/tsconfig.json
Normal file
@ -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"
|
||||
]
|
||||
}
|
30
samples/tslint.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|