diff --git a/samples/react-smart-profile-photo-editor/package-lock.json b/samples/react-smart-profile-photo-editor/package-lock.json index 4af55994a..0e7418a0c 100644 --- a/samples/react-smart-profile-photo-editor/package-lock.json +++ b/samples/react-smart-profile-photo-editor/package-lock.json @@ -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", diff --git a/samples/react-smart-profile-photo-editor/package.json b/samples/react-smart-profile-photo-editor/package.json index 44724bfc0..4d7ec3eac 100644 --- a/samples/react-smart-profile-photo-editor/package.json +++ b/samples/react-smart-profile-photo-editor/package.json @@ -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", diff --git a/samples/react-smart-profile-photo-editor/src/services/AnalysisServices/AnalysisService.ts b/samples/react-smart-profile-photo-editor/src/services/AnalysisServices/AnalysisService.ts index 770c6c9d9..f208f5977 100644 --- a/samples/react-smart-profile-photo-editor/src/services/AnalysisServices/AnalysisService.ts +++ b/samples/react-smart-profile-photo-editor/src/services/AnalysisServices/AnalysisService.ts @@ -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", diff --git a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/AnalysisDialog/AnalysisDialogContent.tsx b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/AnalysisDialog/AnalysisDialogContent.tsx index f4d67e8f2..5c0f86749 100644 --- a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/AnalysisDialog/AnalysisDialogContent.tsx +++ b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/AnalysisDialog/AnalysisDialogContent.tsx @@ -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 { - /** - * - */ 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
} + {!isAnalyzing && celebrity !== undefined && +

{Text.format(strings.YouLookLikeACelebrity, celebrity)}

+ } + + {!isAnalyzing && isValid &&
{strings.AnalysisGoodLabel}
} @@ -215,12 +242,12 @@ export class AnalysisDialogContent extends } {this.state.isSubmitted && - - {strings.SuccessMessage} - + + {strings.SuccessMessage} + } @@ -240,45 +267,48 @@ export class AnalysisDialogContent extends } private onUpdateProfilePhoto = async (_ev?: React.SyntheticEvent) => { - 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 + // }); - 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) => { - if (error) { + // 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", blob.type).put(blob, (error, _res) => { + if (error) { console.log("Error updating profile", error); - } else { + } else { console.log("Profile property Updated"); this.setState({ isSubmitted: true }); - } - }); - }); + } + }); + }); } private onDismiss = (_ev?: React.SyntheticEvent) => { 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 }); - // } - - } diff --git a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/AnalysisDialog/IAnalysisDialogContentState.ts b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/AnalysisDialog/IAnalysisDialogContentState.ts index 5a1012d23..050e4b006 100644 --- a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/AnalysisDialog/IAnalysisDialogContentState.ts +++ b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/AnalysisDialog/IAnalysisDialogContentState.ts @@ -16,4 +16,5 @@ export interface IAnalysisDialogContentState { keywordsValid?: boolean; invalidKeywords?: string[]; isSubmitted: boolean; -} \ No newline at end of file + celebrity?: string; +} diff --git a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/ProfilePhotoEditor.tsx b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/ProfilePhotoEditor.tsx index 1fc817623..2ea35b61c 100644 --- a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/ProfilePhotoEditor.tsx +++ b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/ProfilePhotoEditor.tsx @@ -289,8 +289,6 @@ export default class ProfilePhotoEditor extends React.Component { - console.log("Blob", blob); - const photoRequirements: IPhotoRequirements = { allowAdult: this.props.allowAdult, allowClipart: this.props.allowClipart, diff --git a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/WebCamDialog/WebCamDialog.tsx b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/WebCamDialog/WebCamDialog.tsx index 0eac0455e..4b228e9fe 100644 --- a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/WebCamDialog/WebCamDialog.tsx +++ b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/components/WebCamDialog/WebCamDialog.tsx @@ -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 console.log("OnUserMedia")} - onUserMediaError={() => console.log("OnUserMediaError")} screenshotQuality={0.92} /> @@ -54,12 +55,19 @@ export class WebCamDialog extends React.Component { const imageSrc = this.webcamRef.getScreenshot(); - console.log("ImageSrc", imageSrc); this.props.onCapture(imageSrc); } + /** + * + * Dismisses the dialog + * @param _ev + */ private onDismiss = (_ev?: React.SyntheticEvent) => { this.props.onDismiss(); } diff --git a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/loc/en-us.js b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/loc/en-us.js index 6350a7007..0f7e79944 100644 --- a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/loc/en-us.js +++ b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/loc/en-us.js @@ -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)", diff --git a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/loc/mystrings.d.ts b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/loc/mystrings.d.ts index 5f457aec8..e9a029aa0 100644 --- a/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/loc/mystrings.d.ts +++ b/samples/react-smart-profile-photo-editor/src/webparts/profilePhotoEditor/loc/mystrings.d.ts @@ -1,4 +1,5 @@ declare interface IProfilePhotoEditorWebPartStrings { + YouLookLikeACelebrity: string; CaptureButtonLabel: string; WebCamDialogTitle: string; NoKeywords: string;