Fixed exploits in react-events-aggregator

This commit is contained in:
Hugo Bernier 2020-04-11 23:39:39 -04:00
parent 8a46dea6db
commit 6dc1c30927
67 changed files with 580 additions and 24413 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 929 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

View File

@ -1,26 +0,0 @@
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

View File

@ -1,20 +0,0 @@
{
"$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"
}
}

View File

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

View File

@ -1,7 +0,0 @@
{
"$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 -->"
}

View File

@ -1,24 +0,0 @@
{
"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"
}]
]
}

View File

@ -1,18 +0,0 @@
{
"$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"
}
}

View File

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

View File

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

View File

@ -1,54 +0,0 @@
'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

File diff suppressed because it is too large Load Diff

View File

@ -1,67 +0,0 @@
{
"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"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,31 @@
"@microsoft/sp-office-ui-fabric-core": "~1.4.1",
"@types/webpack-env": ">=1.12.1 <1.14.0",
"minimist": ">=1.2.3",
"acorn": ">=5.7.4"
"acorn": ">=5.7.4",
"kind-of": ">=6.0.3",
"cryptiles": ">=4.1.2",
"fstream": ">=1.0.12",
"js-yaml": ">=3.13.1",
"lodash.merge": ">=4.6.2",
"macaddress": ">=0.2.9",
"node.extend": ">=1.1.7",
"randomatic": ">=3.0.0",
"tar": ">=2.2.2",
"atob": ">=2.1.0",
"extend": ">=3.0.2",
"growl": ">=1.10.0",
"lodash.mergewith": ">=4.6.2",
"mem": ">=4.0.0",
"mixin-deep": ">=1.3.2",
"open": ">=6.0.0",
"set-value": ">=2.0.1",
"base64-url": ">=2.0.0",
"fresh": ">=0.5.2",
"handlebars": ">=4.3.0",
"lodash": ">=4.17.12",
"merge": ">=1.2.1",
"morgan": ">=1.9.1",
"stringstream": ">=0.0.6"
},
"devDependencies": {
"@microsoft/sp-build-web": "~1.4.1",

View File

@ -1,75 +1,13 @@
# SharePoint FrameWork client-side web part samples
# React My Personal Apps (Links)
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.
## Summary
The Web Part My Personal Apps allows the user to define links to Applications or Sites for quick access.
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):
This Web Part uses MSgraph Open Extension to save the personal information on user object.
  
![Birthdays Web Part](./assets/image14.png)
![Birthdays Web Part](./assets/image13.png)
![Birthdays Web Part](./assets/image12.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 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" />
- [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/)

View File

@ -1,24 +0,0 @@
{
"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"
}

View File

@ -1,24 +0,0 @@
{
"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"
}

View File

@ -1,24 +0,0 @@
{
"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"
}

View File

@ -1,16 +0,0 @@
$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;

View File

@ -1,30 +0,0 @@
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;
}

View File

@ -1,5 +0,0 @@
export interface IIconPickerState {
items: string[];
currentIcon?: string;
isPanelOpen: boolean;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,138 +0,0 @@
@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;
}

View File

@ -1,146 +0,0 @@
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>;
}
}

View File

@ -1,2 +0,0 @@
export * from './IIconPickerProps';
export * from './IconPicker';

View File

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

View File

@ -1,6 +0,0 @@
export interface IAppItem {
title:string;
name:string;
description: string;
iconName: string;
}

View File

@ -1,6 +0,0 @@
export interface ITenantProperty {
key: string;
Comment?: string;
Description?: string;
Value: string;
}

View File

@ -1,74 +0,0 @@
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;
}
}

View File

@ -1,28 +0,0 @@
{
"$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"
}
}]
}

View File

@ -1,156 +0,0 @@
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
}
]
})
]
}
]
}
]
};
}
}

View File

@ -1,108 +0,0 @@
@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;
}*/

View File

@ -1,34 +0,0 @@
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>
</>
);
};

View File

@ -1,6 +0,0 @@
export interface IAppItemProps {
title:string;
description: string;
iconName: string;
url:string;
}

View File

@ -1,87 +0,0 @@
@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;
}
}

View File

@ -1,31 +0,0 @@
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>
</>
);
};

View File

@ -1,6 +0,0 @@
export interface IAppTileProps {
title:string;
description: string;
iconName: string;
url:string;
}

View File

@ -1,8 +0,0 @@
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;
}

View File

@ -1,9 +0,0 @@
import { IListItem } from "./ManageApps/IListItem";
export interface IPersonalAppsState {
showPanel: boolean;
apps : IListItem[];
isLoading: boolean;
hasError: boolean;
errorMessage: string;
}

View File

@ -1,6 +0,0 @@
export interface IListItem {
name:string;
description:string;
url:string;
iconName:string;
}

View File

@ -1,7 +0,0 @@
import { IListItem } from "./IListItem";
export interface IManageAppsProps {
showPanel: boolean;
onDismiss: (list: IListItem[], changed:boolean) => void;
Apps: IListItem[];
}

View File

@ -1,7 +0,0 @@
import { IManageAppsProps } from "./IManageAppsProps";
export interface IManageAppsState {
columns: any[];
data: IManageAppsProps[];
}

View File

@ -1,171 +0,0 @@
@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´;
}
}

View File

@ -1,331 +0,0 @@
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>
);
}

View File

@ -1,98 +0,0 @@
@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;
}
}
}

View File

@ -1,159 +0,0 @@
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>
</>
);
}
}

View File

@ -1,17 +0,0 @@
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"
}
});

View File

@ -1,20 +0,0 @@
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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,47 +0,0 @@
{
  "$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"
  }
}

View File

@ -1,64 +0,0 @@
/**
* 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');
}

View File

@ -1,40 +0,0 @@
{
"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"
]
}

View File

@ -1,30 +0,0 @@
{
"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
}
}