Updated orgchart to use office-ui-fabric-react and uifabric/styling (#269)

* initial checkin

* basic code ready

* use office-ui-fabric-react and @uifabric/styling

* formatting

* package-lock

* updated readme

* removed wip code

* fix react children rendering

* deleted lock file added incorrectly.
This commit is contained in:
Vardhaman Deshpande 2017-07-21 18:51:40 +01:00 committed by Vesa Juvonen
parent afce98b92b
commit b2b9c6ea76
8 changed files with 12543 additions and 150 deletions

View File

@ -27,6 +27,7 @@ Version|Date|Comments
-------|----|-------- -------|----|--------
1.0|September 14, 2016|Initial release 1.0|September 14, 2016|Initial release
2.0|March 12, 2017|Updated for SPFx 1.0 2.0|March 12, 2017|Updated for SPFx 1.0
2.1|July 19, 2017|Use office-ui-fabric-react and uifabric/styling
## Disclaimer ## 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.** **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.**

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "react-organisationchart", "name": "react-organisationchart",
"version": "0.0.1", "version": "2.1.0",
"private": true, "private": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -15,6 +15,7 @@
"@types/react-addons-update": "0.14.14", "@types/react-addons-update": "0.14.14",
"@types/react-dom": "0.14.18", "@types/react-dom": "0.14.18",
"@types/webpack-env": ">=1.12.1 <1.14.0", "@types/webpack-env": ">=1.12.1 <1.14.0",
"office-ui-fabric-react": "^4.21.1",
"react": "15.4.2", "react": "15.4.2",
"react-dom": "15.4.2" "react-dom": "15.4.2"
}, },

View File

@ -1,3 +1,3 @@
export interface IOrganisationChartWebPartProps { export interface IOrganisationChartWebPartProps {
organisationName: string; organisationName: string;
} }

View File

@ -1,3 +0,0 @@
@import "~office-ui-fabric/dist/sass/Fabric.scss";
@import "~office-ui-fabric/dist/components/Persona/Persona.scss";
@import "~office-ui-fabric/dist/components/OrgChart/OrgChart.scss";

View File

