commit
40930889f8
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
@ -215,12 +242,12 @@ export class AnalysisDialogContent extends
|
|||
}
|
||||
|
||||
{this.state.isSubmitted &&
|
||||
<MessageBar
|
||||
messageBarType={MessageBarType.success}
|
||||
isMultiline={false}
|
||||
>
|
||||
{strings.SuccessMessage}
|
||||
</MessageBar>
|
||||
<MessageBar
|
||||
messageBarType={MessageBarType.success}
|
||||
isMultiline={false}
|
||||
>
|
||||
{strings.SuccessMessage}
|
||||
</MessageBar>
|
||||
}
|
||||
|
||||
</Panel>
|
||||
|
@ -240,45 +267,48 @@ 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
|
||||
// });
|
||||
|
||||
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<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 });
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -16,4 +16,5 @@ export interface IAnalysisDialogContentState {
|
|||
keywordsValid?: boolean;
|
||||
invalidKeywords?: string[];
|
||||
isSubmitted: boolean;
|
||||
}
|
||||
celebrity?: string;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
declare interface IProfilePhotoEditorWebPartStrings {
|
||||
YouLookLikeACelebrity: string;
|
||||
CaptureButtonLabel: string;
|
||||
WebCamDialogTitle: string;
|
||||
NoKeywords: string;
|
||||
|
|
Loading…
Reference in New Issue