|
@ -467,4 +467,4 @@ With the following template:
|
|||
|
||||
Note that, in the example above, the `person-query` attribute is still bound using the Handlebars syntax `person-query="{{MyPersonField.personValue.email}}"`, whereas the MGT template uses `[[ person.image ]]` and `[[ person.displayName ]]`.
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-web parts/samples/react-content-query-online" />
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-content-query-online" />
|
||||
|
|
|
@ -43,6 +43,7 @@ Version|Date|Comments
|
|||
1.1|February 24, 2021|Added support for large lists
|
||||
1.2|March 01, 2021|Fixed search issue for number field
|
||||
1.3|March 31,2021| Changed UI as per SharePoint list, Set themeing as per current SharePoint site theme, Created custom pagination by using reusable controls, Added features to export CSV based on the filter if the filter is available, Added hyperlink feature for image and link column in export to pdf and also set alternative row formatting in generated pdf as per property pane configuration odd/even row color, fixed object issue (for people/hyperlink, etc) in export to CSV.
|
||||
1.4|April 10, 2021|Added feature to show profile picture in user column and shows display name of user field in export to CSV and PDF.
|
||||
|
||||
|
||||
## Disclaimer
|
||||
|
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 48 KiB |
|
@ -9,7 +9,7 @@
|
|||
"This web part provides easy way to render SharePoint custom list in data table view with all the necessary features."
|
||||
],
|
||||
"created": "2021-03-01",
|
||||
"modified": "2021-03-01",
|
||||
"modified": "2021-04-10",
|
||||
"products": [
|
||||
"SharePoint",
|
||||
"Office"
|
||||
|
@ -49,4 +49,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
export function csvCellFormatter(value: any, type: string) {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
switch (type) {
|
||||
case 'SP.FieldUrl':
|
||||
value = value.props.children;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
switch (type) {
|
||||
case 'SP.FieldUrl':
|
||||
value = value.props.children;
|
||||
break;
|
||||
case 'SP.FieldUser':
|
||||
value = value.props.displayName;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
export function pdfCellFormatter(value: any, type: string) {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
switch (type) {
|
||||
case 'SP.FieldUrl':
|
||||
let { children: text, href: link } = value.props;
|
||||
value = { text, link, color: 'blue' };
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
switch (type) {
|
||||
case 'SP.FieldUrl':
|
||||
let { children: text, href: link } = value.props;
|
||||
value = { text, link, color: 'blue' };
|
||||
break;
|
||||
case 'SP.FieldUser':
|
||||
value = value.props.displayName;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import * as React from 'react';
|
||||
import { Persona, PersonaSize } from 'office-ui-fabric-react/lib/Persona';
|
||||
|
||||
interface IProfilePicProps {
|
||||
loginName: string;
|
||||
displayName: string;
|
||||
getUserProfileUrl: () => Promise<string>;
|
||||
}
|
||||
|
||||
export function RenderProfilePicture(props: IProfilePicProps) {
|
||||
|
||||
const [profileUrl, setProfileUrl] = React.useState<string>();
|
||||
let { displayName, getUserProfileUrl } = props;
|
||||
|
||||
React.useEffect(() => {
|
||||
getUserProfileUrl().then(url => {
|
||||
setProfileUrl(url);
|
||||
});
|
||||
}, [props])
|
||||
|
||||
return (
|
||||
<Persona
|
||||
imageUrl={profileUrl}
|
||||
text={displayName}
|
||||
size={PersonaSize.size32}
|
||||
imageAlt={displayName}
|
||||
styles={{ primaryText: { fontSize: '12px' } }}
|
||||
/>);
|
||||
}
|
|
@ -17,7 +17,7 @@ export class SPService {
|
|||
for (var i = 0; i < selectedFields.length; i++) {
|
||||
switch (selectedFields[i].fieldType) {
|
||||
case 'SP.FieldUser':
|
||||
selectQuery.push(`${selectedFields[i].key}/Title,${selectedFields[i].key}/Name`);
|
||||
selectQuery.push(`${selectedFields[i].key}/Title,${selectedFields[i].key}/EMail,${selectedFields[i].key}/Name`);
|
||||
expandQuery.push(selectedFields[i].key);
|
||||
break;
|
||||
case 'SP.FieldLookup':
|
||||
|
@ -63,9 +63,10 @@ export class SPService {
|
|||
}
|
||||
}
|
||||
|
||||
public async getUserProfileUrl(loginName: string, propertyName: string) {
|
||||
public async getUserProfileUrl(loginName: string) {
|
||||
try {
|
||||
const profileUrl = await sp.profiles.getUserProfilePropertyFor(loginName, propertyName);
|
||||
const properties = await sp.profiles.getPropertiesFor(loginName);
|
||||
const profileUrl = properties['PictureUrl'];
|
||||
return profileUrl;
|
||||
}
|
||||
catch (err) {
|
||||
|
|
|
@ -17,7 +17,7 @@ import { DetailsList, DetailsListLayoutMode, DetailsRow, IDetailsRowStyles, IDet
|
|||
import { pdfCellFormatter } from '../../../shared/common/ExportListItemsToPDF/ExportListItemsToPDFFormatter';
|
||||
import { csvCellFormatter } from '../../../shared/common/ExportListItemsToCSV/ExportListItemsToCSVFormatter';
|
||||
import { IPropertyPaneDropdownOption } from '@microsoft/sp-property-pane';
|
||||
|
||||
import { RenderProfilePicture } from '../../../shared/common/RenderProfilePicture/RenderProfilePicture';
|
||||
|
||||
export default class ReactDatatable extends React.Component<IReactDatatableProps, IReactDatatableState> {
|
||||
|
||||
|
@ -45,6 +45,10 @@ export default class ReactDatatable extends React.Component<IReactDatatableProps
|
|||
this.getSelectedListItems();
|
||||
}
|
||||
|
||||
private getUserProfileUrl = (loginName: string) => {
|
||||
return this._services.getUserProfileUrl(loginName)
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: IReactDatatableProps) {
|
||||
if (prevProps.list !== this.props.list) {
|
||||
this.props.onChangeProperty("list");
|
||||
|
@ -82,7 +86,7 @@ export default class ReactDatatable extends React.Component<IReactDatatableProps
|
|||
private _onConfigure() {
|
||||
this.props.context.propertyPane.open();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public formatColumnValue(value: any, type: string) {
|
||||
if (!value) {
|
||||
|
@ -102,7 +106,9 @@ export default class ReactDatatable extends React.Component<IReactDatatableProps
|
|||
value = value['Title'];
|
||||
break;
|
||||
case 'SP.FieldUser':
|
||||
value = value['Title'];
|
||||
let loginName = value['Name'];
|
||||
let userName = value['Title'];
|
||||
value = <RenderProfilePicture loginName={loginName} displayName={userName} getUserProfileUrl={() => this.getUserProfileUrl(loginName)} ></RenderProfilePicture>;
|
||||
break;
|
||||
case 'SP.FieldMultiLineText':
|
||||
value = <div dangerouslySetInnerHTML={{ __html: value }}></div>;
|
||||
|
@ -124,6 +130,7 @@ export default class ReactDatatable extends React.Component<IReactDatatableProps
|
|||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public formatValueForExportingData(value: any, type?: string) {
|
||||
if (!value) {
|
||||
return value;
|
||||
|
@ -144,17 +151,17 @@ export default class ReactDatatable extends React.Component<IReactDatatableProps
|
|||
return value;
|
||||
}
|
||||
|
||||
private exportDataFormatter(fields: Array<IPropertyPaneDropdownOption & { fieldType: string }>, listItems: any[], cellFormatterFn: (value: any, type: string) => any){
|
||||
private exportDataFormatter(fields: Array<IPropertyPaneDropdownOption & { fieldType: string }>, listItems: any[], cellFormatterFn: (value: any, type: string) => any) {
|
||||
return listItems && listItems.map(item => ({
|
||||
...fields.reduce((ob, f) => {
|
||||
ob[f.text] = item[f.key] ? cellFormatterFn(item[f.key], f.fieldType) : '-';
|
||||
return ob;
|
||||
}, {})
|
||||
}));
|
||||
...fields.reduce((ob, f) => {
|
||||
ob[f.text] = item[f.key] ? cellFormatterFn(item[f.key], f.fieldType) : '-';
|
||||
return ob;
|
||||
}, {})
|
||||
}));
|
||||
}
|
||||
|
||||
private handlePaginationChange(pageNo: number, pageSize: number) {
|
||||
this.setState({ page: pageNo, rowsPerPage: pageSize });
|
||||
private handlePaginationChange(pageNo: number, rowsPerPage: number) {
|
||||
this.setState({ page: pageNo, rowsPerPage: rowsPerPage });
|
||||
}
|
||||
|
||||
public handleSearch(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
|
@ -257,8 +264,8 @@ export default class ReactDatatable extends React.Component<IReactDatatableProps
|
|||
columnHeader={columns.map(c => c.name)}
|
||||
listName={list}
|
||||
description={title}
|
||||
dataSource={()=> this.exportDataFormatter(fields, filteredItems, csvCellFormatter)}
|
||||
/> : <></>}
|
||||
dataSource={() => this.exportDataFormatter(fields, filteredItems, csvCellFormatter)}
|
||||
/> : <></>}
|
||||
{enableDownloadAsPdf
|
||||
? <ExportListItemsToPDF
|
||||
listName={list}
|
||||
|
@ -266,7 +273,7 @@ export default class ReactDatatable extends React.Component<IReactDatatableProps
|
|||
columns={columns.map(c => c.name)}
|
||||
oddRowColor={oddRowColor}
|
||||
evenRowColor={evenRowColor}
|
||||
dataSource={()=> this.exportDataFormatter(fields, filteredItems, pdfCellFormatter)} />
|
||||
dataSource={() => this.exportDataFormatter(fields, filteredItems, pdfCellFormatter)} />
|
||||
: <></>}
|
||||
</Grid>
|
||||
<Grid container justify='flex-end' xs={6}>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.11.0",
|
||||
"libraryName": "react-graph-mgt-client",
|
||||
"libraryId": "8548b10e-1f21-4797-ae74-279081454b9f",
|
||||
"packageManager": "npm",
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
---
|
||||
page_type: sample
|
||||
products:
|
||||
- office-sp
|
||||
- sharepoint
|
||||
- microsoft-graph
|
||||
languages:
|
||||
- javascript
|
||||
- typescript
|
||||
extensions:
|
||||
contentType: samples
|
||||
technologies:
|
||||
- SharePoint Framework
|
||||
- Microsoft Graph
|
||||
platforms:
|
||||
- React
|
||||
createdDate: 2021-04-18T19:43:46.356Z
|
||||
---
|
||||
|
||||
# Graph MGT Client
|
||||
|
||||
## Summary
|
||||
|
||||
This is a sample web part developed using React Framework that showcases how to use the latest `microsoft-graph-client` in order to do advanced configuration of the Microsoft Graph client. This enables scenarios like throttling management, Chaos management and a lot more!
|
||||
|
||||
![Demo of the Graph Client using the MGT providers](./assets/DemoGraphClient.gif)
|
||||
|
||||
## Compatibility
|
||||
|
||||
![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
|
||||
![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg)
|
||||
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
|
||||
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
|
||||
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
|
||||
|
||||
|
||||
## Applies to
|
||||
|
||||
- [SharePoint Framework](https://aka.ms/spfx)
|
||||
- [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
|
||||
|
||||
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/o365devprogram)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
In the `package-solution.json` file, ensure that the scopes are available for the calls you want to do on Microsoft Graph.
|
||||
|
||||
```json
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.Read.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Calendars.Read"
|
||||
}
|
||||
],
|
||||
```
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-graph-mgt-client | [Sébastien Levert](https://www.linkedin.com/in/sebastienlevert), Microsoft ([@sebastienlevert](https://twitter.com/sebastienlevert))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|April 18, 2021|Initial release
|
||||
|
||||
## 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.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Clone this repository
|
||||
- Ensure that you are at the solution folder
|
||||
- in the command-line run:
|
||||
- **npm install**
|
||||
- **gulp bundle**
|
||||
- **gulp package-solution**
|
||||
- Deploy the generated *.sppkg to your App Catalog
|
||||
- Approve the Microsoft Graph scopes
|
||||
- in the command-line run:
|
||||
- **gulp serve**
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
This sample illustrates how to leverage the Microsoft Graph Toolkit providers to extend the experience of using the `microsoft-graph-client` in SharePoint Framework and overcomes some of its current limitations.
|
||||
|
||||
This sample illustrates the following concepts:
|
||||
|
||||
- Adding MGT providers to an existing SPFx solution
|
||||
- Replace Microsoft Graph calls built using the integrated Microsoft Graph client
|
||||
- Use the latest and greatest Microsoft Graph client in order to have more power over your calls
|
||||
|
||||
## References
|
||||
|
||||
- [Getting started with SharePoint Framework](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
|
||||
- [Building for Microsoft teams](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/build-for-teams-overview)
|
||||
- [Use Microsoft Graph in your solution](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/using-microsoft-graph-apis)
|
||||
- [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview)
|
||||
- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-graph-mgt-client" />
|
After Width: | Height: | Size: 144 KiB |
|
@ -0,0 +1,52 @@
|
|||
[
|
||||
{
|
||||
"name": "pnp-sp-dev-spfx-web-parts-react-graph-mgt-client",
|
||||
"source": "pnp",
|
||||
"title": "Graph MGT Client",
|
||||
"shortDescription": "This is a sample web part developed using React Framework that showcases how to use the latest `microsoft-graph-client` in order to do advanced configuration of the Microsoft Graph client. ",
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/master/samples/react-graph-mgt-client",
|
||||
"longDescription": [
|
||||
"This is a sample web part developed using React Framework that showcases how to use the latest `microsoft-graph-client` in order to do advanced configuration of the Microsoft Graph client.",
|
||||
"This enables scenarios like throttling management, Chaos management and a lot more!"
|
||||
],
|
||||
"created": "2021-04-18",
|
||||
"modified": "2021-04-18",
|
||||
"products": [
|
||||
"SharePoint",
|
||||
"Office"
|
||||
],
|
||||
"metadata": [
|
||||
{
|
||||
"key": "CLIENT-SIDE-DEV",
|
||||
"value": "React"
|
||||
},
|
||||
{
|
||||
"key": "SPFX-VERSION",
|
||||
"value": "1.11.0"
|
||||
}
|
||||
],
|
||||
"thumbnails": [
|
||||
{
|
||||
"type": "image",
|
||||
"order": 100,
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-graph-mgt-client/assets/DemoGraphClient.gif",
|
||||
"alt": "Web Part in action"
|
||||
}
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"gitHubAccount": "sebastienlevert",
|
||||
"pictureUrl": "https://github.com/sebastienlevert.png",
|
||||
"name": "Sébastien Levert",
|
||||
"twitter": "sebastienlevert"
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"name": "Build your first SharePoint client-side web part",
|
||||
"description": "Client-side web parts are client-side components that run in the context of a SharePoint page. Client-side web parts can be deployed to SharePoint environments that support the SharePoint Framework. You can also use modern JavaScript web frameworks, tools, and libraries to build them.",
|
||||
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"graph-latest-client-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/graphClient/GraphClientWebPart.js",
|
||||
"manifest": "./src/webparts/graphClient/GraphClientWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"GraphClientWebPartStrings": "lib/webparts/graphClient/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-graph-mgt-client",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-graph-mgt-client-client-side-solution",
|
||||
"id": "8548b10e-1f21-4797-ae74-279081454b9f",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.Read.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Calendars.Read"
|
||||
}
|
||||
],
|
||||
"developer": {
|
||||
"name": "",
|
||||
"websiteUrl": "",
|
||||
"privacyUrl": "",
|
||||
"termsOfUseUrl": "",
|
||||
"mpnId": ""
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-graph-mgt-client.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(require('gulp'));
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "react-graph-mgt-client",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/mgt-element": "^2.1.0",
|
||||
"@microsoft/mgt-sharepoint-provider": "^2.1.0",
|
||||
"@microsoft/microsoft-graph-client": "^2.2.1",
|
||||
"@microsoft/sp-core-library": "1.11.0",
|
||||
"@microsoft/sp-lodash-subset": "1.11.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
|
||||
"@microsoft/sp-property-pane": "1.11.0",
|
||||
"@microsoft/sp-webpart-base": "1.11.0",
|
||||
"@monaco-editor/react": "^4.1.1",
|
||||
"office-ui-fabric-react": "6.214.0",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
|
||||
"@microsoft/rush-stack-compiler-3.7": "^0.6.43",
|
||||
"@microsoft/sp-build-web": "1.11.0",
|
||||
"@microsoft/sp-module-interfaces": "1.11.0",
|
||||
"@microsoft/sp-tslint-rules": "1.11.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.11.0",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/mocha": "2.2.38",
|
||||
"@types/react": "16.8.8",
|
||||
"@types/react-dom": "16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1",
|
||||
"typescript": "^3.7.7"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "aaa33b68-65d0-4403-bbe4-1cb21cc1936b",
|
||||
"alias": "GraphClientWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": ["SharePointWebPart"],
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "GraphClient" },
|
||||
"description": { "default": "GraphClient description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "GraphClient"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
} from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
|
||||
import GraphClient from './components/GraphClient';
|
||||
import { IGraphClientProps } from './components/IGraphClientProps';
|
||||
|
||||
// Importing MGT in the Web Part
|
||||
import { SharePointProvider } from '@microsoft/mgt-sharepoint-provider';
|
||||
import { Providers } from '@microsoft/mgt-element';
|
||||
|
||||
export interface IGraphLatestClientWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class GraphLatestClientWebPart extends BaseClientSideWebPart<IGraphLatestClientWebPartProps> {
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IGraphClientProps> = React.createElement(
|
||||
GraphClient,
|
||||
{
|
||||
description: this.properties.description
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a new SharePoint provider to the MGT providers
|
||||
*/
|
||||
protected async onInit() {
|
||||
Providers.globalProvider = new SharePointProvider(this.context);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: []
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.graphLatestClient {
|
||||
.container {
|
||||
max-width: 700px;
|
||||
margin: 0px auto;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.col2 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-sm2;
|
||||
}
|
||||
|
||||
.col4 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-sm4;
|
||||
}
|
||||
|
||||
.col8 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-sm8;
|
||||
}
|
||||
|
||||
.col10 {
|
||||
@include ms-Grid-col;
|
||||
@include ms-sm10;
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: $ms-color-themePrimary;
|
||||
border-color: $ms-color-themePrimary;
|
||||
color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: $ms-font-size-m;
|
||||
font-weight: $ms-font-weight-regular;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
import * as React from 'react';
|
||||
import styles from './GraphClient.module.scss';
|
||||
import { IGraphClientProps } from './IGraphClientProps';
|
||||
import { Providers } from '@microsoft/mgt-element';
|
||||
import { RetryHandlerOptions } from '@microsoft/microsoft-graph-client';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import { TextField } from 'office-ui-fabric-react/lib/TextField';
|
||||
import { DefaultButton } from 'office-ui-fabric-react/lib/Button';
|
||||
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||
|
||||
export interface IGraphClientState {
|
||||
response: any;
|
||||
apiUrl: string;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export default class GraphClient extends React.Component<IGraphClientProps, IGraphClientState> {
|
||||
constructor(props: IGraphClientProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
response: null,
|
||||
apiUrl: '',
|
||||
loading: false
|
||||
};
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IGraphClientProps> {
|
||||
return (
|
||||
<div className={ styles.graphLatestClient }>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.col8}>
|
||||
<TextField
|
||||
placeholder="Specify your query"
|
||||
value={this.state.apiUrl}
|
||||
onChanged={this._apiUrlChanged}
|
||||
onKeyUp={(e: React.KeyboardEvent<any>) => e.key === 'Enter' && this._runQuery()}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.col4}>
|
||||
<DefaultButton
|
||||
primary={true}
|
||||
onClick={this._runQuery}
|
||||
style={{ width: '100%' }}
|
||||
disabled={this.state.loading}>
|
||||
Run query
|
||||
{this.state.loading && (
|
||||
<>
|
||||
<Spinner className={styles.spinner} size={SpinnerSize.xSmall} />
|
||||
</>
|
||||
)}
|
||||
</DefaultButton>
|
||||
</div>
|
||||
</div>
|
||||
<Editor height="600px" theme="vs-dark" defaultLanguage="json" value={this.state.response ? JSON.stringify(this.state.response, null, 2) : ''} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for api URL change
|
||||
*/
|
||||
private _apiUrlChanged = (val: string) => {
|
||||
this.setState({
|
||||
apiUrl: val,
|
||||
});
|
||||
}
|
||||
|
||||
private _runQuery = async () => {
|
||||
this.setState({
|
||||
response: null,
|
||||
loading: true
|
||||
});
|
||||
|
||||
const data = await Providers.globalProvider.graph.api("/me/events").middlewareOptions([new RetryHandlerOptions(5, 10)]).get();
|
||||
|
||||
if(data && data.value && data.value.length > 0) {
|
||||
this.setState({
|
||||
response: data,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface IGraphClientProps {
|
||||
description: string;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface IGraphClientWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'GraphClientWebPartStrings' {
|
||||
const strings: IGraphClientWebPartStrings;
|
||||
export = strings;
|
||||
}
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 383 B |
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.7/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.11.0",
|
||||
"libraryName": "react-onedrive-finder",
|
||||
"libraryId": "63bcee97-4373-4a30-9c62-a1369b4d08ba",
|
||||
"packageManager": "npm",
|
||||
"isDomainIsolated": true,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 23 KiB |
|
@ -0,0 +1,57 @@
|
|||
[
|
||||
{
|
||||
"name": "pnp-sp-dev-spfx-web-parts-react-onedrive-finder",
|
||||
"source": "pnp",
|
||||
"title": "OneDrive Finder",
|
||||
"shortDescription": "This sample access drives from OneDrive and navigate between his content using Graph OneDrive and Site API and Microsoft Graph Toolkit React controls with the addition of new FileList control.",
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/master/samples/react-onedrive-finder",
|
||||
"longDescription": [
|
||||
"This sample access drives from OneDrive and navigate between his content using Graph OneDrive and Site API and Microsoft Graph Toolkit React controls with the addition of new FileList control.",
|
||||
"This new control provides the ability to retrieve the Drive Library with associated Files and folders making easier to develop and navigate between tenant content and access to file. "
|
||||
],
|
||||
"created": "2021-04-16",
|
||||
"modified": "2021-04-16",
|
||||
"products": [
|
||||
"SharePoint",
|
||||
"Office"
|
||||
],
|
||||
"metadata": [
|
||||
{
|
||||
"key": "CLIENT-SIDE-DEV",
|
||||
"value": "React"
|
||||
},
|
||||
{
|
||||
"key": "SPFX-VERSION",
|
||||
"value": "1.11.0"
|
||||
}
|
||||
],
|
||||
"thumbnails": [
|
||||
{
|
||||
"type": "image",
|
||||
"order": 100,
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-onedrive-finder/Assets/OneDrivefinderSample1.PNG",
|
||||
"alt": "Web Part in action"
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"order": 101,
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-onedrive-finder/Assets/OneDrivefinderSample2.PNG",
|
||||
"alt": "Web Part in action"
|
||||
}
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"gitHubAccount": "aaclage",
|
||||
"pictureUrl": "https://github.com/aaclage.png",
|
||||
"name": "André Lage"
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"name": "Build your first SharePoint client-side web part",
|
||||
"description": "Client-side web parts are client-side components that run in the context of a SharePoint page. Client-side web parts can be deployed to SharePoint environments that support the SharePoint Framework. You can also use modern JavaScript web frameworks, tools, and libraries to build them.",
|
||||
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,84 @@
|
|||
# OneDrive finder
|
||||
|
||||
## Summary
|
||||
|
||||
This sample access drives from OneDrive and navigate between his content using **Graph OneDrive and Site API and [Microsoft Graph Toolkit](https://github.com/microsoftgraph/microsoft-graph-toolkit) react controls "@microsoft/mgt-react**" with the addition of new control **[FileList](https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/f8b8aa81d00bf426b94ee5016d511bc78b36e152/stories/components/fileList.stories.js#L136) "still preview version"** . This new control provides the ability to retrieve the Drive Library with associated Files and folders making easier to develop and navigate between tenant content and access to file.
|
||||
|
||||
**[FileList](https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/f8b8aa81d00bf426b94ee5016d511bc78b36e152/stories/components/fileList.stories.js#L136)** control allow to load files base on graph querys or parameters ids.
|
||||
|
||||
### Retrieve Sites with drives associate
|
||||
|
||||
![Demo](./Assets/OneDrivefinderSample1.PNG)
|
||||
|
||||
### Navigate between folders and Breadcrumb
|
||||
|
||||
![Demo](./Assets/OneDrivefinderSample2.PNG)
|
||||
|
||||
|
||||
## Compatibility
|
||||
|
||||
![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
|
||||
![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg)
|
||||
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
|
||||
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
|
||||
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
|
||||
|
||||
## Applies to
|
||||
|
||||
- [SharePoint Framework](https://aka.ms/spfx)
|
||||
- [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
|
||||
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-onedrive-finder | [André Lage](http://aaclage.blogspot.com) ([@aaclage](https://twitter.com/aaclage))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|April 16, 2021|Initial release
|
||||
|
||||
## 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.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Clone this repository
|
||||
- Ensure that you are at the solution folder
|
||||
- in the command-line run:
|
||||
- **npm install**
|
||||
- **gulp serve**
|
||||
- **gulp bundle --ship**
|
||||
- **gulp package-solution --ship**
|
||||
- Add to AppCatalog and deploy
|
||||
|
||||
## Grant the service principal permission to the Microsoft Graph API
|
||||
|
||||
Once installed, the solution will request the required permissions via the **Office 365 admin portal > SharePoint > Advanced > API access**.
|
||||
If you prefer to approve the permissions in advance, for example when testing the solution in the Workbench page without installing it, you can do so using the [CLI for Microsoft 365](https://pnp.github.io/cli-microsoft365/):
|
||||
|
||||
```bash
|
||||
o365 spo login https://contoso-admin.sharepoint.com
|
||||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Files.Read'
|
||||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Files.Read.All'
|
||||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Sites.Read.All'
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
Description of the extension that expands upon high-level summary above.
|
||||
|
||||
This extension illustrates the following concepts:
|
||||
|
||||
- Easy to navigate between shared Drives using **Graph API and Breadcrumb**
|
||||
- **[FileList](https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/f8b8aa81d00bf426b94ee5016d511bc78b36e152/stories/components/fileList.stories.js#L136)** control allow to load documents base on graph querys or parameters ids.
|
||||
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-onedrive-finder" />
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"one-drive-finder-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/oneDriveFinder/OneDriveFinderWebPart.js",
|
||||
"manifest": "./src/webparts/oneDriveFinder/OneDriveFinderWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"OneDriveFinderWebPartStrings": "lib/webparts/oneDriveFinder/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-onedrive-finder",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-onedrive-finder-client-side-solution",
|
||||
"id": "63bcee97-4373-4a30-9c62-a1369b4d08ba",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"developer": {
|
||||
"name": "",
|
||||
"websiteUrl": "",
|
||||
"privacyUrl": "",
|
||||
"termsOfUseUrl": "",
|
||||
"mpnId": ""
|
||||
},
|
||||
"webApiPermissionRequests": [{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Files.Read"
|
||||
}, {
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Files.Read.All"
|
||||
}, {
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Sites.Read.All"
|
||||
}]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-onedrive-finder.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(require('gulp'));
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "react-onedrive-finder",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/mgt": "^2.2.0-next.file.0e80deb",
|
||||
"@microsoft/mgt-react": "^2.2.0-next.file.0e80deb",
|
||||
"@microsoft/sp-core-library": "1.11.0",
|
||||
"@microsoft/sp-lodash-subset": "1.11.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
|
||||
"@microsoft/sp-property-pane": "1.11.0",
|
||||
"@microsoft/sp-webpart-base": "1.11.0",
|
||||
"office-ui-fabric-react": "6.214.0",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
|
||||
"@microsoft/rush-stack-compiler-3.7": "^0.6.43",
|
||||
"@microsoft/sp-build-web": "1.11.0",
|
||||
"@microsoft/sp-module-interfaces": "1.11.0",
|
||||
"@microsoft/sp-tslint-rules": "1.11.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.11.0",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/mocha": "2.2.38",
|
||||
"@types/react": "16.8.8",
|
||||
"@types/react-dom": "16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "88e1b1fd-d39d-4194-966c-9bc1c307bcdf",
|
||||
"alias": "OneDriveFinderWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": ["SharePointWebPart"],
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "OneDrive finder" },
|
||||
"description": { "default": "OneDrive finder description" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "OneDrive finder"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
import { Providers, SharePointProvider } from '@microsoft/mgt';
|
||||
|
||||
import * as strings from 'OneDriveFinderWebPartStrings';
|
||||
import OneDriveFinder from './components/OneDriveFinder';
|
||||
import { IOneDriveFinderProps } from './components/IOneDriveFinderProps';
|
||||
|
||||
export interface IOneDriveFinderWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class OneDriveFinderWebPart extends BaseClientSideWebPart<IOneDriveFinderWebPartProps> {
|
||||
protected onInit() {
|
||||
Providers.globalProvider = new SharePointProvider(this.context);
|
||||
return super.onInit();
|
||||
}
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IOneDriveFinderProps> = React.createElement(
|
||||
OneDriveFinder,
|
||||
{
|
||||
description: this.properties.description,
|
||||
context: this.context
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('description', {
|
||||
label: strings.DescriptionFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
export interface IOneDriveFinderProps {
|
||||
description: string;
|
||||
context: WebPartContext;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { IBreadcrumbItem } from 'office-ui-fabric-react/lib/Breadcrumb';
|
||||
import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||
|
||||
|
||||
export interface IOneDriveFinderState {
|
||||
breadcrumbItem: IBreadcrumbItem[];
|
||||
pageSize: number;
|
||||
siteID:string;
|
||||
itemID:string;
|
||||
siteItems: IDropdownOption[];
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
.some-page-wrapper {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-basis: 100%;
|
||||
flex: 1;
|
||||
}
|
|
@ -0,0 +1,364 @@
|
|||
import * as React from 'react';
|
||||
import styles from './OneDriveFinder.module.scss';
|
||||
import { IOneDriveFinderProps } from './IOneDriveFinderProps';
|
||||
import { IOneDriveFinderState } from './IOneDriveFinderState';
|
||||
import { FileList } from '@microsoft/mgt-react';
|
||||
import { Breadcrumb, IBreadcrumbItem } from 'office-ui-fabric-react/lib/Breadcrumb';
|
||||
import { Dropdown, IDropdownOption, IDropdownProps } from 'office-ui-fabric-react/lib/Dropdown';
|
||||
import { AadHttpClient } from "@microsoft/sp-http";
|
||||
import { ITheme, mergeStyleSets, getTheme } from 'office-ui-fabric-react/lib/Styling';
|
||||
import { Icon } from 'office-ui-fabric-react/lib/Icon';
|
||||
|
||||
const theme: ITheme = getTheme();
|
||||
const { palette, fonts } = theme;
|
||||
const iconStyles = { marginRight: '8px' };
|
||||
const classNames = mergeStyleSets({
|
||||
itemIndex: {
|
||||
fontSize: fonts.small.fontSize,
|
||||
color: palette.neutralTertiary,
|
||||
marginBottom: 5,
|
||||
},
|
||||
});
|
||||
const dropdownStyles = {
|
||||
dropdown: { width: 300 },
|
||||
root: {
|
||||
}
|
||||
};
|
||||
const dropdownFilterStyles = {
|
||||
dropdown: {
|
||||
width: 130
|
||||
},
|
||||
label: {
|
||||
},
|
||||
root: {
|
||||
paddingLeft: 90
|
||||
}
|
||||
};
|
||||
|
||||
const onRenderCaretDown = (): JSX.Element => {
|
||||
return <Icon iconName="PageRight" />;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param option
|
||||
* Render dropdown list of sites.
|
||||
*/
|
||||
const onRenderOption = (option: IDropdownOption): JSX.Element => {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{option.data && option.data.icon && (
|
||||
<Icon style={iconStyles} iconName={option.data.icon} aria-hidden="true" title={option.data.icon} />
|
||||
)}
|
||||
<span>{option.text}</span>
|
||||
</div>
|
||||
<div className={classNames.itemIndex}>{option.data.webUrl}</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render Selected dropdown
|
||||
*/
|
||||
const onRenderTitle = (options: IDropdownOption[]): JSX.Element => {
|
||||
const option = options[0];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{option.data && option.data.icon && (
|
||||
<Icon style={iconStyles} iconName={option.data.icon} aria-hidden="true" title={option.data.icon} />
|
||||
)}
|
||||
<span>{option.text}</span>
|
||||
</div>
|
||||
<div className={classNames.itemIndex}>{option.data.webUrl}</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const onRenderPlaceholder = (props: IDropdownProps): JSX.Element => {
|
||||
return (
|
||||
<div className="dropdownExample-placeholder">
|
||||
<span>{props.placeholder}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default class OneDriveFinder extends React.Component<IOneDriveFinderProps, IOneDriveFinderState> {
|
||||
public _domain: string;
|
||||
public _itemID: string;
|
||||
public _siteID: string;
|
||||
public _pageSize: number;
|
||||
public _breadcrumbItem: IBreadcrumbItem[] = [];
|
||||
constructor(props: IOneDriveFinderProps, state: IOneDriveFinderState) {
|
||||
super(props);
|
||||
|
||||
// Initialize the state of the component
|
||||
this.state = {
|
||||
breadcrumbItem: [],
|
||||
pageSize: 50,
|
||||
siteID: "",
|
||||
siteItems: [],
|
||||
itemID: ""
|
||||
};
|
||||
this.getDomainData();
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IOneDriveFinderProps> {
|
||||
const { siteID, itemID, pageSize, breadcrumbItem, siteItems, } = this.state;
|
||||
this._itemID = itemID;
|
||||
this._siteID = siteID;
|
||||
this._breadcrumbItem = breadcrumbItem;
|
||||
return (
|
||||
<div>
|
||||
<div className={styles['some-page-wrapper']}>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.column}>
|
||||
<Dropdown
|
||||
placeholder="Select an Site"
|
||||
label="List of Drives"
|
||||
ariaLabel="Custom dropdown example"
|
||||
onRenderPlaceholder={onRenderPlaceholder}
|
||||
onRenderOption={onRenderOption}
|
||||
onRenderTitle={onRenderTitle}
|
||||
onRenderCaretDown={onRenderCaretDown}
|
||||
styles={dropdownStyles}
|
||||
options={siteItems}
|
||||
onChange={(e, selectedOption) => {
|
||||
if (selectedOption.data.root == undefined) {
|
||||
this._siteID = selectedOption.key.toString();
|
||||
this.setState({
|
||||
siteID: this._siteID,
|
||||
breadcrumbItem: this._breadcrumbItem
|
||||
});
|
||||
} else {
|
||||
this._siteID = "";
|
||||
this.setState({
|
||||
siteID: this._siteID,
|
||||
itemID: "",
|
||||
breadcrumbItem: this._breadcrumbItem
|
||||
});
|
||||
}
|
||||
this.getDrives(selectedOption);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.column}>
|
||||
<Dropdown
|
||||
placeholder="50 Items"
|
||||
label="Filter Items"
|
||||
defaultValue={'50 Items'}
|
||||
styles={dropdownFilterStyles}
|
||||
options={[
|
||||
{ key: 5, text: '5 Items' },
|
||||
{ key: 10, text: '10 Items' },
|
||||
{ key: 50, text: '50 Items' },
|
||||
{ key: 100, text: '100 Items' },
|
||||
{ key: 500, text: '500 Items' },
|
||||
{ key: 1000, text: '1000 Items' },
|
||||
]}
|
||||
onChange={(e, selectedOption) => {
|
||||
let _pageSize: number = +selectedOption.key;
|
||||
this._pageSize = _pageSize;
|
||||
this.setState({
|
||||
pageSize: _pageSize
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Breadcrumb
|
||||
items={this._breadcrumbItem}
|
||||
maxDisplayedItems={10}
|
||||
ariaLabel="Breadcrumb with items rendered as buttons"
|
||||
overflowAriaLabel="More links"
|
||||
/>
|
||||
{(this.state.itemID != "" || this.state.itemID != "") &&
|
||||
<FileList
|
||||
pageSize={pageSize}
|
||||
siteId={this._siteID}
|
||||
itemId={this._itemID}
|
||||
itemClick={this.manageFolder}
|
||||
></FileList>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
* Capture file or folder and manages breadcrumb
|
||||
*/
|
||||
private manageFolder = (e: any) => {
|
||||
if (e.detail.folder != undefined) {
|
||||
this._breadcrumbItem.push({
|
||||
text: e.detail.name,
|
||||
key: e.detail.id,
|
||||
onClick: (event, item) => {
|
||||
let _cleanBreadcrumbItems: IBreadcrumbItem[] = [];
|
||||
var i = 0;
|
||||
this._breadcrumbItem.some((value) => {
|
||||
if (i == 0) {
|
||||
_cleanBreadcrumbItems.push(value);
|
||||
if (value.key === item.key) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
});
|
||||
this._itemID = e.detail.id;
|
||||
this.setState({
|
||||
itemID: item.key,
|
||||
breadcrumbItem: _cleanBreadcrumbItems
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
itemID: e.detail.id,
|
||||
breadcrumbItem: this._breadcrumbItem
|
||||
});
|
||||
} else {
|
||||
window.open(e.detail.webUrl, '_blank');
|
||||
}
|
||||
}
|
||||
private getRootDriveFolderID = async (siteID) => {
|
||||
let graphData: any = await this.getGraphContent("https://graph.microsoft.com/v1.0/sites/" + siteID + "/drive/root://:/?$select=id");
|
||||
return graphData.id;
|
||||
}
|
||||
|
||||
private getOneDriveRootFolderID = async (key) => {
|
||||
let graphData: any = await this.getGraphContent("https://graph.microsoft.com/v1.0/me/drive/items/" + key + "?$select=id");
|
||||
return graphData.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Folder item ID from Site or OneDrive
|
||||
*/
|
||||
private getDrives = async (selectedOption: IDropdownOption) => {
|
||||
let itemID: any;
|
||||
if (selectedOption.data.root != undefined) {
|
||||
itemID = await this.getOneDriveRootFolderID(selectedOption.key)
|
||||
} else {
|
||||
itemID = await this.getRootDriveFolderID(selectedOption.key)
|
||||
}
|
||||
|
||||
this._breadcrumbItem = [];
|
||||
this._breadcrumbItem.push({
|
||||
text: selectedOption.text,
|
||||
key: itemID,
|
||||
onClick: (e, item) => {
|
||||
let _cleanBreadcrumbItems: IBreadcrumbItem[] = [];
|
||||
var i = 0;
|
||||
this._breadcrumbItem.some((value) => {
|
||||
if (i == 0) {
|
||||
_cleanBreadcrumbItems.push(value);
|
||||
if (value.key === item.key) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
});
|
||||
this._itemID = itemID;
|
||||
this.setState({
|
||||
itemID: itemID,
|
||||
breadcrumbItem: _cleanBreadcrumbItems
|
||||
});
|
||||
}
|
||||
});
|
||||
this.setState(
|
||||
{
|
||||
breadcrumbItem: this._breadcrumbItem,
|
||||
itemID: itemID
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Retrieve domain name
|
||||
*/
|
||||
private getDomainData = async () => {
|
||||
let graphData: any = await this.getGraphContent("https://graph.microsoft.com/v1.0/sites/root?$select=siteCollection");
|
||||
this.getSiteData(graphData);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param DomainData
|
||||
* Retrieves sites from domain
|
||||
*/
|
||||
private getSiteData = async (DomainData) => {
|
||||
|
||||
let MyDriveData: any = await this.getGraphContent("https://graph.microsoft.com/v1.0/me/drive/root/?$Select=id,name,displayName,webUrl");
|
||||
|
||||
let graphData: any = await this.getGraphContent("https://graph.microsoft.com/v1.0/sites?search=" + escape(DomainData.siteCollection.hostname.split(".")[0]) + ".sharepoint&$Select=id,name,displayName,webUrl");
|
||||
var sharedSitesOptions: Array<IDropdownOption> = new Array<IDropdownOption>();
|
||||
|
||||
// Map the JSON response to the output array
|
||||
graphData.value.map((item: any) => {
|
||||
|
||||
sharedSitesOptions.push({
|
||||
key: item.id,
|
||||
text: item.displayName,
|
||||
data: { icon: 'Globe', webUrl: item.webUrl.split("sharepoint.com")[1] }
|
||||
});
|
||||
});
|
||||
|
||||
//Sort by Web url
|
||||
sharedSitesOptions = sharedSitesOptions.sort((Option1, Option2) => {
|
||||
if (Option1.data.webUrl > Option2.data.webUrl) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (Option1.data.webUrl < Option2.data.webUrl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
//OneDrive Folder is added as First on List
|
||||
sharedSitesOptions.unshift({
|
||||
key: MyDriveData.id,
|
||||
text: MyDriveData.name,
|
||||
data: { icon: 'OneDriveFolder16', webUrl: MyDriveData.webUrl.split("sharepoint.com")[1], root: true }
|
||||
});
|
||||
|
||||
// Update the component state accordingly to the result
|
||||
this.setState(
|
||||
{
|
||||
siteItems: sharedSitesOptions,
|
||||
}
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Method to Connect Graph
|
||||
*/
|
||||
private getGraphContent = (graphQuery: string) => {
|
||||
|
||||
// Using Graph here, but any 1st or 3rd party REST API that requires Azure AD auth can be used here.
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.props.context.aadHttpClientFactory
|
||||
.getClient("https://graph.microsoft.com")
|
||||
.then((client: AadHttpClient) => {
|
||||
// Querys to Graph base on url
|
||||
return client
|
||||
.get(
|
||||
`${graphQuery}`,
|
||||
AadHttpClient.configurations.v1
|
||||
);
|
||||
})
|
||||
.then(response => {
|
||||
return response.json();
|
||||
})
|
||||
.then(json => {
|
||||
resolve(json);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
10
samples/react-onedrive-finder/src/webparts/oneDriveFinder/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare interface IOneDriveFinderWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'OneDriveFinderWebPartStrings' {
|
||||
const strings: IOneDriveFinderWebPartStrings;
|
||||
export = strings;
|
||||
}
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 383 B |
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.7/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
|
@ -147,8 +147,14 @@ By default you have to install this web part per site collection where you want
|
|||
|
||||
In order to make it available to absolutely all sites you need apply the _Deploy to non-script sites / modern team site_ change as well.
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/drop-1.10.0-green.svg)
|
||||
|
||||
## Compatibility
|
||||
|
||||
![SPFx 1.10](https://img.shields.io/badge/SPFx-1.10.0-green.svg)
|
||||
![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x%20%7C%20LTS%208.x-green.svg)
|
||||
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
|
||||
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
|
||||
![Workbench Local | Hosted](https://img.shields.io/badge/Workbench-Local%20%7C%20Hosted-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
|
|
|
@ -88,4 +88,4 @@ Version|Date|Comments
|
|||
- [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/publish-to-marketplace-overview)
|
||||
- [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-web parts/samples/react-content-query-online" />
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-content-query-online" />
|
||||
|
|
|
@ -31,7 +31,7 @@ This web part shows the current user's colleagues, and allows the user to search
|
|||
![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
|
||||
![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg)
|
||||
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
|
||||
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
|
||||
![Teams Yes: Designed for Microsoft Teams](https://img.shields.io/badge/Teams-Yes-green.svg "Designed for Microsoft Teams")
|
||||
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
|
||||
|
||||
## Applies to
|
||||
|
@ -45,6 +45,8 @@ Solution|Author(s)
|
|||
--------|---------
|
||||
react-staffdirectory|Ari Gunawan ([@arigunawan3023](https://twitter.com/arigunawan3023))
|
||||
react-staffdirectory|João Mendes ([joaojmendes](https://github.com/joaojmendes))
|
||||
react-staffdirectory|[Tristian O'brien](https://github.com/tristian2)
|
||||
|
||||
|
||||
|
||||
## Version history
|
||||
|
@ -53,6 +55,7 @@ Version|Date|Comments
|
|||
-------|----|--------
|
||||
1.0.0|February 16, 2021|Initial release
|
||||
1.0.1|March 28, 2021|Added missing Graph API Permission (User.Read.All) for getting users information
|
||||
1.0.2|April 14, 2021|Added About Me and Skills
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"This web part shows the current user\u0027s colleagues, and allows the user to search AD directory, The user can configure the properties to show when expand the user card."
|
||||
],
|
||||
"created": "2021-03-09",
|
||||
"modified": "2021-03-28",
|
||||
"modified": "2021-04-14",
|
||||
"products": [
|
||||
"SharePoint",
|
||||
"Office"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"solution": {
|
||||
"name": "staff-directory-client-side-solution",
|
||||
"id": "89d7389c-be48-41e9-9f72-5eb9a1099c1f",
|
||||
"version": "1.0.1.0",
|
||||
"version": "1.0.2.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "staff-directory",
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.2",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
|
|
|
@ -19,7 +19,6 @@ import { IUserExtended } from "../../entites/IUserExtended";
|
|||
import { IAppContext } from "../../common/IAppContext";
|
||||
import { IUserCardProps } from "./IUserCardProps";
|
||||
|
||||
|
||||
const teamsDefaultTheme = require("../../common/TeamsDefaultTheme.json");
|
||||
const teamsDarkTheme = require("../../common/TeamsDarkTheme.json");
|
||||
const teamsContrastTheme = require("../../common/TeamsContrastTheme.json");
|
||||
|
@ -97,6 +96,7 @@ export const UserCard = (props: IUserCardProps) => {
|
|||
}
|
||||
};
|
||||
|
||||
//tris added onclick event
|
||||
const _onRenderPrimaryText = (persona: IPersonaProps) => {
|
||||
return (
|
||||
<>
|
||||
|
@ -108,7 +108,12 @@ export const UserCard = (props: IUserCardProps) => {
|
|||
root: { justifyContent: "flex-start", width: "100%" },
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
<Text onClick={
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
window.open("https://gbr.delve.office.com/?u=" + userData.id + "&v=work", "_blank");
|
||||
}
|
||||
}
|
||||
variant="medium"
|
||||
block
|
||||
nowrap
|
||||
|
@ -116,8 +121,9 @@ export const UserCard = (props: IUserCardProps) => {
|
|||
width: "100%",
|
||||
fontWeight: 600,
|
||||
padding: 0,
|
||||
marginBottom: 3,
|
||||
marginBottom: 3
|
||||
}}
|
||||
title={persona.text}
|
||||
>
|
||||
{persona.text}
|
||||
</Text>
|
||||
|
@ -130,6 +136,7 @@ export const UserCard = (props: IUserCardProps) => {
|
|||
>
|
||||
<div style={{ fontSize: 12 }}>
|
||||
<ActionButton
|
||||
|
||||
styles={{
|
||||
root: {
|
||||
height: 21,
|
||||
|
@ -184,11 +191,19 @@ export const UserCard = (props: IUserCardProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
|
||||
//tris added onclick event
|
||||
const _onRenderSecondaryText = (persona: IPersonaProps) => {
|
||||
return (
|
||||
<>
|
||||
<Stack verticalAlign="start" tokens={{ childrenGap: 0 }}>
|
||||
<Text title={persona.secondaryText} variant="medium" block nowrap>
|
||||
<Text onClick={
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
window.open("https://gbr.delve.office.com/?u=" + userData.id + "&v=work", "_blank");
|
||||
}
|
||||
}
|
||||
title={persona.secondaryText} variant="medium" block nowrap>
|
||||
{" "}
|
||||
{persona.secondaryText}
|
||||
</Text>
|
||||
|
@ -221,11 +236,11 @@ export const UserCard = (props: IUserCardProps) => {
|
|||
}
|
||||
text={userData.displayName}
|
||||
title={userData.displayName}
|
||||
tertiaryText={userData.mail}
|
||||
tertiaryText={userData.mail}
|
||||
secondaryText={userData.jobTitle}
|
||||
onRenderPrimaryText={_onRenderPrimaryText}
|
||||
onRenderSecondaryText={_onRenderSecondaryText}
|
||||
></Persona>
|
||||
></Persona>
|
||||
</Stack>
|
||||
{isDetailsOpen && (
|
||||
<>
|
||||
|
@ -707,8 +722,82 @@ export const UserCard = (props: IUserCardProps) => {
|
|||
<div className={styleClasses.separator}></div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "aboutMe":
|
||||
return (
|
||||
<div
|
||||
className={`${styleClasses.styleField}`}
|
||||
title={userData.aboutMe ? userData.aboutMe.replace(/<[^>]+>/g, '').replace(/ /gi,' ') : "Not available"}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
tokens={{ childrenGap: 5 }}
|
||||
style={{ marginRight: 20, marginLeft: 20 }}
|
||||
>
|
||||
<FontIcon
|
||||
className={styleClasses.styleIconDetails}
|
||||
iconName="Medal"
|
||||
/>
|
||||
<Label className={styleClasses.styleFieldLabel}>
|
||||
About Me
|
||||
</Label>
|
||||
</Stack>
|
||||
<Text
|
||||
styles={styleTextField}
|
||||
variant="medium"
|
||||
block={true}
|
||||
nowrap={true}
|
||||
>
|
||||
{userData.aboutMe ? userData.aboutMe.replace(/<[^>]+>/g, '').replace(/ /gi,' ') : "Not available"}
|
||||
</Text>
|
||||
<div className={styleClasses.separator}></div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case "skills":
|
||||
return (
|
||||
<div
|
||||
className={`${styleClasses.styleField}`}
|
||||
title={
|
||||
userData.skills.join(",")
|
||||
? userData.skills.join(",")
|
||||
: "Not available"
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
tokens={{ childrenGap: 5 }}
|
||||
style={{ marginRight: 20, marginLeft: 20 }}
|
||||
>
|
||||
<FontIcon
|
||||
className={styleClasses.styleIconDetails}
|
||||
iconName="Education"
|
||||
/>
|
||||
<Label className={styleClasses.styleFieldLabel}>
|
||||
Skills
|
||||
</Label>
|
||||
</Stack>
|
||||
<Text
|
||||
styles={styleTextField}
|
||||
variant="medium"
|
||||
block={true}
|
||||
nowrap={true}
|
||||
>
|
||||
{userData.skills.join(",") ? (
|
||||
<>
|
||||
{userData.skills.join(",")}
|
||||
</>
|
||||
) : (
|
||||
"Not available"
|
||||
)}
|
||||
</Text>
|
||||
<div className={styleClasses.separator}></div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
})}
|
||||
</Stack>
|
||||
</>
|
||||
|
|
|
@ -15,5 +15,4 @@ export interface IUser {
|
|||
officeLocation: string;
|
||||
postalCode: string;
|
||||
userType: string;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export interface IUserBio {
|
||||
id?: string;
|
||||
aboutMe: string;
|
||||
skills: string[];
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { IUser } from "./IUser";
|
||||
import { IUserPresence } from "./IUserPresence";
|
||||
export interface IUserExtended extends IUser, IUserPresence {
|
||||
import { IUserBio } from "./IUserBio";
|
||||
export interface IUserExtended extends IUser, IUserPresence, IUserBio {
|
||||
count: number;
|
||||
pictureBase64: string;
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@ import "@pnp/graph/users";
|
|||
import { IUserExtended } from "../entites/IUserExtended";
|
||||
import { IUser } from "../entites/IUser";
|
||||
import { IUserPresence } from "../entites/IUserPresence";
|
||||
import { IUserBio } from "../entites/IUserBio";
|
||||
import { SPComponentLoader } from "@microsoft/sp-loader";
|
||||
import { findIndex } from "lodash";
|
||||
import { findIndex, join, values } from "lodash";
|
||||
import { DetailsRow } from "office-ui-fabric-react";
|
||||
|
||||
/*************************************************************************************/
|
||||
// Hook to search users
|
||||
|
@ -22,6 +24,7 @@ export const useSearchUsers = async (
|
|||
pageSize?: number
|
||||
): Promise<{ usersExtended: IUserExtended[]; nextPage: string }> => {
|
||||
pageSize = pageSize ? pageSize : 5;
|
||||
|
||||
const _searchResults: any = await _MSGraphClient
|
||||
.api('/users?$search="' + searchString + '"')
|
||||
.version("beta")
|
||||
|
@ -40,8 +43,11 @@ export const useSearchUsers = async (
|
|||
for (const _user of _users) {
|
||||
const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
|
||||
const _pictureBase64: string = await getUserPhoto(_user.mail);
|
||||
const _userBio = await getUserBio(_user.id, _MSGraphClient);
|
||||
|
||||
_usersExtended.push({
|
||||
..._user,
|
||||
..._userBio,
|
||||
..._userPresence,
|
||||
pictureBase64: _pictureBase64,
|
||||
count: 0,
|
||||
|
@ -77,14 +83,18 @@ export const useGetUsersByDepartment = async (
|
|||
.count(true)
|
||||
.get();
|
||||
|
||||
|
||||
const _users: IUser[] = _searchResults.value;
|
||||
let _usersExtended: IUserExtended[] = [];
|
||||
|
||||
for (const _user of _users) {
|
||||
const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
|
||||
const _pictureBase64: string = await getUserPhoto(_user.mail);
|
||||
const _userBio = await getUserBio(_user.id, _MSGraphClient);
|
||||
|
||||
_usersExtended.push({
|
||||
..._user,
|
||||
..._userBio,
|
||||
..._userPresence,
|
||||
pictureBase64: _pictureBase64,
|
||||
count: 0,
|
||||
|
@ -119,8 +129,10 @@ export const useGetUsersNextPage = async (
|
|||
for (const _user of _users) {
|
||||
const _userPresence = await getUserPresence(_user.id, _MSGraphClient);
|
||||
const _pictureBase64: string = await getUserPhoto(_user.mail);
|
||||
const _userBio = await getUserBio(_user.id, _MSGraphClient);
|
||||
_usersExtended.push({
|
||||
..._user,
|
||||
..._userBio,
|
||||
..._userPresence,
|
||||
pictureBase64: _pictureBase64,
|
||||
count: 0,
|
||||
|
@ -185,6 +197,21 @@ const getUserPresence = async (
|
|||
return _presence;
|
||||
};
|
||||
|
||||
//*************************************************************************************//
|
||||
// function Get Users About Me and skillz
|
||||
//*************************************************************************************//
|
||||
|
||||
const getUserBio = async (
|
||||
userObjId,
|
||||
_MSGraphClient
|
||||
): Promise<IUserBio> => {
|
||||
let _bio : IUserBio = await _MSGraphClient
|
||||
.api("/users/{" + userObjId + "}?$select=aboutMe,skills")
|
||||
.version("beta")
|
||||
.get();
|
||||
return _bio;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets user photo
|
||||
* @param userId
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "SPFx Custom Web Parts" },
|
||||
"title": { "default": "Search Directory" },
|
||||
"description": { "default": "Search Directory" },
|
||||
"title": { "default": "UoB Search Directory" },
|
||||
"description": { "default": "University of Brighton Search Directory" },
|
||||
"officeFabricIconFontName": "ProfileSearch",
|
||||
"properties": {
|
||||
"title": "Search Directory",
|
||||
"title": "UoB Search Directory",
|
||||
"maxHeight": 700,
|
||||
"showBox": true,
|
||||
"refreshInterval": 3,
|
||||
|
|
|
@ -42,8 +42,6 @@ export default class StaffDirectoryWebPart extends BaseClientSideWebPart<IStaffD
|
|||
|
||||
protected async onInit(): Promise<void> {
|
||||
|
||||
|
||||
|
||||
this._themeProvider = this.context.serviceScope.consume(
|
||||
ThemeProvider.serviceKey
|
||||
);
|
||||
|
@ -189,7 +187,6 @@ export default class StaffDirectoryWebPart extends BaseClientSideWebPart<IStaffD
|
|||
PropertyFieldMultiSelect('userAttributes', {
|
||||
key: 'userAttributes',
|
||||
label: strings.UserAttributesLabel,
|
||||
|
||||
options: [
|
||||
{
|
||||
key: "company",
|
||||
|
@ -223,6 +220,14 @@ export default class StaffDirectoryWebPart extends BaseClientSideWebPart<IStaffD
|
|||
key: "userType",
|
||||
text: "User Type"
|
||||
},
|
||||
{
|
||||
key: "aboutMe",
|
||||
text: "About Me"
|
||||
},
|
||||
{
|
||||
key: "skills",
|
||||
text: "Skills"
|
||||
},
|
||||
],
|
||||
selectedKeys: this.properties.userAttributes
|
||||
}),
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
"longDescription": [
|
||||
"TODO"
|
||||
],
|
||||
"created": "2021-04-02",
|
||||
"modified": "2021-04-02",
|
||||
"created": "2020-04-27",
|
||||
"modified": "2021-04-20",
|
||||
"products": [
|
||||
"SharePoint",
|
||||
"Office"
|
||||
|
|