Merge pull request #1439 from pnp/hb-photo-editor-2

Hb photo editor 2
This commit is contained in:
Hugo Bernier 2020-08-12 23:27:57 -04:00 committed by GitHub
commit 40930889f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 119 additions and 104 deletions

View File

@ -1,6 +1,6 @@
{
"name": "smart-profile-photo-editor",
"version": "0.0.1",
"version": "1.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -2487,11 +2487,6 @@
"isomorphic-fetch": "^2.2.1"
}
},
"@microsoft/microsoft-graph-types": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-1.7.0.tgz",
"integrity": "sha512-Mxu5H+69F8T5NzV4+U8FkTvpIYYWHsmRZzfAuOlIO0zJJGlVyRIVqpq4NmOdUXGC00vZ73ONgCuzuaksxqDm/Q=="
},
"@microsoft/node-core-library": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/@microsoft/node-core-library/-/node-core-library-3.13.0.tgz",
@ -4264,22 +4259,6 @@
}
}
},
"@pnp/graph": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/@pnp/graph/-/graph-1.3.8.tgz",
"integrity": "sha512-uBBDrpWNILGBfTxtHqFrdv3YkJZm+jgXOLBp3KR8ocBrTt+yQmkCEwztawYqUhSys6SoDkcFE/DqLIjhyHiBDA==",
"requires": {
"@microsoft/microsoft-graph-types": "1.7.0",
"tslib": "1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
}
}
},
"@pnp/logging": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/@pnp/logging/-/logging-1.3.8.tgz",
@ -4310,21 +4289,6 @@
}
}
},
"@pnp/sp": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/@pnp/sp/-/sp-1.3.8.tgz",
"integrity": "sha512-x85cQL/L5fBYJWqWvDL3e2sHdYZIqUeWifFndsGRk6iLFHTq2DsxNNzTnCpP7JCKUwK6jHpu0JzIuK98E8Hl9w==",
"requires": {
"tslib": "1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
}
}
},
"@pnp/sp-clientsvc": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@pnp/sp-clientsvc/-/sp-clientsvc-1.3.11.tgz",
@ -4487,6 +4451,21 @@
"react-ace": "5.8.0"
},
"dependencies": {
"@pnp/sp": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@pnp/sp/-/sp-1.3.11.tgz",
"integrity": "sha512-NjdeGe81aukiSPelSPjgAFRC1+SrNPTXvTdEqTH+Q1ZvgNtk8bdZp6K6xf9emfeM2qZDOu9GpKZpg0W/emq++g==",
"requires": {
"tslib": "1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
}
}
},
"@uifabric/icons": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-5.8.0.tgz",

View File

@ -20,11 +20,6 @@
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
"@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.11.0",
"@pnp/common": "^1.3.8",
"@pnp/graph": "^1.3.8",
"@pnp/logging": "^1.3.8",
"@pnp/odata": "^1.3.8",
"@pnp/sp": "^1.3.8",
"@pnp/spfx-controls-react": "^1.19.0",
"@pnp/spfx-property-controls": "^1.20.0-beta.1472053",
"cropperjs": "^1.5.6",

View File

