Adding react-avatar sample
This commit is contained in:
parent
ff9a6d1c0e
commit
e86b4ce420
|
@ -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](/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`
|
||||
|
||||
|
||||
|
||||
|
||||
|
Binary file not shown.
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'));
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue