Open extensions added to store metadata to saved mail
This commit is contained in:
parent
e4e1a044b1
commit
6fd6866903
|
@ -1,4 +1,4 @@
|
|||
## outlook-2-teams-spfx
|
||||
## outlook-2-sp-spfx
|
||||
|
||||
|
||||
## Summary
|
||||
|
@ -8,7 +8,7 @@ Furthermore it shows you how to retrieve a complete mail as a mimestream via Mic
|
|||
* Writing normal files smaller 4MB
|
||||
* Writing big files with an UploadSession when bigger than 4MB
|
||||
|
||||
## outlook-2-teams-spfx in action
|
||||
## outlook-2-sp-spfx in action
|
||||
![WebPartInAction](https://mmsharepoint.files.wordpress.com/2020/01/addin_overall.png)
|
||||
|
||||
A detailed functionality and technical description can be found in the [author's blog series](https://mmsharepoint.wordpress.com/2020/01/11/an-outlook-add-in-with-sharepoint-framework-spfx-introduction/)
|
||||
|
@ -25,13 +25,14 @@ A detailed functionality and technical description can be found in the [author's
|
|||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
outlook-2-teams-spfx| Markus Moeller ([@moeller2_0](http://www.twitter.com/moeller2_0))
|
||||
outlook-2-sp-spfx| Markus Moeller ([@moeller2_0](http://www.twitter.com/moeller2_0))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|February 05, 2020|Initial release
|
||||
1.0|January 29, 2020|Initial release
|
||||
1.1|April 06, 2020|Open extensions to store metadata added
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
@ -68,5 +69,4 @@ This Outlook Add-In shows the following capabilities on top of the SharePoint Fr
|
|||
* Use Microsoft Graph to retrieve folders and subfolders for OneDrive or Teams/Group drives
|
||||
* Use Microsoft Graph to retrieve complete mail mimestream by given ID
|
||||
* Use Microsoft Graph to save normal or big files (in size bigger 4MB) with different concepts
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-outlook-copy2teams" />
|
||||
* Optionally store metadata of save operation to copied mail with open extension (configure line 15 Outlook2SharePoint.tsx)
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Mail.Read"
|
||||
"scope": "Mail.ReadWrite"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
|
|
|
@ -2,19 +2,27 @@ import { MSGraphClient, MSGraphClientFactory } from '@microsoft/sp-http';
|
|||
import Utilities from './Utilities';
|
||||
import { IFolder } from '../model/IFolder';
|
||||
import { IMail } from '../model/IMail';
|
||||
import { IMailMetadata } from '../model/IMailMetadata';
|
||||
|
||||
export default class GraphController {
|
||||
private client: MSGraphClient;
|
||||
private metadataExtensionName = 'mmsharepoint.onmicrosoft.MailStorage';
|
||||
private saveMetadata: boolean;
|
||||
|
||||
constructor (graphFactory: MSGraphClientFactory, callback: () => void) {
|
||||
graphFactory
|
||||
.getClient()
|
||||
.then((client: MSGraphClient) => {
|
||||
this.client = client;
|
||||
callback();
|
||||
});
|
||||
constructor (saveMetadata: boolean) {
|
||||
this.saveMetadata = saveMetadata;
|
||||
}
|
||||
|
||||
this.retrieveMimeMail = this.retrieveMimeMail.bind(this);
|
||||
public init(graphFactory: MSGraphClientFactory): Promise<boolean> {
|
||||
return graphFactory
|
||||
.getClient()
|
||||
.then((client: MSGraphClient) => {
|
||||
this.client = client;
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public getClient() {
|
||||
|
@ -29,11 +37,12 @@ export default class GraphController {
|
|||
.api('me/drive/root/children')
|
||||
.version('v1.0')
|
||||
.filter('folder ne null')
|
||||
.select('id, name, parentReference, webUrl')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
response.value.forEach((item) => {
|
||||
folders.push({ id: item.id, name: item.name, driveID: item.parentReference.driveId, parentFolder: null});
|
||||
folders.push({ id: item.id, name: item.name, driveID: item.parentReference.driveId, parentFolder: null, webUrl: item.webUrl });
|
||||
});
|
||||
return folders;
|
||||
});
|
||||
|
@ -44,11 +53,12 @@ export default class GraphController {
|
|||
.api(`drives/${group.driveID}/root/children`)
|
||||
.version('v1.0')
|
||||
.filter('folder ne null')
|
||||
.select('id, name, webUrl')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
response.value.forEach((item) => {
|
||||
folders.push({ id: item.id, name: item.name, driveID: group.driveID, parentFolder: group});
|
||||
folders.push({ id: item.id, name: item.name, driveID: group.driveID, parentFolder: group, webUrl: item.webUrl});
|
||||
});
|
||||
return folders;
|
||||
});
|
||||
|
@ -59,11 +69,12 @@ export default class GraphController {
|
|||
.api(`drives/${folder.driveID}/items/${folder.id}/children`)
|
||||
.version('v1.0')
|
||||
.filter('folder ne null')
|
||||
.select('id, name, webUrl')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
response.value.forEach((item) => {
|
||||
folders.push({ id: item.id, name: item.name, driveID: folder.driveID, parentFolder: folder});
|
||||
folders.push({ id: item.id, name: item.name, driveID: folder.driveID, parentFolder: folder, webUrl: item.webUrl});
|
||||
});
|
||||
return folders;
|
||||
});
|
||||
|
@ -76,6 +87,7 @@ export default class GraphController {
|
|||
return this.client
|
||||
.api('me/memberOf')
|
||||
.version('v1.0')
|
||||
.select('id, displayName, webUrl')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
|
@ -83,7 +95,7 @@ export default class GraphController {
|
|||
// Show unified Groups but NO Teams
|
||||
if (item['@odata.type'] === '#microsoft.graph.group') {
|
||||
if(!item.resourceProvisioningOptions || item.resourceProvisioningOptions.indexOf('Team') === -1) {
|
||||
folders.push({ id: item.id, name: item.displayName, driveID: item.id, parentFolder: null});
|
||||
folders.push({ id: item.id, name: item.displayName, driveID: item.id, parentFolder: null, webUrl: item.webUrl});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -98,11 +110,12 @@ export default class GraphController {
|
|||
return this.client
|
||||
.api('me/joinedTeams')
|
||||
.version('v1.0')
|
||||
.select('id, displayName, webUrl')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
response.value.forEach((item) => {
|
||||
folders.push({ id: item.id, name: item.displayName, driveID: item.id, parentFolder: null});
|
||||
folders.push({ id: item.id, name: item.displayName, driveID: item.id, parentFolder: null, webUrl: item.webUrl});
|
||||
});
|
||||
return folders;
|
||||
});
|
||||
|
@ -115,42 +128,46 @@ export default class GraphController {
|
|||
return this.client
|
||||
.api(`groups/${group.id}/drives`)
|
||||
.version('v1.0')
|
||||
.select('id, name, webUrl')
|
||||
.get()
|
||||
.then((response): any => {
|
||||
let folders: Array<IFolder> = new Array<IFolder>();
|
||||
response.value.forEach((item) => {
|
||||
folders.push({ id: item.id, name: item.name, driveID: item.id, parentFolder: group});
|
||||
folders.push({ id: item.id, name: item.name, driveID: item.id, parentFolder: group, webUrl: item.webUrl});
|
||||
});
|
||||
return folders;
|
||||
});
|
||||
}
|
||||
|
||||
public retrieveMimeMail(driveID: string, folderID: string, mail: IMail, clientCallback: (msg: string)=>void): Promise<string> {
|
||||
public retrieveMimeMail = (driveID: string, folderID: string, mail: IMail, clientCallback: (msg: string)=>void): Promise<string> => {
|
||||
return this.client
|
||||
.api(`me/messages/${mail.id}/$value`)
|
||||
.version('v1.0')
|
||||
.responseType('TEXT')
|
||||
.get((err: any, response, rawResponse): any => {
|
||||
.get()
|
||||
.then((response): any => {
|
||||
if (response.length < (4 * 1024 * 1024)) // If Mail size bigger 4MB use resumable upload
|
||||
{
|
||||
this.saveNormalMail(driveID, folderID, response, Utilities.createMailFileName(mail.subject), clientCallback);
|
||||
return this.saveNormalMail(driveID, folderID, response, Utilities.createMailFileName(mail.subject), clientCallback);
|
||||
}
|
||||
else {
|
||||
this.saveBigMail(driveID, folderID, response, Utilities.createMailFileName(mail.subject), clientCallback);
|
||||
return this.saveBigMail(driveID, folderID, response, Utilities.createMailFileName(mail.subject), clientCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private saveNormalMail(driveID: string, folderID: string, mimeStream: string, fileName: string, clientCallback: (msg: string)=>void) {
|
||||
private saveNormalMail(driveID: string, folderID: string, mimeStream: string, fileName: string, clientCallback: (msg: string)=>void): Promise<string> {
|
||||
const apiUrl = driveID !== folderID ? `drives/${driveID}/items/${folderID}:/${fileName}.eml:/content` : `drives/${driveID}/root:/${fileName}.eml:/content`;
|
||||
this.client
|
||||
return this.client
|
||||
.api(apiUrl)
|
||||
.put(mimeStream)
|
||||
.then((response) => {
|
||||
clientCallback('Success');
|
||||
return 'Success';
|
||||
})
|
||||
.catch((error) => {
|
||||
clientCallback('Error');
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -161,32 +178,30 @@ export default class GraphController {
|
|||
}
|
||||
};
|
||||
const apiUrl = driveID !== folderID ? `drives/${driveID}/items/${folderID}:/${fileName}.eml:/createUploadSession` : `drives/${driveID}/root:/${fileName}.eml:/createUploadSession`;
|
||||
this.client
|
||||
return this.client
|
||||
.api(apiUrl)
|
||||
.post(JSON.stringify(sessionOptions))
|
||||
.then(async (response):Promise<any> => {
|
||||
console.log(response.uploadUrl);
|
||||
console.log(response.expirationDateTime);
|
||||
try {
|
||||
const resp = await this.uploadMailSlices(mimeStream, response.uploadUrl);
|
||||
console.log(resp);
|
||||
clientCallback('Success');
|
||||
return 'Success';
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
clientCallback('Error');
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async uploadMailSlices(mimeStream: string, uploadUrl: string) {
|
||||
let minSize=0;
|
||||
let maxSize=327680; // 320kb slices
|
||||
let maxSize=5*327680; // 5*320kb slices --> MUST be a multiple of 320 KiB (327,680 bytes)
|
||||
while(mimeStream.length > minSize) {
|
||||
const fileSlice = mimeStream.slice(minSize, maxSize);
|
||||
const resp = await this.uploadMailSlice(uploadUrl, minSize, maxSize, mimeStream.length, fileSlice);
|
||||
minSize = maxSize;
|
||||
maxSize += 327680;
|
||||
maxSize += 5*327680;
|
||||
if (maxSize > mimeStream.length) {
|
||||
maxSize = mimeStream.length;
|
||||
}
|
||||
|
@ -210,12 +225,53 @@ export default class GraphController {
|
|||
.put(fileSlice);
|
||||
}
|
||||
|
||||
private saveMailCallback(error: any, response: any, rawResponse?: any): void {
|
||||
if (error !== null) {
|
||||
console.log(error);
|
||||
}
|
||||
else {
|
||||
console.log(response);
|
||||
public saveMailMetadata(mailId: string, displayName: string, url: string, savedDate: Date) {
|
||||
if (this.saveMetadata) {
|
||||
const apiUrl = `/me/messages/${mailId}/extensions`;
|
||||
const metadataBody = {
|
||||
"@odata.type" : "microsoft.graph.openTypeExtension",
|
||||
"extensionName" : this.metadataExtensionName,
|
||||
"saveDisplayName" : displayName,
|
||||
"saveUrl" : url,
|
||||
"savedDate" : savedDate.toISOString()
|
||||
};
|
||||
this.client
|
||||
.api(apiUrl)
|
||||
.version('v1.0')
|
||||
.post(JSON.stringify(metadataBody))
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public retrieveMailMetadata(mailId: string): Promise<any> {
|
||||
const apiUrl = `/me/messages/${mailId}`;
|
||||
const expand = `Extensions($filter=id eq 'Microsoft.OutlookServices.OpenTypeExtension.${this.metadataExtensionName}')`;
|
||||
return this.client
|
||||
.api(apiUrl)
|
||||
.version('v1.0')
|
||||
.expand(expand)
|
||||
.select('id,subject,extensions')
|
||||
.get()
|
||||
.then((response) => {
|
||||
if (typeof response.extensions !== 'undefined' && response.extensions !== null) {
|
||||
const metadata: IMailMetadata = {
|
||||
extensionName: response.extensions[0].extensionName,
|
||||
saveDisplayName: response.extensions[0].saveDisplayName,
|
||||
saveUrl: response.extensions[0].saveUrl,
|
||||
savedDate: new Date(response.extensions[0].savedDate)
|
||||
};
|
||||
return metadata;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.log(error);
|
||||
return null;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,4 +4,5 @@ export interface IFolder {
|
|||
id: string;
|
||||
driveID: string;
|
||||
parentFolder: IFolder;
|
||||
webUrl: string;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export interface IMailMetadata {
|
||||
extensionName: string;
|
||||
saveDisplayName: string;
|
||||
saveUrl: string;
|
||||
savedDate: Date;
|
||||
}
|
|
@ -3,7 +3,7 @@ import * as ReactDom from 'react-dom';
|
|||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
PropertyPaneToggle
|
||||
} from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
|
||||
|
@ -13,7 +13,7 @@ import Outlook2SharePoint from './components/Outlook2SharePoint';
|
|||
import { IOutlook2SharePointProps } from './components/IOutlook2SharePointProps';
|
||||
|
||||
export interface IOutlook2SharePointWebPartProps {
|
||||
description: string;
|
||||
|
||||
}
|
||||
|
||||
export default class Outlook2SharePointWebPart extends BaseClientSideWebPart <IOutlook2SharePointWebPartProps> {
|
||||
|
@ -21,8 +21,9 @@ export default class Outlook2SharePointWebPart extends BaseClientSideWebPart <IO
|
|||
let mail: IMail = null;
|
||||
if (this.context.sdks.office) {
|
||||
const item = this.context.sdks.office.context.mailbox.item;
|
||||
const itemId = this.context.sdks.office.context.mailbox.convertToRestId(item.itemId, 'v2.0');
|
||||
if (item !== null) {
|
||||
mail = { id: item.itemId,subject: item.subject };
|
||||
mail = { id: itemId, subject: item.subject };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,8 +57,8 @@ export default class Outlook2SharePointWebPart extends BaseClientSideWebPart <IO
|
|||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('description', {
|
||||
label: strings.DescriptionFieldLabel
|
||||
PropertyPaneToggle('saveMetadata', {
|
||||
label: strings.SaveMetadataFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
|||
import Breadcrumb from './controls/Breadcrumb';
|
||||
import Folder from './Folder';
|
||||
import styles from './Groups.module.scss';
|
||||
import * as strings from 'Outlook2SharePointWebPartStrings';
|
||||
import { IGroupsProps } from './IGroupsProps';
|
||||
import { IGroupsState } from './IGroupsState';
|
||||
import { IFolder } from '../../../model/IFolder';
|
||||
|
@ -16,6 +17,7 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
|
|||
folders: [],
|
||||
grandParentFolder: null,
|
||||
parentFolder: null,
|
||||
selectedGroupName: '',
|
||||
showSpinner: false
|
||||
};
|
||||
}
|
||||
|
@ -50,7 +52,7 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
|
|||
<div>
|
||||
<PrimaryButton
|
||||
className={styles.saveBtn}
|
||||
text="Save here"
|
||||
text={strings.SaveLabel}
|
||||
onClick={this.saveMailTo}
|
||||
disabled={this.state.parentFolder === null}
|
||||
allowDisabledFocus={true}
|
||||
|
@ -58,7 +60,7 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
|
|||
{ this.state.showSpinner && (
|
||||
<div className={styles.spinnerContainer}>
|
||||
<Overlay >
|
||||
<Spinner size={ SpinnerSize.large } label='Processing request' />
|
||||
<Spinner size={ SpinnerSize.large } label={strings.SpinnerLabel} />
|
||||
</Overlay>
|
||||
</div>
|
||||
) }
|
||||
|
@ -78,19 +80,14 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
|
|||
}
|
||||
|
||||
private getGroupDrives = (group: IFolder) => {
|
||||
let nextParent: IFolder = null;
|
||||
this.state.folders.forEach((fldr) => {
|
||||
if (fldr.id === group.id) {
|
||||
nextParent = fldr;
|
||||
}
|
||||
});
|
||||
this.props.graphController.getGroupDrives(group).then((folders) => {
|
||||
if (folders.length > 0) {
|
||||
this.setState((prevState: IGroupsState, props: IGroupsProps) => {
|
||||
return {
|
||||
folders: folders,
|
||||
grandParentFolder: null,
|
||||
parentFolder: group
|
||||
parentFolder: group,
|
||||
selectedGroupName: group.name
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -122,7 +119,6 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private showRoot = () => {
|
||||
this.getGroups();
|
||||
}
|
||||
|
@ -142,7 +138,11 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
|
|||
showSpinner: true
|
||||
};
|
||||
});
|
||||
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback);
|
||||
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback)
|
||||
.then((response: string) => {
|
||||
const saveLocationDisplayName = `${this.state.selectedGroupName} ...> ${this.state.grandParentFolder.name} > ${this.state.parentFolder.name}`;
|
||||
this.props.graphController.saveMailMetadata(this.props.mail.id, saveLocationDisplayName, this.state.parentFolder.webUrl, new Date());
|
||||
});
|
||||
}
|
||||
|
||||
private saveMailCallback = (message: string) => {
|
||||
|
@ -152,10 +152,10 @@ export default class Groups extends React.Component<IGroupsProps, IGroupsState>
|
|||
};
|
||||
});
|
||||
if (message.indexOf('Success') > -1) {
|
||||
this.props.successCallback(message);
|
||||
this.props.successCallback(strings.SuccessMessage);
|
||||
}
|
||||
else {
|
||||
this.props.errorCallback(message);
|
||||
this.props.errorCallback(strings.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,5 +4,6 @@ export interface IGroupsState {
|
|||
folders: IFolder[];
|
||||
grandParentFolder: IFolder;
|
||||
parentFolder: IFolder;
|
||||
selectedGroupName: string;
|
||||
showSpinner: boolean;
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
import GraphController from '../../../controller/GraphController';
|
||||
import { IMailMetadata } from '../../../model/IMailMetadata';
|
||||
|
||||
export interface IOutlook2SharePointState {
|
||||
graphController: GraphController;
|
||||
mailMetadata: IMailMetadata;
|
||||
showSuccess: boolean;
|
||||
showError: boolean;
|
||||
showOneDrive: boolean;
|
||||
|
|
|
@ -4,5 +4,6 @@ export interface ITeamsState {
|
|||
folders: IFolder[];
|
||||
grandParentFolder: IFolder;
|
||||
parentFolder: IFolder;
|
||||
selectedTeamName: string;
|
||||
showSpinner: boolean;
|
||||
}
|
|
@ -4,6 +4,7 @@ import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
|
|||
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||
import Folder from './Folder';
|
||||
import styles from './Groups.module.scss';
|
||||
import * as strings from 'Outlook2SharePointWebPartStrings';
|
||||
import Breadcrumb from './controls/Breadcrumb';
|
||||
import { IOneDriveProps } from './IOneDriveProps';
|
||||
import { IOneDriveState } from './IOneDriveState';
|
||||
|
@ -49,14 +50,14 @@ export default class OneDrive extends React.Component<IOneDriveProps, IOneDriveS
|
|||
<div>
|
||||
<PrimaryButton
|
||||
className={styles.saveBtn}
|
||||
text="Save here"
|
||||
text={strings.SaveLabel}
|
||||
onClick={this.saveMailTo}
|
||||
allowDisabledFocus={true}
|
||||
/>
|
||||
{ this.state.showSpinner && (
|
||||
<div className={styles.spinnerContainer}>
|
||||
<Overlay >
|
||||
<Spinner size={ SpinnerSize.large } label='Processing request' />
|
||||
<Spinner size={ SpinnerSize.large } label={strings.SpinnerLabel} />
|
||||
</Overlay>
|
||||
</div>
|
||||
) }
|
||||
|
@ -103,7 +104,11 @@ export default class OneDrive extends React.Component<IOneDriveProps, IOneDriveS
|
|||
showSpinner: true
|
||||
};
|
||||
});
|
||||
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback);
|
||||
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback)
|
||||
.then((response: string) => {
|
||||
const saveLocationDisplayName = `OneDrive ...> ${this.state.grandParentFolder.name} > ${this.state.parentFolder.name}`;
|
||||
this.props.graphController.saveMailMetadata(this.props.mail.id, saveLocationDisplayName, this.state.parentFolder.webUrl, new Date());
|
||||
});
|
||||
}
|
||||
|
||||
private saveMailCallback = (message: string) => {
|
||||
|
@ -113,10 +118,10 @@ export default class OneDrive extends React.Component<IOneDriveProps, IOneDriveS
|
|||
};
|
||||
});
|
||||
if (message.indexOf('Success') > -1) {
|
||||
this.props.successCallback(message);
|
||||
this.props.successCallback(strings.SuccessMessage);
|
||||
}
|
||||
else {
|
||||
this.props.errorCallback(message);
|
||||
this.props.errorCallback(strings.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,4 +14,19 @@
|
|||
@include ms-fontSize-l;
|
||||
margin-left: 3px;
|
||||
}
|
||||
.metadata {
|
||||
margin-left: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.subMetadata {
|
||||
margin-left: 6px;
|
||||
a {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
a:hover {
|
||||
@include ms-fontColor-themePrimary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import styles from './Outlook2SharePoint.module.scss';
|
||||
import * as strings from 'Outlook2SharePointWebPartStrings';
|
||||
import { Icon } from 'office-ui-fabric-react/lib/Icon';
|
||||
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
|
||||
import GraphController from '../../../controller/GraphController';
|
||||
|
@ -11,11 +12,13 @@ import { IOutlook2SharePointState } from './IOutlook2SharePointState';
|
|||
|
||||
export default class Outlook2SharePoint extends React.Component<IOutlook2SharePointProps, IOutlook2SharePointState> {
|
||||
private graphController: GraphController;
|
||||
private saveMetadata = true; // For simplicity reasons and as I am not convinced with the current "Property handling" of Office Add-In we configure 'hard-coded'
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
graphController: null,
|
||||
mailMetadata: null,
|
||||
showError: false,
|
||||
showSuccess: false,
|
||||
showOneDrive: false,
|
||||
|
@ -24,13 +27,25 @@ export default class Outlook2SharePoint extends React.Component<IOutlook2SharePo
|
|||
successMessage: '',
|
||||
errorMessage: ''
|
||||
};
|
||||
this.graphController = new GraphController(this.props.msGraphClientFactory, this.graphClientReadyCallback);
|
||||
this.graphController = new GraphController(this.saveMetadata);
|
||||
this.graphController.init(this.props.msGraphClientFactory)
|
||||
.then((controllerReady) => {
|
||||
if (controllerReady) {
|
||||
this.graphClientReady();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IOutlook2SharePointProps> {
|
||||
|
||||
return (
|
||||
<div className={ styles.outlook2SharePoint }>
|
||||
{this.state.mailMetadata !== null &&
|
||||
<div className={styles.metadata}>
|
||||
<div><Icon iconName="InfoSolid" /> {strings.SaveInfo}</div>
|
||||
<div className={styles.subMetadata}>{strings.To} <a href={this.state.mailMetadata.saveUrl}>{this.state.mailMetadata.saveDisplayName}</a></div>
|
||||
<div className={styles.subMetadata}>{strings.On} <span>{this.state.mailMetadata.savedDate.toLocaleDateString()}</span></div>
|
||||
</div>}
|
||||
{this.state.showSuccess && <div>
|
||||
<MessageBar
|
||||
messageBarType={MessageBarType.success}
|
||||
|
@ -97,12 +112,15 @@ export default class Outlook2SharePoint extends React.Component<IOutlook2SharePo
|
|||
/**
|
||||
* This function first retrieves all OneDrive root folders from user
|
||||
*/
|
||||
private graphClientReadyCallback = () => {
|
||||
private graphClientReady = () => {
|
||||
this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => {
|
||||
return {
|
||||
graphController: this.graphController
|
||||
};
|
||||
});
|
||||
if (this.saveMetadata) {
|
||||
this.getMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
private showError = (message: string) => {
|
||||
|
@ -172,4 +190,17 @@ export default class Outlook2SharePoint extends React.Component<IOutlook2SharePo
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getMetadata() {
|
||||
this.state.graphController.retrieveMailMetadata(this.props.mail.id)
|
||||
.then((response) => {
|
||||
if (response !== null) {
|
||||
this.setState((prevState: IOutlook2SharePointState, props: IOutlook2SharePointProps) => {
|
||||
return {
|
||||
mailMetadata: response
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
|||
import Breadcrumb from './controls/Breadcrumb';
|
||||
import Folder from './Folder';
|
||||
import styles from './Groups.module.scss';
|
||||
import * as strings from 'Outlook2SharePointWebPartStrings';
|
||||
import { ITeamsProps } from './ITeamsProps';
|
||||
import { ITeamsState } from './ITeamsState';
|
||||
import { IFolder } from '../../../model/IFolder';
|
||||
|
@ -16,6 +17,7 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
|
|||
folders: [],
|
||||
grandParentFolder: null,
|
||||
parentFolder: null,
|
||||
selectedTeamName: '',
|
||||
showSpinner: false
|
||||
};
|
||||
}
|
||||
|
@ -50,7 +52,7 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
|
|||
<div>
|
||||
<PrimaryButton
|
||||
className={styles.saveBtn}
|
||||
text="Save here"
|
||||
text={strings.SaveLabel}
|
||||
onClick={this.saveMailTo}
|
||||
disabled={this.state.parentFolder === null}
|
||||
allowDisabledFocus={true}
|
||||
|
@ -58,7 +60,7 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
|
|||
{ this.state.showSpinner && (
|
||||
<div className={styles.spinnerContainer}>
|
||||
<Overlay >
|
||||
<Spinner size={ SpinnerSize.large } label='Processing request' />
|
||||
<Spinner size={ SpinnerSize.large } label={strings.SpinnerLabel} />
|
||||
</Overlay>
|
||||
</div>
|
||||
) }
|
||||
|
@ -78,19 +80,14 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
|
|||
}
|
||||
|
||||
private getGroupDrives = (group: IFolder) => {
|
||||
let nextParent: IFolder = null;
|
||||
this.state.folders.forEach((fldr) => {
|
||||
if (fldr.id === group.id) {
|
||||
nextParent = fldr;
|
||||
}
|
||||
});
|
||||
this.props.graphController.getGroupDrives(group).then((folders) => {
|
||||
if (folders.length > 0) {
|
||||
this.setState((prevState: ITeamsState, props: ITeamsProps) => {
|
||||
return {
|
||||
folders: folders,
|
||||
grandParentFolder: null,
|
||||
parentFolder: group
|
||||
parentFolder: group,
|
||||
selectedTeamName: group.name
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -122,7 +119,6 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private showRoot = () => {
|
||||
this.getTeams();
|
||||
}
|
||||
|
@ -142,7 +138,11 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
|
|||
showSpinner: true
|
||||
};
|
||||
});
|
||||
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback);
|
||||
this.props.graphController.retrieveMimeMail(this.state.parentFolder.driveID, this.state.parentFolder.id, this.props.mail, this.saveMailCallback)
|
||||
.then((response: string) => {
|
||||
const saveLocationDisplayName = `${this.state.selectedTeamName} ...> ${this.state.grandParentFolder.name} > ${this.state.parentFolder.name}`;
|
||||
this.props.graphController.saveMailMetadata(this.props.mail.id, saveLocationDisplayName, this.state.parentFolder.webUrl, new Date());
|
||||
});
|
||||
}
|
||||
|
||||
private saveMailCallback = (message: string) => {
|
||||
|
@ -152,10 +152,10 @@ export default class Teams extends React.Component<ITeamsProps, ITeamsState> {
|
|||
};
|
||||
});
|
||||
if (message.indexOf('Success') > -1) {
|
||||
this.props.successCallback(message);
|
||||
this.props.successCallback(strings.SuccessMessage);
|
||||
}
|
||||
else {
|
||||
this.props.errorCallback(message);
|
||||
this.props.errorCallback(strings.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,14 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
"PropertyPaneDescription": "Copy to OneDrive/Teams",
|
||||
"BasicGroupName": "Configuration",
|
||||
"SaveMetadataFieldLabel": "Save Metadata on copied mail",
|
||||
"SaveInfo": "You already saved this mail",
|
||||
"To": "to",
|
||||
"On": "on",
|
||||
"SaveLabel": "Save here",
|
||||
"SpinnerLabel": "Processing request",
|
||||
"SuccessMessage": "Success",
|
||||
"ErrorMessage": "Error occured"
|
||||
}
|
||||
});
|
|
@ -1,7 +1,14 @@
|
|||
declare interface IOutlook2SharePointWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
SaveMetadataFieldLabel: string;
|
||||
SaveInfo: string;
|
||||
To: string;
|
||||
On: string;
|
||||
SaveLabel: string;
|
||||
SpinnerLabel: string;
|
||||
SuccessMessage: string;
|
||||
ErrorMessage: string;
|
||||
}
|
||||
|
||||
declare module 'Outlook2SharePointWebPartStrings' {
|
||||
|
|
Loading…
Reference in New Issue