@ -23,9 +23,11 @@ export class AnalysisService implements IAnalysisService {
new ApiKeyCredentials({ inHeader: { 'Ocp-Apim-Subscription-Key': this.key } }), this.endpoint);
var analysis: AnalyzeImageInStreamResponse = (await computerVisionClient.analyzeImageInStream(buf, {
details: ["Celebrities"],
visualFeatures: ["Categories",
"Adult",
"Tags",
"Tags",
"Description",
"Faces",
"Color",

View File

@ -3,41 +3,41 @@ import * as React from 'react';
import { IAnalysisDialogContentProps } from './IAnalysisDialogContentProps';
import { IAnalysisDialogContentState } from './IAnalysisDialogContentState';
import styles from './AnalysisDialogContent.module.scss';
import { css } from "@uifabric/utilities/lib/css";
// Used for localized text
import * as strings from 'ProfilePhotoEditorWebPartStrings';
import { Text } from '@microsoft/sp-core-library';
// Used to determine if we should be making real calls to APIs or just mock calls
import { Environment, EnvironmentType } from '@microsoft/sp-core-library';
// Stuff we use for the dialog
import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import {
MessageBar,
MessageBarType
} from 'office-ui-fabric-react';
import { Image, ImageFit } from 'office-ui-fabric-react/lib/Image';
import { Panel } from 'office-ui-fabric-react/lib/Panel';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { Shimmer } from 'office-ui-fabric-react/lib/Shimmer';
import { ProgressIndicator } from 'office-ui-fabric-react/lib/ProgressIndicator';
import styles from './AnalysisDialogContent.module.scss';
// Used for localized text
import * as strings from 'ProfilePhotoEditorWebPartStrings';
// Used to determine if we should be making real calls to APIs or just mock calls
import { Environment, EnvironmentType } from '@microsoft/sp-core-library';
import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { css } from "@uifabric/utilities/lib/css";
import {
MessageBar,
MessageBarType
} from 'office-ui-fabric-react';
import { Image, ImageFit } from 'office-ui-fabric-react/lib/Image';
// Stuff we use for analysis results
import { IAnalysisService, AnalysisService, MockAnalysisService } from '../../../../services/AnalysisServices';
import { AnalyzeImageInStreamResponse, ImageTag } from '@azure/cognitiveservices-computervision/esm/models';
import AnalysisChecklist from '../AnalysisChecklist/AnalysisChecklist';
import { sp } from "@pnp/sp";
import { MSGraphClient, SPHttpClient } from '@microsoft/sp-http';
// This is used if you use the graph client to update pictures
import { MSGraphClient } from '@microsoft/sp-http';
export class AnalysisDialogContent extends
React.Component<IAnalysisDialogContentProps, IAnalysisDialogContentState> {
/**
*
*/
constructor(props: IAnalysisDialogContentProps) {
super(props);
this.state = {
@ -70,11 +70,23 @@ export class AnalysisDialogContent extends
const analysis: AnalyzeImageInStreamResponse = await service.AnalyzeImage(this.props.imageUrl);
// Evaluate analysis against requirements
// Is this a portrait?
const isPortrait: boolean = analysis && analysis.categories && analysis.categories.filter(c => c.name === "people_portrait").length > 0;
// If the portrait valid?
const isPortraitValid: boolean = photoRequirements.requirePortrait ? isPortrait : true;
// is there only one person in the photo?
const onlyOnePersonValid: boolean = analysis.faces.length === 1;
// Is this a clipart?
const isClipartValid: boolean = photoRequirements.allowClipart ? true : analysis.imageType.clipArtType === 0;
// Is this a line drawing?
const isLinedrawingValid: boolean = photoRequirements.allowLinedrawing ? true : analysis.imageType.lineDrawingType === 0;
// Are we looking at naughty pictures?
const isAdultValid: boolean = photoRequirements.allowAdult ? true : !analysis.adult.isAdultContent;
const isRacyValid: boolean = photoRequirements.allowRacy ? true : !analysis.adult.isRacyContent;
const isGoryValid: boolean = photoRequirements.allowGory ? true : !analysis.adult.isGoryContent;
@ -91,9 +103,16 @@ export class AnalysisDialogContent extends
});
}
// Did we find forbidden keywords
const keywordsValid: boolean = invalidKeywords.length < 1;
console.log("Invalid keywords", invalidKeywords);
// Look for celebrities
let celebName: string = undefined;
const categories = analysis && analysis.categories && analysis.categories.filter(c => c.detail !== undefined && c.detail.celebrities !== undefined);
if (categories && categories.length > 0) {
// Get the first celebrity
celebName = categories[0] && categories[0].detail && categories[0].detail.celebrities[0] && categories[0].detail.celebrities[0].name;
}
// Photo is valid if it meets all requirements
const isValid: boolean = isPortraitValid
@ -105,6 +124,7 @@ export class AnalysisDialogContent extends
&& isGoryValid
&& keywordsValid;
// Set the state so we can refresh the status
this.setState({
isAnalyzing: false,
analysis,
@ -118,7 +138,8 @@ export class AnalysisDialogContent extends
isRacyValid,
isGoryValid,
keywordsValid,
invalidKeywords
invalidKeywords,
celebrity: celebName
});
}
@ -138,7 +159,8 @@ export class AnalysisDialogContent extends
isLinedrawingValid,
onlyOnePersonValid,
invalidKeywords,
keywordsValid } = this.state;
keywordsValid,
celebrity } = this.state;
if (analysis !== undefined) {
console.log("Analysis", analysis);
@ -187,6 +209,11 @@ export class AnalysisDialogContent extends
<div className={styles.iconContainer} ><Icon iconName={isValid ? "CheckMark" : "StatusCircleErrorX"} className={css(styles.icon, isValid ? styles.iconGood : styles.iconBad)} /></div>
}
{!isAnalyzing && celebrity !== undefined &&
<div><p>{Text.format(strings.YouLookLikeACelebrity, celebrity)}</p></div>
}
{!isAnalyzing && isValid &&
<div>{strings.AnalysisGoodLabel}</div>
}
@ -240,21 +267,35 @@ export class AnalysisDialogContent extends
}
private onUpdateProfilePhoto = async (_ev?: React.SyntheticEvent<HTMLElement, Event>) => {
console.log("Submitting photo");
// Get image array buffer
const profileBlob: Blob = this.props.blob;
// Get image array buffer
this.updateProfilePic(profileBlob);
// Submit using the approach you want
this.updateProfilePicUsingGraph(profileBlob);
//this.updateProfilePicUsingPnP(profileBlob);
}
private async updateProfilePic(buffer) {
console.log("Update profile pic", buffer);
// private async updateProfilePicUsingPnP(blob: Blob) {
// pnpSetup({
// spfxContext: this.props.context
// });
// console.log("Update profile pic using PnP", blob);
// const response = await sp.profiles.setMyProfilePic(blob);
// console.log("Profile property Updated", response);
// this.setState({
// isSubmitted: true
// });
// }
// Update photo using Graph.
// See https://docs.microsoft.com/en-us/graph/api/profilephoto-update?view=graph-rest-1.0&tabs=http
private async updateProfilePicUsingGraph(blob: Blob) {
this.props.context.msGraphClientFactory
.getClient().then((client: MSGraphClient) => {
client
.api("me/photo/$value")
.version("v1.0").header("Content-Type", buffer.type).put(buffer, (error, res) => {
.version("v1.0").header("Content-Type", blob.type).put(blob, (error, _res) => {
if (error) {
console.log("Error updating profile", error);
} else {
@ -270,15 +311,4 @@ export class AnalysisDialogContent extends
private onDismiss = (_ev?: React.SyntheticEvent<HTMLElement, Event>) => {
this.props.onDismiss();
}
// private dataURLtoBlob = (dataurl: string): Blob => {
// var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
// bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
// while (n--) {
// u8arr[n] = bstr.charCodeAt(n);
// }
// return new Blob([u8arr], { type: mime });
// }
}

View File

@ -16,4 +16,5 @@ export interface IAnalysisDialogContentState {
keywordsValid?: boolean;
invalidKeywords?: string[];
isSubmitted: boolean;
celebrity?: string;
}

View File

@ -289,8 +289,6 @@ export default class ProfilePhotoEditor extends React.Component<IProfilePhotoEdi
// Get the image to approve
const imageToApprove: string = this.cropper.getCroppedCanvas().toDataURL();
this.cropper.getCroppedCanvas().toBlob((blob: Blob)=> {
console.log("Blob", blob);
const photoRequirements: IPhotoRequirements = {
allowAdult: this.props.allowAdult,
allowClipart: this.props.allowClipart,

View File

@ -10,6 +10,9 @@ import styles from './WebCamDialog.module.scss';
import Webcam from "react-webcam";
/**
* Set the video constraints to be a square from the user-facing camera
*/
const videoConstraints = {
width: 300,
height: 300,
@ -41,8 +44,6 @@ export class WebCamDialog extends React.Component<IWebCamDialogProps, IWebCamDia
screenshotFormat="image/jpeg"
videoConstraints={videoConstraints}
imageSmoothing={true}
onUserMedia={() => console.log("OnUserMedia")}
onUserMediaError={() => console.log("OnUserMediaError")}
screenshotQuality={0.92}
/>
@ -54,12 +55,19 @@ export class WebCamDialog extends React.Component<IWebCamDialogProps, IWebCamDia
);
}
/**
* Captures an image from the web cam
*/
private onCapture = () => {
const imageSrc = this.webcamRef.getScreenshot();
console.log("ImageSrc", imageSrc);
this.props.onCapture(imageSrc);
}
/**
*
* Dismisses the dialog
* @param _ev
*/
private onDismiss = (_ev?: React.SyntheticEvent<HTMLElement, Event>) => {
this.props.onDismiss();
}

View File

@ -1,5 +1,6 @@
define([], function() {
return {
YouLookLikeACelebrity: "Uh... did anyone ever tell you that you look like {0}? It's uncanny!",
CaptureButtonLabel: "Capture",
WebCamDialogTitle: "Insert photo from camera",
NoKeywords: "(none)",

View File

@ -1,4 +1,5 @@
declare interface IProfilePhotoEditorWebPartStrings {
YouLookLikeACelebrity: string;
CaptureButtonLabel: string;
WebCamDialogTitle: string;
NoKeywords: string;