@ -1,9 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import styles from './OrganisationChart.module.scss';
import { escape } from '@microsoft/sp-lodash-subset'; import { escape } from '@microsoft/sp-lodash-subset';
import { IOrganisationChartProps } from './IOrganisationChartProps'; import { Persona, PersonaSize, PersonaPresence } from 'office-ui-fabric-react/lib/Persona';
import { FontClassNames } from '@uifabric/styling';
import { IOrganisationChartProps } from './IOrganisationChartProps';
import { ServiceScope, Environment, EnvironmentType } from '@microsoft/sp-core-library'; import { ServiceScope, Environment, EnvironmentType } from '@microsoft/sp-core-library';
import { IPerson, IUserProfileService } from '../interfaces'; import { IPerson, IUserProfileService } from '../interfaces';
@ -16,6 +18,35 @@ export interface IOrganisationChartWebPartState {
reports?: IPerson[]; reports?: IPerson[];
} }
interface IPersonaListProps {
title: string;
users: IPerson[];
getProfilePhoto: (photoUrl: string) => string;
onProfileLinkClick: (profileLink: string) => void;
}
class PersonaList extends React.Component<IPersonaListProps, {}> {
public render() {
return (
<div>
<div className={FontClassNames.large}>{this.props.title}</div>
{this.props.users.map((user, index) => (
<div key={index}>
<Persona
imageUrl={this.props.getProfilePhoto(user.PictureUrl)}
primaryText={user.DisplayName}
secondaryText={user.Title}
size={PersonaSize.regular}
presence={PersonaPresence.none}
onClick={() => this.props.onProfileLinkClick(user.UserUrl)}
/>
</div>
))}
</div>
);
}
}
export default class OrganisationChart extends React.Component<IOrganisationChartProps, IOrganisationChartWebPartState> { export default class OrganisationChart extends React.Component<IOrganisationChartProps, IOrganisationChartWebPartState> {
private userProfileServiceInstance: IUserProfileService; private userProfileServiceInstance: IUserProfileService;
@ -48,66 +79,33 @@ export default class OrganisationChart extends React.Component<IOrganisationChar
public render(): React.ReactElement<IOrganisationChartProps> { public render(): React.ReactElement<IOrganisationChartProps> {
return ( return (
<div className={styles['ms-OrgChart']}> <div>
<div className="ms-font-xl"> <div className={FontClassNames.xLarge}>
{escape(this.props.organisationName)} {escape(this.props.organisationName)}
</div> </div>
<div className="ms-OrgChart-group"> <PersonaList
<div className="ms-OrgChart-groupTitle">Managers</div> title="Managers"
<ul className={styles['ms-OrgChart-list']}> users={this.state.managers}
{this.state.managers.map((manager, index) => ( getProfilePhoto={this.getProfilePhoto.bind(this)}
<li key={index} className={styles['ms-OrgChart-listItem']}> onProfileLinkClick={this.onProfileLinkClick.bind(this)}
<button className={styles['ms-OrgChart-listItemBtn']} onClick={() => this.onProfileLinkClick(manager.UserUrl)}> />
<div className="ms-Persona"> <div>
<div className="ms-Persona-imageArea"> <div className={FontClassNames.large}>You</div>
<img className="ms-Persona-image" alt="" role="presentation" src={this.getProfilePhoto(manager.PictureUrl)}></img> <Persona
</div> imageUrl={this.getProfilePhoto(this.state.user.PictureUrl)}
<div className="ms-Persona-details"> primaryText={this.state.user.DisplayName}
<div className="ms-Persona-primaryText">{manager.DisplayName}</div> secondaryText={this.state.user.Title}
<div className="ms-Persona-secondaryText">{manager.Title}</div> size={PersonaSize.regular}
</div> presence={PersonaPresence.none}
</div> onClick={() => this.onProfileLinkClick(this.state.user.UserUrl)}
</button> />
</li>))}
</ul>
</div>
<div className="ms-OrgChart-group">
<div className="ms-OrgChart-groupTitle">You</div>
<ul className={styles['ms-OrgChart-list']}>
<li className={styles['ms-OrgChart-listItem']}>
<button className={styles['ms-OrgChart-listItemBtn']} onClick={() => this.onProfileLinkClick(this.state.user.UserUrl)}>
<div className="ms-Persona">
<div className="ms-Persona-imageArea">
<img className="ms-Persona-image" alt="" role="presentation" src={this.getProfilePhoto(this.state.user.PictureUrl)}></img>
</div>
<div className="ms-Persona-details">
<div className="ms-Persona-primaryText">{this.state.user.DisplayName}</div>
<div className="ms-Persona-secondaryText">{this.state.user.Title}</div>
</div>
</div>
</button>
</li>
</ul>
</div>
<div className="ms-OrgChart-group">
<div className="ms-OrgChart-groupTitle">Reports</div>
<ul className={styles['ms-OrgChart-list']}>
{this.state.reports.map((report, index) => (
<li key={index} className={styles['ms-OrgChart-listItem']}>
<button className={styles['ms-OrgChart-listItemBtn']} onClick={() => this.onProfileLinkClick(report.UserUrl)}>
<div className="ms-Persona">
<div className="ms-Persona-imageArea">
<img className="ms-Persona-image" alt="" role="presentation" src={this.getProfilePhoto(report.PictureUrl)}></img>
</div>
<div className="ms-Persona-details">
<div className="ms-Persona-primaryText">{report.DisplayName}</div>
<div className="ms-Persona-secondaryText">{report.Title}</div>
</div>
</div>
</button>
</li>))}
</ul>
</div> </div>
<PersonaList
title="Reports"
users={this.state.reports}
getProfilePhoto={this.getProfilePhoto.bind(this)}
onProfileLinkClick={this.onProfileLinkClick.bind(this)}
/>
</div> </div>
); );
} }

View File

@ -2,42 +2,42 @@ import { IPerson, IUserProfileService } from '../interfaces';
import { ServiceScope, ServiceKey } from '@microsoft/sp-core-library'; import { ServiceScope, ServiceKey } from '@microsoft/sp-core-library';
export class MockUserProfileService implements IUserProfileService { export class MockUserProfileService implements IUserProfileService {
public static readonly serviceKey: ServiceKey<IUserProfileService> = ServiceKey.create<IUserProfileService>('vrd:MockUserProfileService', MockUserProfileService); public static readonly serviceKey: ServiceKey<IUserProfileService> = ServiceKey.create<IUserProfileService>('vrd:MockUserProfileService', MockUserProfileService);
constructor(serviceScope: ServiceScope) { constructor(serviceScope: ServiceScope) {
} }
public getPropertiesForCurrentUser(): Promise<IPerson> { public getPropertiesForCurrentUser(): Promise<IPerson> {
return new Promise<IPerson>((resolve, reject) => { return new Promise<IPerson>((resolve, reject) => {
const user: IPerson = { Title: "Consultant", DisplayName: "Adam Jones", PictureUrl: "https://raw.githubusercontent.com/OfficeDev/office-ui-fabric-react/master/packages/office-ui-fabric-react/images/persona-male.png" }; const user: IPerson = { Title: "Consultant", DisplayName: "Adam Jones", PictureUrl: "https://raw.githubusercontent.com/OfficeDev/office-ui-fabric-react/master/packages/office-ui-fabric-react/images/persona-male.png" };
resolve(user); resolve(user);
}); });
} }
public getManagers(userLoginNames: string[]): Promise<IPerson[]> { public getManagers(userLoginNames: string[]): Promise<IPerson[]> {
return new Promise<IPerson[]>((resolve, reject) => { return new Promise<IPerson[]>((resolve, reject) => {
const users: IPerson[] = []; const users: IPerson[] = [];
users.push({ Title: "Manager", DisplayName: "Grant Steel", PictureUrl: "https://raw.githubusercontent.com/OfficeDev/office-ui-fabric-react/master/packages/office-ui-fabric-react/images/persona-male.png" }); users.push({ Title: "Manager", DisplayName: "Grant Steel", PictureUrl: "https://raw.githubusercontent.com/OfficeDev/office-ui-fabric-react/master/packages/office-ui-fabric-react/images/persona-male.png" });
users.push({ Title: "Head of Management", DisplayName: "Marcel Grose", PictureUrl: "https://raw.githubusercontent.com/OfficeDev/office-ui-fabric-react/master/packages/office-ui-fabric-react/images/persona-female.png" }); users.push({ Title: "Head of Management", DisplayName: "Marcel Grose", PictureUrl: "https://raw.githubusercontent.com/OfficeDev/office-ui-fabric-react/master/packages/office-ui-fabric-react/images/persona-female.png" });
resolve(users); resolve(users);
}); });
} }
public getReports(userLoginNames: string[]): Promise<IPerson[]> { public getReports(userLoginNames: string[]): Promise<IPerson[]> {
return new Promise<IPerson[]>((resolve, reject) => { return new Promise<IPerson[]>((resolve, reject) => {
const users: IPerson[] = []; const users: IPerson[] = [];
users.push({ Title: "Developer", DisplayName: "Russel Miller", PictureUrl: "https://raw.githubusercontent.com/OfficeDev/office-ui-fabric-react/master/packages/office-ui-fabric-react/images/persona-female.png" }); users.push({ Title: "Developer", DisplayName: "Russel Miller", PictureUrl: "https://raw.githubusercontent.com/OfficeDev/office-ui-fabric-react/master/packages/office-ui-fabric-react/images/persona-female.png" });
users.push({ Title: "IT Admin", DisplayName: "Robert Fischer", PictureUrl: "https://raw.githubusercontent.com/OfficeDev/office-ui-fabric-react/master/packages/office-ui-fabric-react/images/persona-female.png" }); users.push({ Title: "IT Admin", DisplayName: "Robert Fischer", PictureUrl: "https://raw.githubusercontent.com/OfficeDev/office-ui-fabric-react/master/packages/office-ui-fabric-react/images/persona-female.png" });
resolve(users); resolve(users);
}); });
} }
public getProfilePhoto(photoUrl: string) { public getProfilePhoto(photoUrl: string) {
return photoUrl; return photoUrl;
} }
} }

View File

@ -5,77 +5,77 @@ import { SPHttpClient, ISPHttpClientBatchCreationOptions, SPHttpClientResponse,
export class UserProfileService implements IUserProfileService { export class UserProfileService implements IUserProfileService {
public static readonly serviceKey: ServiceKey<IUserProfileService> = ServiceKey.create<IUserProfileService>('vrd:UserProfileService', UserProfileService); public static readonly serviceKey: ServiceKey<IUserProfileService> = ServiceKey.create<IUserProfileService>('vrd:UserProfileService', UserProfileService);
private _spHttpClient: SPHttpClient; private _spHttpClient: SPHttpClient;
private _pageContext: PageContext; private _pageContext: PageContext;
private _currentWebUrl: string; private _currentWebUrl: string;
constructor(serviceScope: ServiceScope) { constructor(serviceScope: ServiceScope) {
serviceScope.whenFinished(() => { serviceScope.whenFinished(() => {
this._spHttpClient = serviceScope.consume(SPHttpClient.serviceKey); this._spHttpClient = serviceScope.consume(SPHttpClient.serviceKey);
this._pageContext = serviceScope.consume(PageContext.serviceKey); this._pageContext = serviceScope.consume(PageContext.serviceKey);
this._currentWebUrl = this._pageContext.web.absoluteUrl; this._currentWebUrl = this._pageContext.web.absoluteUrl;
}); });
} }
public getPropertiesForCurrentUser(): Promise<IPerson> { public getPropertiesForCurrentUser(): Promise<IPerson> {
return this._spHttpClient.get(`${this._currentWebUrl}/_api/SP.UserProfiles.PeopleManager/GetMyProperties?$select=DisplayName,Title,UserUrl,PictureUrl,DirectReports,ExtendedManagers`, return this._spHttpClient.get(`${this._currentWebUrl}/_api/SP.UserProfiles.PeopleManager/GetMyProperties?$select=DisplayName,Title,UserUrl,PictureUrl,DirectReports,ExtendedManagers`,
SPHttpClient.configurations.v1) SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => { .then((response: SPHttpClientResponse) => {
return response.json(); return response.json();
}); });
} }
public getManagers(userLoginNames: string[]): Promise<IPerson[]> { public getManagers(userLoginNames: string[]): Promise<IPerson[]> {
return this.getPropertiesForUsers(userLoginNames); return this.getPropertiesForUsers(userLoginNames);
} }
public getReports(userLoginNames: string[]): Promise<IPerson[]> { public getReports(userLoginNames: string[]): Promise<IPerson[]> {
return this.getPropertiesForUsers(userLoginNames); return this.getPropertiesForUsers(userLoginNames);
} }
private getPropertiesForUsers(userLoginNames: string[]): Promise<IPerson[]> { private getPropertiesForUsers(userLoginNames: string[]): Promise<IPerson[]> {
return new Promise<IPerson[]>((resolve, reject) => { return new Promise<IPerson[]>((resolve, reject) => {
//at least 1 login name should be supplied //at least 1 login name should be supplied
if (userLoginNames.length > 0) { if (userLoginNames.length > 0) {
const arrayOfPersons: IPerson[] = []; const arrayOfPersons: IPerson[] = [];
const spBatchCreationOpts: ISPHttpClientBatchCreationOptions = { webUrl: this._currentWebUrl }; const spBatchCreationOpts: ISPHttpClientBatchCreationOptions = { webUrl: this._currentWebUrl };
const spBatch: SPHttpClientBatch = this._spHttpClient.beginBatch(spBatchCreationOpts); const spBatch: SPHttpClientBatch = this._spHttpClient.beginBatch(spBatchCreationOpts);
const userResponses: Promise<SPHttpClientResponse>[] = []; const userResponses: Promise<SPHttpClientResponse>[] = [];
for (const userLoginName of userLoginNames) { for (const userLoginName of userLoginNames) {
const getUserProps: Promise<SPHttpClientResponse> = spBatch.get(`${this._currentWebUrl}/_api/SP.UserProfiles.PeopleManager/GetPropertiesFor(accountName=@v)?@v='${encodeURIComponent(userLoginName)}' const getUserProps: Promise<SPHttpClientResponse> = spBatch.get(`${this._currentWebUrl}/_api/SP.UserProfiles.PeopleManager/GetPropertiesFor(accountName=@v)?@v='${encodeURIComponent(userLoginName)}'
&$select=DisplayName,Title,UserUrl,PictureUrl,DirectReports,ExtendedManagers`, &$select=DisplayName,Title,UserUrl,PictureUrl,DirectReports,ExtendedManagers`,
SPHttpClientBatch.configurations.v1); SPHttpClientBatch.configurations.v1);
userResponses.push(getUserProps); userResponses.push(getUserProps);
}
// Make the batch request
spBatch.execute().then(() => {
userResponses.forEach((item, index) => {
item.then((response: SPHttpClientResponse) => {
response.json().then((responseJSON: IPerson) => {
arrayOfPersons.push(responseJSON);
if (index == (userResponses.length) - 1) {
resolve(arrayOfPersons);
} }
});
// Make the batch request });
spBatch.execute().then(() => { });
userResponses.forEach((item, index) => {
item.then((response: SPHttpClientResponse) => {
response.json().then((responseJSON: IPerson) => {
arrayOfPersons.push(responseJSON);
if (index == (userResponses.length) - 1) {
resolve(arrayOfPersons);
}
});
});
});
});
}
}); });
} }
});
}
//SharePoint does not return the userphoto if the current user has not currently signed in to their MySite (ODfB site) //SharePoint does not return the userphoto if the current user has not currently signed in to their MySite (ODfB site)
//This method of getting the user photo works in all scenarios. //This method of getting the user photo works in all scenarios.
public getProfilePhoto(photoUrl: string) { public getProfilePhoto(photoUrl: string) {
return `/_layouts/15/userphoto.aspx?size=M&url=${photoUrl}`; return `/_layouts/15/userphoto.aspx?size=M&url=${photoUrl}`;
} }
} }