Merge pull request #3 from pnp/master
|
@ -52,6 +52,7 @@ Version|Date|Comments
|
|||
1.2|June 04, 2020|Added full-width support
|
||||
1.3|July 07, 2020|Simplified web part
|
||||
1.4|July 28, 2020|Update styles to minimise toolbar overlap
|
||||
1.5|July 30, 2020|Update styles to improve full-width mode
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"solution": {
|
||||
"name": "workbench-customizer-client-side-solution",
|
||||
"id": "5d6f4a5a-9d2b-4a93-a283-16b8f5ea75d6",
|
||||
"version": "1.4.0.0",
|
||||
"version": "1.5.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "workbench-customizer",
|
||||
"version": "1.4.0",
|
||||
"version": "1.5.0",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
.CanvasZone {
|
||||
margin: auto;
|
||||
padding-left: 24px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.CanvasZone--noMargin {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
.CanvasComponent {
|
||||
.CanvasZone {
|
||||
max-width: 100%;
|
||||
padding-left: 0;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0;
|
||||
|
||||
.CanvasSection {
|
||||
|
|
|
@ -4902,9 +4902,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
|
||||
"integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==",
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
# SPFx Avatar
|
||||
|
||||
## Summary
|
||||
|
||||
This is a sample web part that helps user create their avatar and save as profile picture. User can even download their avatar as PNG file. This webpart can be useful in Intranet to help user create their avatar and save it as profile picture.
|
||||
|
||||
|
||||
##
|
||||
![directory](/samples/react-avatar/assets/reactAvatarOutcome.gif)
|
||||
|
||||
## Features
|
||||
|
||||
* Avatar Generator from multiple options available
|
||||
* Set Avatar as Profile Picture.
|
||||
* Download Avatar as PNG file.
|
||||
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/version-1.10.0-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Online](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* SharePoint Online tenant
|
||||
* You have to provide permission in SharePoint admin for accessing Graph API on behalf of your solution. We can do it before deployment as proactive steps, or after deployment. You can refer to [steps about how to do this post-deployment](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/use-aad-tutorial#deploy-the-solution-and-grant-permissions). You have to use API Access Page of SharePoint admin and add below permission for our use case.
|
||||
|
||||
```
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.ReadWrite"
|
||||
}
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
## Concepts
|
||||
|
||||
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
* Using react framework in SPFx webpart
|
||||
* Calling Graph API my Photo to store the image in SPFx webpart
|
||||
* Usage of Material UI Tab and Dialog
|
||||
* Usage of [avataaars](https://getavataaars.com/)
|
||||
|
||||
|
||||
## WebPart Properties
|
||||
|
||||
Property |Type|Required| comments
|
||||
--------------------|----|--------|----------
|
||||
Web Part Title | Text| no|
|
||||
|
||||
|
||||
## Solution
|
||||
The web part Use avataaars library for creating avatars and MS Graph with User.ReadWrite and User.ReadWriteAll for saving avatar as current users Profile Picture.FileSaver for downloading avatar image as png file.
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react Avatar|Kunj Sangani
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0.0|August 1, 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
|
||||
|
||||
- Clone this repository
|
||||
- in the command line run:
|
||||
- `npm install`
|
||||
- `gulp build`
|
||||
- `gulp bundle --ship`
|
||||
- `gulp package-solution --ship`
|
||||
- `Add to AppCatalog and deploy`
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-avatar" />
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 962 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"avatar-generator-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/avatarGenerator/AvatarGeneratorWebPart.js",
|
||||
"manifest": "./src/webparts/avatarGenerator/AvatarGeneratorWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"AvatarGeneratorWebPartStrings": "lib/webparts/avatarGenerator/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-avatars",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-avatars-client-side-solution",
|
||||
"id": "b96dfed7-ec48-4082-ba50-f6c7b09143c7",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"isDomainIsolated": false,
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.ReadWrite"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-avatars.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(require('gulp'));
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "react-avatars",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@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",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "16.8.8",
|
||||
"@types/react-dom": "16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"avataaars": "^1.2.1",
|
||||
"file-saver": "^2.0.2",
|
||||
"office-ui-fabric-react": "6.189.2",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.8.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.10.0",
|
||||
"@microsoft/sp-tslint-rules": "1.10.0",
|
||||
"@microsoft/sp-module-interfaces": "1.10.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.10.0",
|
||||
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "4052b4ec-72e9-4248-b411-18a4cd438d4b",
|
||||
"alias": "AvatarGeneratorWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": ["SharePointWebPart"],
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "AvatarGenerator" },
|
||||
"description": { "default": "AvatarGenerator for SharePoint Profile Picture" },
|
||||
"officeFabricIconFontName": "ContactInfo",
|
||||
"properties": {
|
||||
"description": "AvatarGenerator"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
|
||||
import * as strings from 'AvatarGeneratorWebPartStrings';
|
||||
import AvatarGenerator from './components/AvatarGenerator';
|
||||
import { IAvatarGeneratorProps } from './components/IAvatarGeneratorProps';
|
||||
|
||||
|
||||
|
||||
export interface IAvatarGeneratorWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class AvatarGeneratorWebPart extends BaseClientSideWebPart <IAvatarGeneratorWebPartProps> {
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IAvatarGeneratorProps> = React.createElement(
|
||||
AvatarGenerator,
|
||||
{
|
||||
description: this.properties.description,
|
||||
context:this.context
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('description', {
|
||||
label: 'Web Part Title'
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.avatarGenerator {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
}
|
||||
|
||||
.column4 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg4;
|
||||
@include ms-xl4;
|
||||
}
|
||||
|
||||
.column8 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg8;
|
||||
@include ms-xl8;
|
||||
}
|
||||
|
||||
.piece{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: $ms-color-themePrimary;
|
||||
border-color: $ms-color-themePrimary;
|
||||
color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: $ms-font-size-m;
|
||||
font-weight: $ms-font-weight-regular;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
:global{
|
||||
#workbenchPageContent{
|
||||
max-width: 1200px;
|
||||
}
|
||||
.ms-ChoiceField{
|
||||
margin-left: 40px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,422 @@
|
|||
import * as PropTypes from 'prop-types';
|
||||
import * as React from 'react';
|
||||
import styles from './AvatarGenerator.module.scss';
|
||||
import { IAvatarGeneratorProps } from './IAvatarGeneratorProps';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
import { Avatar, Piece, allOptions, OptionContext, AvatarStyle } from "avataaars";
|
||||
import { TabPanel } from "./TabPanel";
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import * as FileSaver from 'file-saver';
|
||||
import { MSGraphClient } from "@microsoft/sp-http";
|
||||
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
|
||||
const options: IChoiceGroupOption[] = [
|
||||
{ key: AvatarStyle.Circle, text: 'Circle' },
|
||||
{ key: AvatarStyle.Transparent, text: 'Transparent' }
|
||||
];
|
||||
|
||||
export interface IAvatarGeneratorState {
|
||||
avatarStyle: AvatarStyle;
|
||||
value: number;
|
||||
open: boolean;
|
||||
savedMessage: string;
|
||||
savedMessageTitle: string;
|
||||
}
|
||||
|
||||
export default class AvatarGenerator extends React.Component<IAvatarGeneratorProps, IAvatarGeneratorState> {
|
||||
public static childContextTypes = {
|
||||
optionContext: PropTypes.instanceOf(OptionContext)
|
||||
};
|
||||
|
||||
private avatarRef: Avatar | null = null;
|
||||
private canvasRef: HTMLCanvasElement | null = null;
|
||||
|
||||
private optionContext: OptionContext = new OptionContext(allOptions);
|
||||
|
||||
constructor(props: IAvatarGeneratorProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
avatarStyle: AvatarStyle.Circle,
|
||||
value: 0,
|
||||
open: false,
|
||||
savedMessage: "",
|
||||
savedMessageTitle: ""
|
||||
};
|
||||
}
|
||||
|
||||
public getChildContext() {
|
||||
return { optionContext: this.optionContext };
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
|
||||
const { optionContext } = this;
|
||||
const value = optionContext.options;
|
||||
optionContext.options.map((option, index) => {
|
||||
const optionState = optionContext.getOptionState(option.key)!;
|
||||
if (optionState.available <= 0) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
public a11yProps(index) {
|
||||
return {
|
||||
id: `scrollable-auto-tab-${index}`,
|
||||
'aria-controls': `scrollable-auto-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
private triggerDownload = (imageBlob: Blob, fileName: string) => {
|
||||
FileSaver.saveAs(imageBlob, fileName);
|
||||
}
|
||||
|
||||
private saveAsProfile = (imageBlob: Blob) => {
|
||||
var reader = new FileReader();
|
||||
var tempfile = new File([imageBlob], "myavataaars.png", { type: "image/png" });
|
||||
this.props.context.msGraphClientFactory
|
||||
.getClient().then((client: MSGraphClient) => {
|
||||
client
|
||||
.api("me/photo/$value")
|
||||
.version("v1.0").header("Content-Type", "image/png").put(tempfile, (err, res) => {
|
||||
if (!err) {
|
||||
this.setState({
|
||||
open: true, savedMessage: "Your profile picture is updated",
|
||||
savedMessageTitle: "Profile Picture Saved"
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
open: true, savedMessage: err.message,
|
||||
savedMessageTitle: "Error while saving Profile Picture"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private onDownloadPNG = (isDownload: boolean) => {
|
||||
const svgNode = ReactDOM.findDOMNode(this.avatarRef!);
|
||||
const canvas = this.canvasRef!;
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const anyWindow = window as any;
|
||||
const DOMURL = anyWindow.URL || anyWindow.webkitURL || window;
|
||||
|
||||
const data = svgNode["outerHTML"];
|
||||
const img = new Image(canvas.width, canvas.height);
|
||||
const svg = new Blob([data], { type: 'image/svg+xml' });
|
||||
const url = DOMURL.createObjectURL(svg);
|
||||
|
||||
img.onload = () => {
|
||||
ctx.save();
|
||||
ctx.scale(2, 2);
|
||||
ctx.drawImage(img, 0, 0);
|
||||
ctx.restore();
|
||||
DOMURL.revokeObjectURL(url);
|
||||
this.canvasRef!.toBlob(imageBlob => {
|
||||
if (isDownload) {
|
||||
this.triggerDownload(imageBlob!, 'myavataaars.png');
|
||||
} else {
|
||||
this.saveAsProfile(imageBlob!);
|
||||
}
|
||||
});
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
|
||||
public _onChange = (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption): void => {
|
||||
this.setState({ avatarStyle: option.key as AvatarStyle });
|
||||
}
|
||||
|
||||
public handleClose = () => {
|
||||
this.setState({ open: false });
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IAvatarGeneratorProps> {
|
||||
let count: number = -1;
|
||||
let internalcount: number = -1;
|
||||
return (
|
||||
<div className={styles.avatarGenerator}>
|
||||
{this.props.description &&
|
||||
<h1>{this.props.description}</h1>}
|
||||
<div className={styles.container}>
|
||||
<div className={styles.row} style={{ textAlign: "center" }}>
|
||||
<div className={styles.column4}>
|
||||
<Avatar ref={(ref) => { this.avatarRef = ref; }}
|
||||
avatarStyle={this.state.avatarStyle}
|
||||
/>
|
||||
<ChoiceGroup styles={{ flexContainer: { display: "flex" } }}
|
||||
defaultSelectedKey={AvatarStyle.Circle} options={options} onChange={this._onChange} label="Avatar Style" />
|
||||
<Button variant="contained" style={{ marginTop: 10 }} color="primary" onClick={(ev) => {
|
||||
this.onDownloadPNG(false);
|
||||
}}>
|
||||
Save as Profile Picture
|
||||
</Button>
|
||||
<Button variant="contained" style={{ marginTop: 10 }} color="primary" onClick={(ev) => {
|
||||
this.onDownloadPNG(true);
|
||||
}}>
|
||||
Download as Image
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.column8}>
|
||||
<AppBar position="static">
|
||||
<Tabs
|
||||
variant="scrollable"
|
||||
scrollButtons="on"
|
||||
indicatorColor="primary"
|
||||
value={this.state.value}
|
||||
onChange={(ev, num) => { this.setState({ value: num }); }}
|
||||
aria-label="simple tabs example">
|
||||
{this.optionContext.options.map((option, index) => {
|
||||
const optionState = this.optionContext.getOptionState(option.key)!;
|
||||
if (optionState.available <= 0) {
|
||||
return null;
|
||||
} else {
|
||||
count++;
|
||||
return <Tab label={option.label} {...this.a11yProps(count)}></Tab>;
|
||||
}
|
||||
})}
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
{this.optionContext.options.map((option, index) => {
|
||||
const optionState = this.optionContext.getOptionState(option.key)!;
|
||||
if (optionState.available <= 0) {
|
||||
return null;
|
||||
} else {
|
||||
internalcount++;
|
||||
switch (option.key) {
|
||||
case "topType":
|
||||
return <TabPanel value={this.state.value} index={internalcount}>
|
||||
{optionState.options
|
||||
.map(type => {
|
||||
return (<div className={styles.piece}
|
||||
onClick={(ev) => {
|
||||
var selectedData = this.optionContext["_data"];
|
||||
selectedData[`${option.key}`] = type;
|
||||
this.optionContext.setData(selectedData as any);
|
||||
}}
|
||||
><Piece avatarStyle="Circle"
|
||||
pieceType="top"
|
||||
pieceSize="100"
|
||||
topType={type} /></div>);
|
||||
})}
|
||||
</TabPanel>;
|
||||
case "accessoriesType":
|
||||
return <TabPanel value={this.state.value} index={internalcount}>
|
||||
{optionState.options
|
||||
.map(type => {
|
||||
return (<div className={styles.piece}
|
||||
onClick={(ev) => {
|
||||
var selectedData = this.optionContext["_data"];
|
||||
selectedData[`${option.key}`] = type;
|
||||
this.optionContext.setData(selectedData as any);
|
||||
}}
|
||||
><Piece avatarStyle=""
|
||||
pieceType="accessories"
|
||||
pieceSize="100"
|
||||
accessoriesType={type} /></div>);
|
||||
})}
|
||||
</TabPanel>;
|
||||
case "hairColor":
|
||||
return <TabPanel value={this.state.value} index={internalcount}>
|
||||
{optionState.options
|
||||
.map(type => {
|
||||
return (<div className={styles.piece}
|
||||
onClick={(ev) => {
|
||||
var selectedData = this.optionContext["_data"];
|
||||
selectedData[`${option.key}`] = type;
|
||||
this.optionContext.setData(selectedData as any);
|
||||
}}
|
||||
> <Piece
|
||||
avatarStyle=""
|
||||
pieceType="top"
|
||||
pieceSize="100"
|
||||
topType="LongHairFro"
|
||||
hairColor={type} />
|
||||
</div>);
|
||||
})}
|
||||
</TabPanel>;
|
||||
break;
|
||||
case "facialHairType":
|
||||
return <TabPanel value={this.state.value} index={internalcount}>
|
||||
{optionState.options
|
||||
.map(type => {
|
||||
return (<div className={styles.piece}
|
||||
onClick={(ev) => {
|
||||
var selectedData = this.optionContext["_data"];
|
||||
selectedData[`${option.key}`] = type;
|
||||
this.optionContext.setData(selectedData as any);
|
||||
}}
|
||||
><Piece avatarStyle=""
|
||||
pieceType="facialHair"
|
||||
pieceSize="100"
|
||||
facialHairType={type} /></div>);
|
||||
})}
|
||||
</TabPanel>;
|
||||
break;
|
||||
case "clotheType":
|
||||
return <TabPanel value={this.state.value} index={internalcount}>
|
||||
{optionState.options
|
||||
.map(type => {
|
||||
return (<div className={styles.piece}
|
||||
onClick={(ev) => {
|
||||
var selectedData = this.optionContext["_data"];
|
||||
selectedData[`${option.key}`] = type;
|
||||
this.optionContext.setData(selectedData as any);
|
||||
}}
|
||||
><Piece avatarStyle=""
|
||||
pieceType="clothe"
|
||||
pieceSize="100"
|
||||
clotheType={type} /></div>);
|
||||
})}
|
||||
</TabPanel>;
|
||||
break;
|
||||
case "clotheColor":
|
||||
return <TabPanel value={this.state.value} index={internalcount}>
|
||||
{optionState.options
|
||||
.map(type => {
|
||||
return (<div className={styles.piece}
|
||||
onClick={(ev) => {
|
||||
var selectedData = this.optionContext["_data"];
|
||||
selectedData[`${option.key}`] = type;
|
||||
this.optionContext.setData(selectedData as any);
|
||||
}}
|
||||
><Piece avatarStyle=""
|
||||
pieceType="clotheColor"
|
||||
pieceSize="100"
|
||||
clotheColor={type} /></div>);
|
||||
})}
|
||||
</TabPanel>;
|
||||
break;
|
||||
case "graphicType":
|
||||
return <TabPanel value={this.state.value} index={internalcount}>
|
||||
{optionState.options
|
||||
.map(type => {
|
||||
return (<div className={styles.piece}
|
||||
onClick={(ev) => {
|
||||
var selectedData = this.optionContext["_data"];
|
||||
selectedData[`${option.key}`] = type;
|
||||
this.optionContext.setData(selectedData as any);
|
||||
}}
|
||||
><Piece avatarStyle=""
|
||||
pieceType="graphics"
|
||||
pieceSize="100"
|
||||
graphicType={type} /></div>);
|
||||
})}
|
||||
</TabPanel>;
|
||||
break;
|
||||
case "eyeType":
|
||||
return <TabPanel value={this.state.value} index={internalcount}>
|
||||
{optionState.options
|
||||
.map(type => {
|
||||
return (<div className={styles.piece}
|
||||
onClick={(ev) => {
|
||||
var selectedData = this.optionContext["_data"];
|
||||
selectedData[`${option.key}`] = type;
|
||||
this.optionContext.setData(selectedData as any);
|
||||
}}
|
||||
><Piece avatarStyle=""
|
||||
pieceType="eyes"
|
||||
pieceSize="100"
|
||||
eyeType={type} /></div>);
|
||||
})}
|
||||
</TabPanel>;
|
||||
break;
|
||||
case "eyebrowType":
|
||||
return <TabPanel value={this.state.value} index={internalcount}>
|
||||
{optionState.options
|
||||
.map(type => {
|
||||
return (<div className={styles.piece}
|
||||
onClick={(ev) => {
|
||||
var selectedData = this.optionContext["_data"];
|
||||
selectedData[`${option.key}`] = type;
|
||||
this.optionContext.setData(selectedData as any);
|
||||
}}
|
||||
><Piece avatarStyle=""
|
||||
pieceType="eyebrows"
|
||||
pieceSize="100"
|
||||
eyebrowType={type} /></div>);
|
||||
})}
|
||||
</TabPanel>;
|
||||
break;
|
||||
case "mouthType":
|
||||
return <TabPanel value={this.state.value} index={internalcount}>
|
||||
{optionState.options
|
||||
.map(type => {
|
||||
return (<div className={styles.piece}
|
||||
onClick={(ev) => {
|
||||
var selectedData = this.optionContext["_data"];
|
||||
selectedData[`${option.key}`] = type;
|
||||
this.optionContext.setData(selectedData as any);
|
||||
}}
|
||||
><Piece avatarStyle=""
|
||||
pieceType="mouth"
|
||||
pieceSize="100"
|
||||
mouthType={type} /></div>);
|
||||
})}
|
||||
</TabPanel>;
|
||||
break;
|
||||
case "skinColor":
|
||||
return <TabPanel value={this.state.value} index={internalcount}>
|
||||
{optionState.options
|
||||
.map(type => {
|
||||
return (<div className={styles.piece}
|
||||
onClick={(ev) => {
|
||||
var selectedData = this.optionContext["_data"];
|
||||
selectedData[`${option.key}`] = type;
|
||||
this.optionContext.setData(selectedData as any);
|
||||
}}
|
||||
><Piece avatarStyle=""
|
||||
pieceType="skin"
|
||||
pieceSize="100"
|
||||
skinColor={type} /></div>);
|
||||
})}
|
||||
</TabPanel>;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<canvas
|
||||
style={{ display: "none" }}
|
||||
width='528'
|
||||
height='560'
|
||||
ref={(ref) => { this.canvasRef = ref; }}
|
||||
/>
|
||||
</div>
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">{this.state.savedMessageTitle}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{this.state.savedMessage}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleClose} color="primary">
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
export interface IAvatarGeneratorProps {
|
||||
description: string;
|
||||
context:WebPartContext;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import * as React from 'react';
|
||||
import Box from '@material-ui/core/Box';
|
||||
|
||||
export interface ITabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
index: any;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export const TabPanel: React.FunctionComponent<ITabPanelProps> = (props: ITabPanelProps) => {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`scrollable-auto-tabpanel-${index}`}
|
||||
aria-labelledby={`scrollable-auto-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && (
|
||||
<Box p={3}>
|
||||
<div>{children}</div>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface IAvatarGeneratorWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'AvatarGeneratorWebPartStrings' {
|
||||
const strings: IAvatarGeneratorWebPartStrings;
|
||||
export = strings;
|
||||
}
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.10.0",
|
||||
"libraryName": "react-dynamic-365",
|
||||
"libraryId": "5bc3855d-9c59-4322-95e0-78e17dcd809e",
|
||||
"packageManager": "npm",
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
# React Dynamics CRM API
|
||||
|
||||
## Summary
|
||||
This sample shows how to consume Dynamics CRM API using AadTokenProvider class.
|
||||
|
||||
![react-dynamics365-api](./assets/screenshot.gif)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![SPFx v1.10.0](https://img.shields.io/badge/SPFx-1.10.0-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework Developer](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
|
||||
* [Office 365 developer tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-dynamics365-api|Ramin Ahmadi
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0.0|Jul 12, 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
|
||||
|
||||
* Clone this repository
|
||||
* in the command line run:
|
||||
* `npm install`
|
||||
* `gulp serve`
|
||||
|
||||
### Configuration
|
||||
|
||||
* Login to Azure Portal.
|
||||
* Go to **Azure Active Directory**
|
||||
* Go to the **App Registrations** page.
|
||||
* Select **SharePoint Online Client Extensibility** from the list of applications.
|
||||
* Select **API Permissions**.
|
||||
* Add **Dynamics CRM** permission.
|
||||
* Go to the **Manifest** page, and make sure the value for the `allowPublicClient` and the `oauth2AllowImplicitFlow` are both set to `true`.
|
||||
|
||||
## Features
|
||||
|
||||
This sample illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
* Using AadTokenProvider to consume Dynamics CRM API.
|
||||
* How to get Accounts/Contacts information from Dynamics 365.
|
||||
* React Hooks
|
||||
* Using async / await for the async calls
|
||||
* Ant design for the UI.
|
||||
|
||||
> **NOTE:** This sample will not work in the local workbench.
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-dynamics-crm-api" />
|
After Width: | Height: | Size: 490 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"dynamic-365-sales-accounts-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/dynamic365SalesAccounts/Dynamic365SalesAccountsWebPart.js",
|
||||
"manifest": "./src/webparts/dynamic365SalesAccounts/Dynamic365SalesAccountsWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"Dynamic365SalesAccountsWebPartStrings": "lib/webparts/dynamic365SalesAccounts/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-dynamic-365",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-dynamic-365-client-side-solution",
|
||||
"id": "5bc3855d-9c59-4322-95e0-78e17dcd809e",
|
||||
"version": "1.0.0.1",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Windows Azure Active Directory",
|
||||
"scope": "User.Read"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-dynamic-365.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(require('gulp'));
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "react-dynamic-365",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.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",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "16.8.8",
|
||||
"@types/react-dom": "16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"antd": "^4.4.2",
|
||||
"axios": "^0.19.2",
|
||||
"office-ui-fabric-react": "6.189.2",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.8.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/rush-stack-compiler-3.7": "^0.6.1",
|
||||
"@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",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
type Listener<T> = (val: T) => void;
|
||||
type Unsubscriber = () => void;
|
||||
|
||||
export class Observable<T> {
|
||||
private _listeners: Listener<T>[] = [];
|
||||
|
||||
constructor(private _val: T) {
|
||||
}
|
||||
|
||||
public get(): T {
|
||||
return this._val;
|
||||
}
|
||||
|
||||
public set(val: T) {
|
||||
if (this._val !== val) {
|
||||
this._val = val;
|
||||
this._listeners.forEach(l => l(val));
|
||||
}
|
||||
}
|
||||
|
||||
public subscribe(listener: Listener<T>): Unsubscriber {
|
||||
this._listeners.push(listener);
|
||||
return () => {
|
||||
this._listeners = this._listeners.filter(l => l !== listener);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { Observable } from "./observable";
|
||||
|
||||
export default function useObservable<T>(observable: Observable<T>): T {
|
||||
const [val, setVal] = useState(observable.get());
|
||||
|
||||
useEffect(() => {
|
||||
return observable.subscribe(setVal);
|
||||
}, [observable]);
|
||||
|
||||
return val;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,5 @@
|
|||
export default interface IAccount{
|
||||
name:string;
|
||||
emailaddress1:string;
|
||||
accountid:string;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export default interface IContact{
|
||||
fullname : string;
|
||||
emailaddress1:string;
|
||||
telephone1:string;
|
||||
address1_city:string;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import axios from "axios";
|
||||
import { Observable } from "../hooks/observable";
|
||||
import { AadTokenProviderFactory } from "@microsoft/sp-http";
|
||||
export default class DynamicsService {
|
||||
public readonly errorMessage = new Observable<string>(null);
|
||||
private accessToken: string;
|
||||
public aadTokenProviderFactory: AadTokenProviderFactory;
|
||||
// update ResourceURI based on your dynamic crm uri
|
||||
public resourceUri:string;
|
||||
|
||||
public async getAccessToken(){
|
||||
const token = sessionStorage.getItem("dynamic365Token");
|
||||
if(token)
|
||||
this.accessToken = token;
|
||||
else{
|
||||
await
|
||||
this.aadTokenProviderFactory
|
||||
.getTokenProvider()
|
||||
.then((tokenProvider) => {
|
||||
tokenProvider
|
||||
.getToken(this.resourceUri)
|
||||
.then((t) => {
|
||||
this.accessToken = t;
|
||||
sessionStorage.setItem("dynamic365Token",t);
|
||||
})
|
||||
.catch((err) => console.log("Error: " + err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async getAccounts() {
|
||||
const url = `${this.resourceUri}/api/data/v9.0/accounts?$top=20&$select=name,emailaddress1`;
|
||||
const response = await axios({
|
||||
url,
|
||||
method: "GET",
|
||||
headers:{"Authorization":`Bearer ${this.accessToken}`}
|
||||
});
|
||||
return response.data.value;
|
||||
}
|
||||
|
||||
public async getContacts(id:string){
|
||||
const url = `${this.resourceUri}/api/data/v9.0/contacts?$top=5&$select=fullname,emailaddress1,telephone1,address1_city&$filter=_accountid_value eq '${id}'`;
|
||||
const response = await axios({
|
||||
url,
|
||||
method: "GET",
|
||||
headers:{"Authorization":`Bearer ${this.accessToken}`}
|
||||
});
|
||||
return response.data.value;
|
||||
}
|
||||
|
||||
public async searchAccounts (name:string){
|
||||
const url = `${this.resourceUri}/api/data/v9.0/accounts?$top=20&$select=name,emailaddress1&$filter=contains(name,'${name}')`;
|
||||
const response = await axios({
|
||||
url,
|
||||
method: "GET",
|
||||
headers:{"Authorization":`Bearer ${this.accessToken}`}
|
||||
});
|
||||
return response.data.value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import DynamicsService from './dynamicsService';
|
||||
export const dynamicsService = new DynamicsService();
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "2a144942-a8bc-4e51-8f37-6ca58450bc00",
|
||||
"alias": "Dynamic365SalesAccountsWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": ["SharePointWebPart","TeamsPersonalApp","TeamsTab"],
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "Dynamic 365 Sales Accounts" },
|
||||
"description": { "default": "Dynamic 365 Sales Accounts description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "Dynamic 365 Sales Accounts"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import { dynamicsService } from "./../../services/services";
|
||||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
import { Version } from "@microsoft/sp-core-library";
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
} from "@microsoft/sp-property-pane";
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import * as strings from "Dynamic365SalesAccountsWebPartStrings";
|
||||
import Dynamic365SalesAccounts from "./components/Dynamic365SalesAccounts";
|
||||
import { IDynamic365SalesAccountsProps } from "./components/IDynamic365SalesAccountsProps";
|
||||
import "antd/dist/antd.css";
|
||||
|
||||
export interface IDynamic365SalesAccountsWebPartProps {
|
||||
dynamicCRMDomain: string;
|
||||
}
|
||||
|
||||
export default class Dynamic365SalesAccountsWebPart extends BaseClientSideWebPart<
|
||||
IDynamic365SalesAccountsWebPartProps
|
||||
> {
|
||||
public async onInit(): Promise<void> {
|
||||
dynamicsService.aadTokenProviderFactory = this.context.aadTokenProviderFactory;
|
||||
dynamicsService.resourceUri = `https://${this.properties.dynamicCRMDomain}.dynamics.com`;
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IDynamic365SalesAccountsProps> = React.createElement(
|
||||
Dynamic365SalesAccounts
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse("1.0");
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField("dynamicCRMDomain", {
|
||||
label: strings.DynamicCrmDomainFieldLabel,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.dynamic365SalesAccounts {
|
||||
.container {
|
||||
max-width: 700px;
|
||||
margin: 0px auto;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: $ms-color-themePrimary;
|
||||
border-color: $ms-color-themePrimary;
|
||||
color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: $ms-font-size-m;
|
||||
font-weight: $ms-font-weight-regular;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import * as React from "react";
|
||||
import styles from "./Dynamic365SalesAccounts.module.scss";
|
||||
import { dynamicsService } from "../../../services/services";
|
||||
import IAccount from "../../../model/IAccount";
|
||||
import Search from "antd/lib/input/Search";
|
||||
import { Table, Divider } from 'antd';
|
||||
import Contacts from './contacts';
|
||||
|
||||
const Dynamic365SalesAccounts = () => {
|
||||
const [accounts, setAccounts] = React.useState<IAccount[]>([]);
|
||||
const [selectedRowKeys,setSelectedRowKeys] = React.useState([]);
|
||||
const [selectedRows,setSelectedRows] = React.useState<IAccount[]>([]);
|
||||
const columns = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
dataIndex: 'emailaddress1'
|
||||
}
|
||||
];
|
||||
|
||||
React.useEffect(() => {
|
||||
dynamicsService.getAccessToken().then(_=>{
|
||||
dynamicsService.getAccounts().then((accs) => setAccounts(accs.map(a=>({...a,key:a.accountid}))));
|
||||
});
|
||||
}, []);
|
||||
|
||||
const searchAccounts = async (value) =>{
|
||||
const result: IAccount[] = await dynamicsService.searchAccounts(value);
|
||||
const accs = result.map(acc=> ({...acc,key:acc.accountid}));
|
||||
setAccounts(accs);
|
||||
};
|
||||
|
||||
const rowSelection = {
|
||||
selectedRowKeys,
|
||||
onChange: (_selectedRowKeys, _selectedRows) => {
|
||||
setSelectedRowKeys(_selectedRowKeys);
|
||||
setSelectedRows(_selectedRows);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.dynamic365SalesAccounts}>
|
||||
<Search placeholder="Search accounts" onSearch={value => searchAccounts(value)} enterButton />
|
||||
<Divider/>
|
||||
<Table
|
||||
columns={columns}
|
||||
expandable={{expandedRowRender: record=> <Contacts Id={record.accountid}/>}}
|
||||
dataSource={accounts}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dynamic365SalesAccounts;
|
|
@ -0,0 +1,3 @@
|
|||
export interface IDynamic365SalesAccountsProps {
|
||||
description: string;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import * as React from "react";
|
||||
import { Table } from "antd";
|
||||
import IContact from "../../../model/IContact";
|
||||
import { dynamicsService } from "../../../services/services";
|
||||
|
||||
export interface IContactsProps {
|
||||
Id: string;
|
||||
}
|
||||
|
||||
const Contacts: React.FC<IContactsProps> = (props) => {
|
||||
const [data, setData] = React.useState<IContact[]>([]);
|
||||
React.useEffect(()=>{
|
||||
dynamicsService.getContacts(props.Id).then(res => setData(res));
|
||||
},[]);
|
||||
const columns = [
|
||||
{
|
||||
title: "Full Name",
|
||||
dataIndex: "fullname",
|
||||
render: (text) => <b>{text}</b>
|
||||
},
|
||||
{
|
||||
title: "City",
|
||||
dataIndex: "address1_city"
|
||||
},
|
||||
{
|
||||
title: "Email",
|
||||
dataIndex: "emailaddress1"
|
||||
},
|
||||
{
|
||||
title: "Business Phone",
|
||||
dataIndex: "telephone1",
|
||||
render: (text)=><a>{text}</a>
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table columns={columns} dataSource={data} pagination={false} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contacts;
|
7
samples/react-dynamics-crm-api/src/webparts/dynamic365SalesAccounts/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Dynamic CRM Settings",
|
||||
"DynamicCrmDomainFieldLabel": "Dynamic CRM Domain (e.x., contoso.crm11)"
|
||||
}
|
||||
});
|
10
samples/react-dynamics-crm-api/src/webparts/dynamic365SalesAccounts/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare interface IDynamic365SalesAccountsWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DynamicCrmDomainFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'Dynamic365SalesAccountsWebPartStrings' {
|
||||
const strings: IDynamic365SalesAccountsWebPartStrings;
|
||||
export = strings;
|
||||
}
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"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-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ react-enhanced-powerapps | Hugo Bernier ([Tahoe Ninjas](http://tahoeninjas.blog/
|
|||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|July 27, 2020|Initial release
|
||||
|
||||
1.1|August 15, 2020|Added improved resize event handler
|
||||
## 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.**
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"solution": {
|
||||
"name": "react-enhanced-powerapps-client-side-solution",
|
||||
"id": "601b7688-1e9c-468c-b9a3-e4bb4e14edcd",
|
||||
"version": "1.0.0.0",
|
||||
"version": "1.1.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"isDomainIsolated": false,
|
||||
"developer": {
|
||||
|
|
|
@ -36,5 +36,10 @@ export default class EnhancedPowerAppsWebPart extends BaseClientSideWebPart<IEnh
|
|||
* @param args The new theme
|
||||
*/
|
||||
private _handleThemeChangedEvent;
|
||||
/**
|
||||
* Redraws the web part when resized
|
||||
* @param _newWidth
|
||||
*/
|
||||
protected onAfterResize(_newWidth: number): void;
|
||||
}
|
||||
//# sourceMappingURL=EnhancedPowerAppsWebPart.d.ts.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"EnhancedPowerAppsWebPart.d.ts","sourceRoot":"","sources":["../../../src/webparts/enhancedPowerApps/EnhancedPowerAppsWebPart.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EACL,0BAA0B,EAK3B,MAAM,6BAA6B,CAAC;AAMrC;;GAEG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D;;GAEG;AACH,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAG3B,MAAM,4BAA4B,CAAC;AAuBpC,MAAM,WAAW,8BAA8B;IAC7C,WAAW,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,aAAa,GAAC,aAAa,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAC,KAAK,GAAC,OAAO,GAAC,KAAK,GAAC,QAAQ,CAAC;IACjD,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,CAAC,OAAO,OAAO,wBAAyB,SAAQ,qBAAqB,CAAC,8BAA8B,CAAC;IACzG,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,aAAa,CAA6B;IAElD,SAAS,CAAC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAa1B,MAAM,IAAI,IAAI;IAqDrB,SAAS,CAAC,SAAS,IAAI,IAAI;uBAIb,WAAW,EAAI,OAAO;IAIpC,SAAS,CAAC,4BAA4B,IAAI,0BAA0B;uBAwItD,kBAAkB,EAAI,0BAA0B;IAU9D,OAAO,CAAC,YAAY,CAEnB;IAED;;;;KAIC;IACD,OAAO,CAAC,wBAAwB;CAIjC"}
|
||||
{"version":3,"file":"EnhancedPowerAppsWebPart.d.ts","sourceRoot":"","sources":["../../../src/webparts/enhancedPowerApps/EnhancedPowerAppsWebPart.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EACL,0BAA0B,EAK3B,MAAM,6BAA6B,CAAC;AAMrC;;GAEG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D;;GAEG;AACH,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAG3B,MAAM,4BAA4B,CAAC;AAuBpC,MAAM,WAAW,8BAA8B;IAC7C,WAAW,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,aAAa,GAAC,aAAa,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAC,KAAK,GAAC,OAAO,GAAC,KAAK,GAAC,QAAQ,CAAC;IACjD,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,CAAC,OAAO,OAAO,wBAAyB,SAAQ,qBAAqB,CAAC,8BAA8B,CAAC;IACzG,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,aAAa,CAA6B;IAElD,SAAS,CAAC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAa1B,MAAM,IAAI,IAAI;IA4DrB,SAAS,CAAC,SAAS,IAAI,IAAI;uBAIb,WAAW,EAAI,OAAO;IAIpC,SAAS,CAAC,4BAA4B,IAAI,0BAA0B;uBAwItD,kBAAkB,EAAI,0BAA0B;IAU9D,OAAO,CAAC,YAAY,CAEnB;IAED;;;;KAIC;IACD,OAAO,CAAC,wBAAwB;IAKhC;;;OAGG;IACH,SAAS,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAIjD"}
|
|
@ -54,9 +54,12 @@ var EnhancedPowerAppsWebPart = /** @class */ (function (_super) {
|
|||
return _super.prototype.onInit.call(this);
|
||||
};
|
||||
EnhancedPowerAppsWebPart.prototype.render = function () {
|
||||
var clientWidth = this.domElement.clientWidth;
|
||||
// Context variables and dynamic properties
|
||||
var dynamicProp = this.properties.dynamicProp.tryGetValue();
|
||||
var locale = this.context.pageContext.cultureInfo.currentCultureName;
|
||||
// Get the client width. This is how we'll calculate the aspect ratio and resize the iframe
|
||||
var clientWidth = this.domElement.clientWidth;
|
||||
// Get the aspect width and height based on aspect ratio for the web part
|
||||
var aspectWidth;
|
||||
var aspectHeight;
|
||||
switch (this.properties.aspectratio) {
|
||||
|
@ -77,9 +80,12 @@ var EnhancedPowerAppsWebPart = /** @class */ (function (_super) {
|
|||
aspectHeight = 3;
|
||||
break;
|
||||
case "Custom":
|
||||
// Custom aspects just use the width and height properties
|
||||
aspectWidth = this.properties.width;
|
||||
aspectHeight = this.properties.height;
|
||||
}
|
||||
// If we're using fixed height, we pass the height and don't resize, otherwise we
|
||||
// calculate the height based on the web part's width and selected aspect ratio
|
||||
var clientHeight = this.properties.layout === 'FixedHeight' ?
|
||||
this.properties.height :
|
||||
clientWidth * (aspectHeight / aspectWidth);
|
||||
|
@ -265,6 +271,14 @@ var EnhancedPowerAppsWebPart = /** @class */ (function (_super) {
|
|||
this._themeVariant = args.theme;
|
||||
this.render();
|
||||
};
|
||||
/**
|
||||
* Redraws the web part when resized
|
||||
* @param _newWidth
|
||||
*/
|
||||
EnhancedPowerAppsWebPart.prototype.onAfterResize = function (_newWidth) {
|
||||
// redraw the web part
|
||||
this.render();
|
||||
};
|
||||
return EnhancedPowerAppsWebPart;
|
||||
}(BaseClientSideWebPart));
|
||||
export default EnhancedPowerAppsWebPart;
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"EnhancedPowerApps.d.ts","sourceRoot":"","sources":["../../../../src/webparts/enhancedPowerApps/components/EnhancedPowerApps.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAYpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAAC,uBAAuB,EAAE,uBAAuB,CAAC;IAEvG,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,uBAAuB,CAAC;CAkE7D"}
|
||||
{"version":3,"file":"EnhancedPowerApps.d.ts","sourceRoot":"","sources":["../../../../src/webparts/enhancedPowerApps/components/EnhancedPowerApps.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAYpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,KAAK,CAAC,SAAS,CAAC,uBAAuB,EAAE,uBAAuB,CAAC;IAEvG,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,uBAAuB,CAAC;CAiE7D"}
|
|
@ -24,9 +24,11 @@ var EnhancedPowerApps = /** @class */ (function (_super) {
|
|||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
EnhancedPowerApps.prototype.render = function () {
|
||||
var _a = this.props, dynamicProp = _a.dynamicProp, themeVariant = _a.themeVariant, themeValues = _a.themeValues, appWebLink = _a.appWebLink, useDynamicProp = _a.useDynamicProp, dynamicPropName = _a.dynamicPropName, locale = _a.locale, border = _a.border, height = _a.height, width = _a.width;
|
||||
var _a = this.props, dynamicProp = _a.dynamicProp, themeVariant = _a.themeVariant, themeValues = _a.themeValues, appWebLink = _a.appWebLink, useDynamicProp = _a.useDynamicProp, dynamicPropName = _a.dynamicPropName, locale = _a.locale, border = _a.border, height = _a.height;
|
||||
// The only thing we need for this web part to be configured is an app link or app id
|
||||
var needConfiguration = !appWebLink;
|
||||
var semanticColors = themeVariant.semanticColors;
|
||||
// If we passed a dynamic property, add it as a query string parameter
|
||||
var dynamicPropValue = useDynamicProp && dynamicProp !== undefined ? "&" + encodeURIComponent(dynamicPropName) + "=" + encodeURIComponent(dynamicProp) : '';
|
||||
// We can take an app id or a full link. We'll assume (for now) that people are passing a valid app URL
|
||||
// would LOVE to find an API to retrieve list of valid apps
|
||||
|
@ -46,8 +48,7 @@ var EnhancedPowerApps = /** @class */ (function (_super) {
|
|||
}
|
||||
// Build the frame url
|
||||
var frameUrl = appUrl + "?source=SPClient-EnhancedPowerAppsWebPart&locale=" + locale + "&enableOnBehalfOf=true&authMode=onbehalfof&hideNavBar=true&" + dynamicPropValue + themeParams + "&locale=" + locale;
|
||||
console.log("URL", frameUrl);
|
||||
return (React.createElement("div", { className: styles.enhancedPowerApps, style: { height: height + "px" } },
|
||||
return (React.createElement("div", { className: styles.enhancedPowerApps, style: needConfiguration ? { height: "315px" } : { height: height + "px" } },
|
||||
needConfiguration &&
|
||||
React.createElement(Placeholder, { iconName: 'PowerApps', iconText: strings.PlaceholderIconText, description: strings.PlaceholderDescription, buttonLabel: strings.PlaceholderButtonLabel, onConfigure: this.props.onConfigure }),
|
||||
!needConfiguration &&
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"EnhancedPowerApps.js","sourceRoot":"","sources":["../../../../src/webparts/enhancedPowerApps/components/EnhancedPowerApps.tsx"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,MAAM,MAAM,iCAAiC,CAAC;AAErD,OAAO,KAAK,OAAO,MAAM,iCAAiC,CAAC;AAE3D;;KAEK;AACL,OAAO,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAQvE;IAA+C,qCAAiE;IAAhH;;IAoEA,CAAC;IAlEQ,kCAAM,GAAb;QACQ,IAAA,eAWU,EAVd,4BAAW,EACX,8BAAY,EACZ,4BAAW,EACX,0BAAU,EACV,kCAAc,EACd,oCAAe,EACf,kBAAM,EACN,kBAAM,EACN,kBAAM,EACN,gBACc,CAAC;QACjB,IAAM,iBAAiB,GAAY,CAAC,UAAU,CAAC;QAEvC,IAAA,4CAAc,CAAkC;QAExD,IAAM,gBAAgB,GAAW,cAAc,IAAI,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,MAAI,kBAAkB,CAAC,eAAe,CAAC,SAAI,kBAAkB,CAAC,WAAW,CAAG,CAAA,CAAC,CAAA,EAAE,CAAC;QAE/J,uGAAuG;QACvG,2DAA2D;QAC3D,IAAM,MAAM,GAAW,UAAU,IAAI,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,qCAAmC,UAAY,CAAC,CAAC,CAAC,UAAU,CAAC;QAExI,gEAAgE;QAChE,IAAI,WAAW,GAAW,EAAE,CAAC;QAE7B,IAAI,WAAW,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE;YAC3C,WAAW,CAAC,OAAO,CAAC,UAAC,UAAkB;gBACrC,IAAI;oBAEF,IAAM,UAAU,GAAW,cAAc,CAAC,UAAU,CAAC,CAAC;oBACtD,WAAW,GAAG,WAAW,IAAG,MAAI,UAAU,SAAI,kBAAkB,CAAC,UAAU,CAAG,CAAA,CAAC;iBAChF;gBAAC,OAAO,CAAC,EAAE;oBACV,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;iBAChB;YAEH,CAAC,CAAC,CAAC;SACF;QAGD,sBAAsB;QACtB,IAAM,QAAQ,GAAc,MAAM,6DAAwD,MAAM,mFAA8E,gBAAgB,GAAG,WAAW,gBAAW,MAAQ,CAAC;QAEhO,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAE7B,OAAO,CACL,6BAAK,SAAS,EAAG,MAAM,CAAC,iBAAiB,EAAG,KAAK,EAAE,EAAC,MAAM,EAAI,MAAM,OAAI,EAAC;YACtE,iBAAiB;gBACjB,oBAAC,WAAW,IACZ,QAAQ,EAAC,WAAW,EACpB,QAAQ,EAAE,OAAO,CAAC,mBAAmB,EACrC,WAAW,EAAE,OAAO,CAAC,sBAAsB,EAC3C,WAAW,EAAE,OAAO,CAAC,sBAAsB,EAC3C,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,GAAI;YACtC,CAAC,iBAAiB;gBACnB,0CACC,IAAI,CAAC,KAAK,CAAC,UAAU;oBACvB,gCAAQ,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAC,IAAI,EAAC,KAAK,EAAC,sDAAsD,EAAC,OAAO,EAAC,gIAAgI,EAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EACnR,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAA,CAAC,CAAC,GAAG,GACpB,CAEN,CAEA,CACP,CAAC;IACJ,CAAC;IACH,wBAAC;AAAD,CAAC,AApED,CAA+C,KAAK,CAAC,SAAS,GAoE7D"}
|
||||
{"version":3,"file":"EnhancedPowerApps.js","sourceRoot":"","sources":["../../../../src/webparts/enhancedPowerApps/components/EnhancedPowerApps.tsx"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,MAAM,MAAM,iCAAiC,CAAC;AAErD,OAAO,KAAK,OAAO,MAAM,iCAAiC,CAAC;AAE3D;;KAEK;AACL,OAAO,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAQvE;IAA+C,qCAAiE;IAAhH;;IAmEA,CAAC;IAjEQ,kCAAM,GAAb;QACQ,IAAA,eAUU,EATd,4BAAW,EACX,8BAAY,EACZ,4BAAW,EACX,0BAAU,EACV,kCAAc,EACd,oCAAe,EACf,kBAAM,EACN,kBAAM,EACN,kBACc,CAAC;QAEjB,qFAAqF;QACrF,IAAM,iBAAiB,GAAY,CAAC,UAAU,CAAC;QAEvC,IAAA,4CAAc,CAAkC;QAExD,sEAAsE;QACtE,IAAM,gBAAgB,GAAW,cAAc,IAAI,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,MAAI,kBAAkB,CAAC,eAAe,CAAC,SAAI,kBAAkB,CAAC,WAAW,CAAG,CAAA,CAAC,CAAA,EAAE,CAAC;QAE/J,uGAAuG;QACvG,2DAA2D;QAC3D,IAAM,MAAM,GAAW,UAAU,IAAI,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,qCAAmC,UAAY,CAAC,CAAC,CAAC,UAAU,CAAC;QAExI,gEAAgE;QAChE,IAAI,WAAW,GAAW,EAAE,CAAC;QAE7B,IAAI,WAAW,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE;YACzC,WAAW,CAAC,OAAO,CAAC,UAAC,UAAkB;gBACrC,IAAI;oBAEF,IAAM,UAAU,GAAW,cAAc,CAAC,UAAU,CAAC,CAAC;oBACtD,WAAW,GAAG,WAAW,IAAG,MAAI,UAAU,SAAI,kBAAkB,CAAC,UAAU,CAAG,CAAA,CAAC;iBAChF;gBAAC,OAAO,CAAC,EAAE;oBACV,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;iBAChB;YACH,CAAC,CAAC,CAAC;SACJ;QAGD,sBAAsB;QACtB,IAAM,QAAQ,GAAc,MAAM,6DAAwD,MAAM,mFAA8E,gBAAgB,GAAG,WAAW,gBAAW,MAAQ,CAAC;QAEhO,OAAO,CACL,6BAAK,SAAS,EAAG,MAAM,CAAC,iBAAiB,EAAG,KAAK,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAC,MAAM,EAAC,OAAO,EAAC,CAAA,CAAC,CAAA,EAAC,MAAM,EAAI,MAAM,OAAI,EAAC;YAC3G,iBAAiB;gBACjB,oBAAC,WAAW,IACZ,QAAQ,EAAC,WAAW,EACpB,QAAQ,EAAE,OAAO,CAAC,mBAAmB,EACrC,WAAW,EAAE,OAAO,CAAC,sBAAsB,EAC3C,WAAW,EAAE,OAAO,CAAC,sBAAsB,EAC3C,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,GAAI;YACtC,CAAC,iBAAiB;gBACnB,0CACC,IAAI,CAAC,KAAK,CAAC,UAAU;oBACvB,gCAAQ,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAC,IAAI,EAAC,KAAK,EAAC,sDAAsD,EAAC,OAAO,EAAC,gIAAgI,EAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EACnR,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAA,CAAC,CAAC,GAAG,GACpB,CAEN,CAEA,CACP,CAAC;IACJ,CAAC;IACH,wBAAC;AAAD,CAAC,AAnED,CAA+C,KAAK,CAAC,SAAS,GAmE7D"}
|
|
@ -1,2 +1 @@
|
|||
.enhancedPowerApps_e02a5fc2{color:inherit}.enhancedPowerApps_e02a5fc2 span[class^=placeholderText_]{font-size:20px!important;font-weight:600!important;color:#323130}.enhancedPowerApps_e02a5fc2 span[class^=placeholderDescriptionText_]{color:#605e5c!important;margin:20px 0 28px;line-height:23px;font-size:16px!important}
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNyY1xcd2VicGFydHNcXGVuaGFuY2VkUG93ZXJBcHBzXFxjb21wb25lbnRzXFxFbmhhbmNlZFBvd2VyQXBwcy5tb2R1bGUuc2NzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSw0QkFDRSxNQUFBLFFBREYsMERBU0ksVUFBQSxlQUNBLFlBQUEsY0FDQSxNQUFBLFFBWEoscUVBZUksTUFBQSxrQkFDQSxPQUFBLEtBQUEsRUFBQSxLQUNBLFlBQUEsS0FDQSxVQUFBIn0= */
|
||||
.enhancedPowerApps_e02a5fc2{color:inherit}.enhancedPowerApps_e02a5fc2 span[class^=placeholderText_]{font-size:20px!important;font-weight:600!important;color:#323130}.enhancedPowerApps_e02a5fc2 span[class^=placeholderDescriptionText_]{color:#605e5c!important;margin:20px 0 28px;line-height:23px;font-size:16px!important}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "react-enhanced-powerapps",
|
||||
"version": "0.0.1",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -2211,6 +2211,15 @@
|
|||
"statuses": "1"
|
||||
}
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz",
|
||||
|
@ -2343,6 +2352,44 @@
|
|||
"terser": "^3.16.1",
|
||||
"webpack-sources": "^1.1.0",
|
||||
"worker-farm": "^1.5.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cacache": {
|
||||
"version": "11.3.3",
|
||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz",
|
||||
"integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird": "^3.5.5",
|
||||
"chownr": "^1.1.1",
|
||||
"figgy-pudding": "^3.5.1",
|
||||
"glob": "^7.1.4",
|
||||
"graceful-fs": "^4.1.15",
|
||||
"lru-cache": "^5.1.1",
|
||||
"mississippi": "^3.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"move-concurrently": "^1.0.1",
|
||||
"promise-inflight": "^1.0.1",
|
||||
"rimraf": "^2.6.3",
|
||||
"ssri": "^6.0.1",
|
||||
"unique-filename": "^1.1.1",
|
||||
"y18n": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"uuid": {
|
||||
|
@ -2381,6 +2428,18 @@
|
|||
"watchpack": "^1.5.0",
|
||||
"webpack-sources": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5551,51 +5610,6 @@
|
|||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
|
||||
"dev": true
|
||||
},
|
||||
"cacache": {
|
||||
"version": "11.3.3",
|
||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz",
|
||||
"integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird": "^3.5.5",
|
||||
"chownr": "^1.1.1",
|
||||
"figgy-pudding": "^3.5.1",
|
||||
"glob": "^7.1.4",
|
||||
"graceful-fs": "^4.1.15",
|
||||
"lru-cache": "^5.1.1",
|
||||
"mississippi": "^3.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"move-concurrently": "^1.0.1",
|
||||
"promise-inflight": "^1.0.1",
|
||||
"rimraf": "^2.6.3",
|
||||
"ssri": "^6.0.1",
|
||||
"unique-filename": "^1.1.1",
|
||||
"y18n": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"cache-base": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "react-enhanced-powerapps",
|
||||
"version": "0.0.1",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
|
|
|
@ -306,4 +306,13 @@ export default class EnhancedPowerAppsWebPart extends BaseClientSideWebPart<IEnh
|
|||
this._themeVariant = args.theme;
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraws the web part when resized
|
||||
* @param _newWidth
|
||||
*/
|
||||
protected onAfterResize(_newWidth: number): void {
|
||||
// redraw the web part
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,7 @@ export default class EnhancedPowerApps extends React.Component<IEnhancedPowerApp
|
|||
dynamicPropName,
|
||||
locale,
|
||||
border,
|
||||
height,
|
||||
width
|
||||
height
|
||||
} = this.props;
|
||||
|
||||
// The only thing we need for this web part to be configured is an app link or app id
|
||||
|
@ -37,23 +36,23 @@ export default class EnhancedPowerApps extends React.Component<IEnhancedPowerApp
|
|||
|
||||
// If we passed a dynamic property, add it as a query string parameter
|
||||
const dynamicPropValue: string = useDynamicProp && dynamicProp !== undefined ? `&${encodeURIComponent(dynamicPropName)}=${encodeURIComponent(dynamicProp)}`:'';
|
||||
|
||||
|
||||
// We can take an app id or a full link. We'll assume (for now) that people are passing a valid app URL
|
||||
// would LOVE to find an API to retrieve list of valid apps
|
||||
const appUrl: string = appWebLink && appWebLink.indexOf('https://') != 0 ? `https://apps.powerapps.com/play/${appWebLink}` : appWebLink;
|
||||
|
||||
|
||||
// Build the portion of the URL where we're passing theme colors
|
||||
let themeParams: string = "";
|
||||
|
||||
if (themeValues && themeValues.length > 0) {
|
||||
themeValues.forEach((themeValue: string) => {
|
||||
try {
|
||||
|
||||
|
||||
const themeColor: string = semanticColors[themeValue];
|
||||
themeParams = themeParams + `&${themeValue}=${encodeURIComponent(themeColor)}`;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -61,10 +60,8 @@ export default class EnhancedPowerApps extends React.Component<IEnhancedPowerApp
|
|||
// Build the frame url
|
||||
const frameUrl: string = `${appUrl}?source=SPClient-EnhancedPowerAppsWebPart&locale=${locale}&enableOnBehalfOf=true&authMode=onbehalfof&hideNavBar=true&${dynamicPropValue}${themeParams}&locale=${locale}`;
|
||||
|
||||
console.log("URL", frameUrl);
|
||||
|
||||
return (
|
||||
<div className={ styles.enhancedPowerApps } style={{height:`${height}px`}}>
|
||||
<div className={ styles.enhancedPowerApps } style={needConfiguration ? {height:`315px`}:{height:`${height}px`}}>
|
||||
{needConfiguration &&
|
||||
<Placeholder
|
||||
iconName='PowerApps'
|
||||
|
|
|
@ -686,7 +686,7 @@ var MANIFESTS_ARRAY = [
|
|||
"id": "3d2f89fa-3b9e-4876-82c2-c15ff82190f1",
|
||||
"alias": "EnhancedPowerAppsWebPart",
|
||||
"componentType": "WebPart",
|
||||
"version": "0.0.1",
|
||||
"version": "1.1.0",
|
||||
"manifestVersion": 2,
|
||||
"requiresCustomScript": false,
|
||||
"supportsFullBleed": true,
|
||||
|
@ -725,11 +725,57 @@ var MANIFESTS_ARRAY = [
|
|||
},
|
||||
"EnhancedPowerAppsWebPartStrings": {
|
||||
"defaultPath": "lib/webparts/enhancedPowerApps/loc/en-us.js",
|
||||
"type": "localizedPath"
|
||||
"type": "localizedPath",
|
||||
"paths": {}
|
||||
},
|
||||
"PropertyControlStrings": {
|
||||
"defaultPath": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"type": "localizedPath"
|
||||
"type": "localizedPath",
|
||||
"paths": {
|
||||
"en-US": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"bn": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"chr": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"dv": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"div": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"en": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"fil": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"haw": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"iu": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"lo": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"moh": "node_modules/@pnp/spfx-property-controls/lib/loc/en-us.js",
|
||||
"fr-FR": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"gsw": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"br": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"tzm-Tfng": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"co": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"fr": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"ff": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"lb": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"mg": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"oc": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"zgh": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"wo": "node_modules/@pnp/spfx-property-controls/lib/loc/fr-fr.js",
|
||||
"nl-NL": "node_modules/@pnp/spfx-property-controls/lib/loc/nl-nl.js",
|
||||
"nl": "node_modules/@pnp/spfx-property-controls/lib/loc/nl-nl.js",
|
||||
"fy": "node_modules/@pnp/spfx-property-controls/lib/loc/nl-nl.js",
|
||||
"no": "node_modules/@pnp/spfx-property-controls/lib/loc/no.js",
|
||||
"ru-RU": "node_modules/@pnp/spfx-property-controls/lib/loc/ru-ru.js",
|
||||
"ru": "node_modules/@pnp/spfx-property-controls/lib/loc/ru-ru.js",
|
||||
"ba": "node_modules/@pnp/spfx-property-controls/lib/loc/ru-ru.js",
|
||||
"be": "node_modules/@pnp/spfx-property-controls/lib/loc/ru-ru.js",
|
||||
"ky": "node_modules/@pnp/spfx-property-controls/lib/loc/ru-ru.js",
|
||||
"mn": "node_modules/@pnp/spfx-property-controls/lib/loc/ru-ru.js",
|
||||
"sah": "node_modules/@pnp/spfx-property-controls/lib/loc/ru-ru.js",
|
||||
"tg": "node_modules/@pnp/spfx-property-controls/lib/loc/ru-ru.js",
|
||||
"tt": "node_modules/@pnp/spfx-property-controls/lib/loc/ru-ru.js",
|
||||
"tk": "node_modules/@pnp/spfx-property-controls/lib/loc/ru-ru.js",
|
||||
"zh-CN": "node_modules/@pnp/spfx-property-controls/lib/loc/zh-cn.js",
|
||||
"zh": "node_modules/@pnp/spfx-property-controls/lib/loc/zh-cn.js",
|
||||
"mn-Mong": "node_modules/@pnp/spfx-property-controls/lib/loc/zh-cn.js",
|
||||
"bo": "node_modules/@pnp/spfx-property-controls/lib/loc/zh-cn.js",
|
||||
"ug": "node_modules/@pnp/spfx-property-controls/lib/loc/zh-cn.js",
|
||||
"ii": "node_modules/@pnp/spfx-property-controls/lib/loc/zh-cn.js"
|
||||
}
|
||||
},
|
||||
"@microsoft/sp-property-pane": {
|
||||
"type": "component",
|
||||
|
|
|
@ -4715,9 +4715,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz",
|
||||
"integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=",
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
|
|
|
@ -6856,9 +6856,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
|
||||
"integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==",
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
|
|
|
@ -1961,8 +1961,8 @@ block-stream@*:
|
|||
inherits "~2.0.0"
|
||||
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
|
||||
version "4.11.8"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
|
||||
version "4.11.9"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
|
||||
|
||||
body-parser@1.18.3:
|
||||
version "1.18.3"
|
||||
|
@ -3150,8 +3150,8 @@ electron-to-chromium@^1.3.390:
|
|||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.393.tgz#d13fa4cbf5065e18451c84465d22aef6aca9a911"
|
||||
|
||||
elliptic@^6.0.0:
|
||||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
|
||||
version "6.5.3"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
|
||||
dependencies:
|
||||
bn.js "^4.4.0"
|
||||
brorand "^1.0.1"
|
||||
|
@ -4421,11 +4421,11 @@ hash-base@^3.0.0:
|
|||
safe-buffer "^5.0.1"
|
||||
|
||||
hash.js@^1.0.0, hash.js@^1.0.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846"
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-assert "^1.0.1"
|
||||
|
||||
hawk@~6.0.2:
|
||||
version "6.0.2"
|
||||
|
@ -4651,17 +4651,17 @@ inherits@1:
|
|||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b"
|
||||
|
||||
inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
|
||||
inherits@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
|
||||
|
||||
inherits@2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
inherits@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
|
||||
ini@^1.3.4, ini@~1.3.0:
|
||||
version "1.3.5"
|
||||
|
@ -6568,7 +6568,7 @@ mimic-fn@^1.0.0:
|
|||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
|
||||
|
||||
minimalistic-assert@^1.0.0:
|
||||
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
|
||||
|
|
|
@ -61,11 +61,13 @@ Built with SharePoint Framework GA, Office Graph, React and Chart.JS
|
|||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-modern-charts|Jeremy Coleman (MCP, PC Professional, Inc.)
|
||||
react-modern-charts|Peter Paul Kirschner ([@petkir_at](https://twitter.com/petkir_at))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0.0.3|July 30, 2020| Support for Managed Metadata Field(Single) as Label
|
||||
1.0.0.2|February 09, 2020| Upgrade to SPFx 1.10.0
|
||||
1.0.0.1|April 25, 2018|Update to SPFx 1.4.1
|
||||
1.0.0.0|February 11, 2017|Initial release
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"solution": {
|
||||
"name": "modern-charts-client-side-solution",
|
||||
"id": "f8a78a9a-a93e-4843-89e5-7b871d9b9fa2",
|
||||
"version": "1.0.0.2",
|
||||
"version": "1.0.0.3",
|
||||
"isDomainIsolated": false,
|
||||
"includeClientSideAssets": true
|
||||
},
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
|
||||
// Display errors as warnings
|
||||
"displayAsWarning": true,
|
||||
// The TSLint task may have been configured with several custom lint rules
|
||||
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
|
||||
// project). If true, this flag will deactivate any of these rules.
|
||||
"removeExistingRules": true,
|
||||
// When true, the TSLint task is configured with some default TSLint "rules.":
|
||||
"useDefaultConfigAsBase": false,
|
||||
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
|
||||
// which are active, other than the list of rules below.
|
||||
"lintConfig": {
|
||||
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
|
||||
"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-case": true,
|
||||
"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,
|
||||
"valid-typeof": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ export interface ChartConfiguration {
|
|||
theme: string;
|
||||
bgColors: Array<string>;
|
||||
hoverColors: Array<string>;
|
||||
hasTaxField?: boolean;
|
||||
}
|
||||
|
||||
export interface IModernChartsWebPartProps {
|
||||
|
@ -50,4 +51,4 @@ export interface MChart {
|
|||
labels: Array<string>;
|
||||
config: ChartConfiguration;
|
||||
key: number;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,11 @@ export interface ISPList {
|
|||
Id: string;
|
||||
}
|
||||
|
||||
export interface IFieldProperty extends IPropertyPaneDropdownOption {
|
||||
fieldtype: string;
|
||||
}
|
||||
|
||||
|
||||
export default class ModernChartsWebPart extends BaseClientSideWebPart<IModernChartsWebPartProps> {
|
||||
|
||||
private reactCharts: React.ReactElement<IModernChartsProps>;
|
||||
|
@ -205,12 +210,29 @@ export default class ModernChartsWebPart extends BaseClientSideWebPart<IModernCh
|
|||
data.forEach((item) => {
|
||||
if (chLabels['unique'].indexOf(item[config.unique]) == -1 && item[config.unique] != null && item[config.unique] != "") {
|
||||
chLabels['unique'].push(item[config.unique]);
|
||||
chLabels['labels'].push(item[config.col1]);
|
||||
//if term use VAlue
|
||||
chLabels['labels'].push(this.getLabel(item, config.col1));
|
||||
}
|
||||
});
|
||||
return chLabels;
|
||||
}
|
||||
|
||||
private getLabel(item: Object, col: string) {
|
||||
if (!!item[col] && !!item[col]['WssId'] && !!item["TaxCatchAll"] && item["TaxCatchAll"].length > 0) {
|
||||
//Filter because you can have more Fields from this type to select the right value
|
||||
const wssid: number = item[col]['WssId'];
|
||||
const terms = (item["TaxCatchAll"]).filter((x) => x.ID === wssid);
|
||||
if (!!terms && terms.length > 0) {
|
||||
return terms[0].Term;
|
||||
}
|
||||
return 'TermLabel not Found';
|
||||
}
|
||||
//TODO HyperLink
|
||||
//lookup user
|
||||
|
||||
return item[col];
|
||||
}
|
||||
|
||||
private getValues(data: Array<Object>, unique: Array<string>, config: ChartConfiguration): Array<Array<any>> {
|
||||
|
||||
const values: Object = {};
|
||||
|
@ -270,6 +292,21 @@ export default class ModernChartsWebPart extends BaseClientSideWebPart<IModernCh
|
|||
this.properties.chartConfig[pPathInd].bgColors = newTheme['bgColors'];
|
||||
this.properties.chartConfig[pPathInd].hoverColors = newTheme['hoverColors'];
|
||||
}
|
||||
if (pPath === 'col1' && (newValue != oldValue)) {
|
||||
this.properties.chartConfig[pPathInd].hasTaxField = false;
|
||||
if (!!newValue
|
||||
&& !!this.properties.chartConfig[pPathInd].columns
|
||||
&& this.properties.chartConfig[pPathInd].columns.length > 0
|
||||
) {
|
||||
const selects = this.properties.chartConfig[pPathInd].columns.filter(f => f.key === newValue);
|
||||
if (selects.length > 0) {
|
||||
const selected = selects[0];
|
||||
|
||||
this.properties.chartConfig[pPathInd].hasTaxField = selected.fieldtype === 'TaxonomyFieldType';
|
||||
}
|
||||
}
|
||||
}
|
||||
//col1
|
||||
this.context.propertyPane.refresh();
|
||||
this.render();
|
||||
}
|
||||
|
@ -457,7 +494,14 @@ export default class ModernChartsWebPart extends BaseClientSideWebPart<IModernCh
|
|||
}
|
||||
|
||||
public getData(chartConfig: Object) {
|
||||
return this.context.spHttpClient.get(chartConfig['dataurl'] + `/_api/web/lists/GetByTitle(\'${chartConfig['list']}\')/items?$orderby=Id desc&$limit=10&$top=${this.properties.maxResults}`, SPHttpClient.configurations.v1)
|
||||
const urlparttax = '&$select=*,TaxCatchAll/Term,TaxCatchAll/ID&$expand=TaxCatchAll';
|
||||
const resturl = `/_api/web/lists/GetByTitle(\'${chartConfig['list']}\')/items?$orderby=Id desc&$limit=10&$top=${this.properties.maxResults}`;
|
||||
let requesturl = chartConfig['dataurl'] + resturl;
|
||||
|
||||
if (!!chartConfig['hasTaxField']) {
|
||||
requesturl = requesturl + urlparttax;
|
||||
}
|
||||
return this.context.spHttpClient.get(requesturl, SPHttpClient.configurations.v1)
|
||||
.then((response: SPHttpClientResponse) => {
|
||||
return response.json();
|
||||
});
|
||||
|
@ -480,10 +524,14 @@ export default class ModernChartsWebPart extends BaseClientSideWebPart<IModernCh
|
|||
|
||||
private _updateListColumns(siteUrl: string, listName: string, _chartConfig: ChartConfiguration): void {
|
||||
this._getListColumns(listName, siteUrl).then((response) => {
|
||||
var respLists: IPropertyPaneDropdownOption[] = [];
|
||||
var respLists: IFieldProperty[] = [];
|
||||
console.log(response.value);
|
||||
for (var _key in response.value) {
|
||||
respLists.push({ key: response.value[_key]['InternalName'], text: response.value[_key]['Title'] });
|
||||
respLists.push({
|
||||
key: response.value[_key]['InternalName'],
|
||||
text: response.value[_key]['Title'],
|
||||
fieldtype: response.value[_key]['TypeAsString']
|
||||
});
|
||||
}
|
||||
this._columnOptions = respLists;
|
||||
_chartConfig.columns = respLists;
|
||||
|
|
|
@ -52,19 +52,19 @@ export default class ModernCharts extends React.Component<IModernChartsProps, {}
|
|||
tChart = <Doughnut data={data} options={options} />;
|
||||
return tChart;
|
||||
case 'line':
|
||||
tChart = <Line data={data} options={options} />;
|
||||
return tChart;
|
||||
debugger;
|
||||
return <Line data={data} options={options} legend={{ display: false }} />;
|
||||
case 'pie':
|
||||
tChart = <Pie data={data} options={options} />;
|
||||
return tChart;
|
||||
case 'bar':
|
||||
tChart = <Bar data={data} options={options} />;
|
||||
tChart = <Bar data={data} options={options} legend={{ display: false }} />;
|
||||
return tChart;
|
||||
case 'horizontalbar':
|
||||
tChart = <HorizontalBar data={data} options={options} />;
|
||||
tChart = <HorizontalBar data={data} options={options} legend={{ display: false }} />;
|
||||
return tChart;
|
||||
case 'radar':
|
||||
tChart = <Radar data={data} options={options} />;
|
||||
tChart = <Radar data={data} options={options} legend={{ display: false }} />;
|
||||
return tChart;
|
||||
case 'polar':
|
||||
tChart = <Polar data={data} options={options} />;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.11.0",
|
||||
"libraryName": "spfx-msgraph-peoplesearch",
|
||||
"libraryId": "98a8d9d1-47c4-477c-addd-ecae95b235cc",
|
||||
"environment": "spo",
|
||||
"packageManager": "npm",
|
||||
"framework": "react",
|
||||
"isCreatingSolution": true,
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
# Microsoft Graph People Search Web Part
|
||||
|
||||
## Summary
|
||||
|
||||
Show and search users from your organization, through Microsoft Graph. Search results show as a nice People Card, and display the Live Persona Card on hover.
|
||||
The web part accepts a search query through a Dynamic Data connection, to further filter the displayed results. A source for this search query is not provided, but by default this can come from the Microsoft Search search box or the Page Environment. You could also use the Search Box Web Part provided by the [PnP Modern Search Web Parts](https://microsoft-search.github.io/pnp-modern-search/).
|
||||
|
||||
![directory](./assets/MicrosoftGraphPeopleSearch.gif)
|
||||
![directory](./assets/MicrosoftGraphPeopleSearch-LPC.gif)
|
||||
|
||||
## Future improvements
|
||||
- Support loading Profile Pictures
|
||||
- Support for multiple pages
|
||||
- Improve $select field with predefined properties of the User object
|
||||
- Improve field mapping with the selected properties defined in $select
|
||||
- Toggle Live Person Card
|
||||
|
||||
## Accompanying blog post
|
||||
I wrote a blog post covering more if the inner workings, you can find it at [SPFx People Search web part based on Microsoft Graph](https://blog.yannickreekmans.be/spfx-people-search-web-part-based-on-microsoft-graph/)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![1.11.0](https://img.shields.io/badge/version-1.11-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Online](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
|
||||
* [Microsoft Teams](https://products.office.com/en-US/microsoft-teams/group-chat-software) - Untested!!
|
||||
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-msgraph-peoplesearch | Yannick Reekmans ([YannickReekmans](https://twitter.com/YannickReekmans))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
2.0.0|July 30, 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
|
||||
|
||||
- Clone this repository
|
||||
- in the command line run:
|
||||
- `npm install`
|
||||
- `gulp build`
|
||||
- `gulp bundle --ship`
|
||||
- `gulp package-solution --ship`
|
||||
- Add to AppCatalog and deploy
|
||||
- Assign `User.Read.All` delegated permissions to the **SharePoint Online Client Extensibility Web Application Principal**, easiest way is with [Office 365 CLI](https://pnp.github.io/office365-cli/):
|
||||
```
|
||||
o365 login
|
||||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'User.Read.All'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgements / Inspiration
|
||||
|
||||
There are many web parts that aim to do the same thing, but they either use SharePoint Search as data store or they render their results in a completely different way. It's impossible to acknowledge all sources of inspiration to this solution, but I do want to give a shout out to two projects (and their contributors) that were foundational to deliver this solution as quickly as I did:
|
||||
|
||||
### React Directory Web Part
|
||||
The foundation on which I started building my own solution. This web part can be downloaded from the [SharePoint Framework Client-Side Web Part Samples & Tutorial Materials](https://github.com/pnp/sp-dev-fx-webparts/tree/master/samples/react-directory)
|
||||
|
||||
#### Thanks to
|
||||
- João Mendes ([@joaojmendes](https://twitter.com/joaojmendes))
|
||||
- Peter Paul Kirschner ([@petkir_at](https://twitter.com/petkir_at))
|
||||
|
||||
### PnP Modern Search Web Parts
|
||||
These web parts were an enormous inspiration on code structure and implementation approach. Their codebase is very impressive, and a lot of the code in this web part is a literal copy paste from them. You can find more on the [PnP Modern Search Web Parts](https://microsoft-search.github.io/pnp-modern-search/) page.
|
||||
|
||||
#### Thanks to
|
||||
- Franck Cornu (aequos) - [@FranckCornu](http://www.twitter.com/FranckCornu) - [GitHub Sponsor Page](https://github.com/sponsors/FranckyC)
|
||||
- Mikael Svenson (Microsoft) - [@mikaelsvenson](http://www.twitter.com/mikaelsvenson)
|
||||
- Yannick Reekmans - [@yannickreekmans](https://twitter.com/yannickreekmans)
|
||||
- Albert-Jan Schot - [@appieschot](https://twitter.com/appieschot)
|
||||
- Tarald Gåsbakk (PuzzlePart) - [@taraldgasbakk](https://twitter.com/Taraldgasbakk)
|
||||
- Brad Schlintz (Microsoft) - [@bschlintz](https://twitter.com/bschlintz)
|
||||
- Richard Gigan - [@PooLP](https://twitter.com/PooLP)
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-msgraph-peoplesearch" />
|
After Width: | Height: | Size: 596 KiB |
After Width: | Height: | Size: 24 MiB |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"peoplesearch-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/peoplesearch/PeopleSearchWebPart.js",
|
||||
"manifest": "./src/webparts/peoplesearch/PeopleSearchWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"PeopleSearchWebPartStrings": "lib/webparts/peoplesearch/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
|
||||
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "spfx-msgraph-peoplesearch",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{"preset":"@voitanos/jest-preset-spfx-react16","rootDir":"../src","coverageReporters":["text","json","lcov","text-summary","cobertura"],"reporters":["default","jest-junit"]}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"developer": {
|
||||
"name": "Contoso",
|
||||
"privacyUrl": "https://contoso.com/privacy",
|
||||
"termsOfUseUrl": "https://contoso.com/terms-of-use",
|
||||
"websiteUrl": "https://contoso.com/my-app",
|
||||
"mpnId": "000000"
|
||||
},
|
||||
"name": "Microsoft Graph People Search",
|
||||
"id": "98a8d9d1-47c4-477c-addd-ecae95b235cc",
|
||||
"version": "2.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/spfx-msgraph-peoplesearch.sppkg"
|
||||
}
|
||||
}
|