commit new sample react-documents-links-accordion
This commit is contained in:
parent
1911f8a5c8
commit
d7c31a3938
|
@ -0,0 +1,33 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
release
|
||||
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.12.1",
|
||||
"libraryName": "react-menu-accordion",
|
||||
"libraryId": "f7134ff1-6e97-430c-9a73-5dc4902c75e3",
|
||||
"packageManager": "npm",
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
# Documents Links Accordion
|
||||
|
||||
## Summary
|
||||
|
||||
This web part allows user create a accordion with documents links grouped by any column of document library.
|
||||
When the user clicks on the header it dynamically load documents.
|
||||
|
||||
![documentsLinksAccordion](./assets/documentsLinksAccordion.gif)
|
||||
|
||||
## Screenshots
|
||||
|
||||
![documentsLinksAccordion](./assets/documentsLinksAccordion1.png)
|
||||
|
||||
|
||||
|
||||
## Compatibility
|
||||
|
||||
![SPFx 1.12.1](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
|
||||
![Node.js LTS 12.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 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
|
||||
|
||||
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
|
||||
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
|
||||
|
||||
## WebPart Properties
|
||||
|
||||
Property |Type|Required| comments
|
||||
--------------------|----|--------|----------
|
||||
WebPart Title| Text| no|
|
||||
Select Document Library| dropdown|yes
|
||||
Select Field to Group By | dropdown|yes
|
||||
|
||||
|
||||
## Solution
|
||||
|
||||
The Web Part Use PnPjs library, Fluent-Ui-react components
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
React Documents Links Accordion |[João Mendes](https://github.com/joaojmendes) ([@joaojmendes](https://twitter.com/joaojmendes))
|
||||
|
||||
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0.0|October 10, 2021|Initial release
|
||||
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Clone this repository
|
||||
- Move to sample folder
|
||||
- in the command line run:
|
||||
- `npm install`
|
||||
- `gulp build`
|
||||
- `gulp bundle --ship`
|
||||
- `gulp package-solution --ship`
|
||||
- Add to AppCatalog and deploy
|
||||
|
||||
|
||||
## 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.**
|
||||
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
We do not support samples, but we do use GitHub to track issues and constantly want to improve these samples.
|
||||
|
||||
If you encounter any issues while using this sample, [create a new issue](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=bug-report.yml&sample=react-list-items-menu&authors=@joaojmendes%20@Ravikadri&title=react-list-items-menu%20-%20).
|
||||
|
||||
For questions regarding this sample, [create a new question](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=question.yml&sample=react-list-items-menu&authors=@joaojmendes%20@Ravikadri&title=react-list-items-menu%20-%20).
|
||||
|
||||
Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=suggestion.yml&sample=react-list-items-menu&authors=@joaojmendes%20@Ravikadri&title=react-list-items-menu%20-%20).
|
||||
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-documents-links-accordion" />
|
Binary file not shown.
After Width: | Height: | Size: 3.1 MiB |
Binary file not shown.
After Width: | Height: | Size: 460 KiB |
|
@ -0,0 +1,92 @@
|
|||
[
|
||||
{
|
||||
"name": "pnp-sp-dev-spfx-web-parts-react-list-items-menu",
|
||||
"source": "pnp",
|
||||
"title": "List Items Menu",
|
||||
"shortDescription": "Allows user create a navigation menu , grouped by any column of document library. When the user clicks on the header it dynamically load documents.",
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-list-items-menu",
|
||||
"longDescription": [
|
||||
"Allows user create a navigation menu , grouped by any column of document library. When the user clicks on the header it dynamically load documents."
|
||||
],
|
||||
"creationDateTime": "2021-02-18",
|
||||
"updateDateTime": "2021-02-18",
|
||||
"products": [
|
||||
"SharePoint",
|
||||
"Office"
|
||||
],
|
||||
"metadata": [
|
||||
{
|
||||
"key": "CLIENT-SIDE-DEV",
|
||||
"value": "React"
|
||||
},
|
||||
{
|
||||
"key": "SPFX-VERSION",
|
||||
"value": "1.11.0"
|
||||
},
|
||||
{
|
||||
"key": "SPFX-SUPPORTSTHEMEVARIANTS",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "SPFX-TEAMSTAB",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "SPFX-TEAMSPERSONALAPP",
|
||||
"value": "true"
|
||||
}
|
||||
],
|
||||
"thumbnails": [
|
||||
{
|
||||
"type": "image",
|
||||
"order": 100,
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-list-items-menu/assets/ListMenuDocs.gif",
|
||||
"alt": "List Items Menu"
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"order": 101,
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-list-items-menu/assets/reactListItems1.JPG?raw=true",
|
||||
"alt": "List Items Menu"
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"order": 102,
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-list-items-menu/assets/reactListItems2.JPG?raw=true",
|
||||
"alt": "List Items Menu"
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"order": 103,
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-list-items-menu/assets/reactListItems3.JPG?raw=true",
|
||||
"alt": "List Items Menu"
|
||||
}
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"gitHubAccount": "joaojmendes",
|
||||
"company": "Storm Technology Ltd",
|
||||
"pictureUrl": "https://github.com/joaojmendes.png",
|
||||
"name": "Jo\u00E3o Mendes",
|
||||
"twitter": "joaojmendes"
|
||||
}
|
||||
],
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"name": "Supporting section backgrounds",
|
||||
"description": "Starting with SharePoint Framework v1.8, web parts can be made aware of any section backgrounds and use these colors to improve the appearance of a web part when hosted in a section with a different background.",
|
||||
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/supporting-section-backgrounds"
|
||||
},
|
||||
{
|
||||
"name": "Building Microsoft Teams Tabs using SharePoint Framework",
|
||||
"description": "Starting with SharePoint Framework v1.8, you can build tabs for Microsoft Teams with the SharePoint Framework tooling and use SharePoint as a host for your solutions. As part of the SharePoint Framework v1.10 you can also publish your solution as Microsoft Teams personal app.",
|
||||
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/integrate-with-teams-introduction"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"documentslinksaccordion-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/DocumentsLinksAccordion/DocumentsLinksAccordionWebPart.js",
|
||||
"manifest": "./src/webparts/DocumentsLinksAccordion/DocumentsLinksAccordionWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"DocumentsLinksAccordionWebPartStrings": "lib/webparts/DocumentsLinksAccordion/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
|
||||
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "./release/assets/"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./release/assets/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-menu-accordion",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-documentslinks-accordion",
|
||||
"id": "f7134ff1-6e97-430c-9a73-5dc4902c75e3",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false,
|
||||
"developer": {
|
||||
"name": "",
|
||||
"websiteUrl": "",
|
||||
"privacyUrl": "",
|
||||
"termsOfUseUrl": "",
|
||||
"mpnId": ""
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-documentslinks-accordion.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,16 @@
|
|||
'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.`);
|
||||
|
||||
var getTasks = build.rig.getTasks;
|
||||
build.rig.getTasks = function () {
|
||||
var result = getTasks.call(build.rig);
|
||||
|
||||
result.set('serve', result.get('serve-deprecated'));
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
build.initialize(require('gulp'));
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "react-documentslinks-accordion",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.12.1",
|
||||
"@microsoft/sp-lodash-subset": "1.12.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.12.1",
|
||||
"@microsoft/sp-property-pane": "1.12.1",
|
||||
"@microsoft/sp-webpart-base": "1.12.1",
|
||||
"@pnp/spfx-controls-react": "^3.3.0",
|
||||
"@pnp/spfx-property-controls": "^3.2.0",
|
||||
"@uifabric/file-type-icons": "^7.8.1",
|
||||
"date-fns": "^2.25.0",
|
||||
"office-ui-fabric-react": "7.156.0",
|
||||
"react": "16.9.0",
|
||||
"react-dom": "16.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "16.9.36",
|
||||
"@types/react-dom": "16.9.8",
|
||||
"@microsoft/sp-build-web": "1.12.1",
|
||||
"@microsoft/sp-tslint-rules": "1.12.1",
|
||||
"@microsoft/sp-module-interfaces": "1.12.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.12.1",
|
||||
"@microsoft/rush-stack-compiler-3.7": "0.2.3",
|
||||
"gulp": "~4.0.2",
|
||||
"ajv": "~5.2.2",
|
||||
"@types/webpack-env": "1.13.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
import { SPComponentLoader } from "@microsoft/sp-loader";
|
||||
import { sp } from "@pnp/sp";
|
||||
import "@pnp/sp/webs";
|
||||
import "@pnp/sp/regional-settings/web";
|
||||
import { IRegionalSettingsInfo } from "@pnp/sp/regional-settings";
|
||||
|
||||
// get all the web's regional settings
|
||||
|
||||
const DEFAULT_PERSONA_IMG_HASH: string = "7ad602295f8386b7615b582d87bcc294";
|
||||
const DEFAULT_IMAGE_PLACEHOLDER_HASH: string = "4a48f26592f4e1498d7a478a4c48609c";
|
||||
const MD5_MODULE_ID: string = "8494e7d7-6b99-47b2-a741-59873e42f16f";
|
||||
const PROFILE_IMAGE_URL: string = "/_layouts/15/userphoto.aspx?size=M&accountname=";
|
||||
|
||||
/**
|
||||
* Gets user photo
|
||||
* @param userId
|
||||
* @returns user photo
|
||||
*/
|
||||
export const getUserPhoto = async (userId): Promise<string> => {
|
||||
const personaImgUrl = PROFILE_IMAGE_URL + userId;
|
||||
console.log(personaImgUrl);
|
||||
// tslint:disable-next-line: no-use-before-declare
|
||||
const url: string = await getImageBase64(personaImgUrl);
|
||||
// tslint:disable-next-line: no-use-before-declare
|
||||
const newHash = await getMd5HashForUrl(url);
|
||||
|
||||
if (newHash !== DEFAULT_PERSONA_IMG_HASH && newHash !== DEFAULT_IMAGE_PLACEHOLDER_HASH) {
|
||||
return "data:image/png;base64," + url;
|
||||
} else {
|
||||
return "undefined";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get MD5Hash for the image url to verify whether user has default image or custom image
|
||||
* @param url
|
||||
*/
|
||||
export const getMd5HashForUrl = async (url: string) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// tslint:disable-next-line: no-use-before-declare
|
||||
const library: any = await loadSPComponentById(MD5_MODULE_ID);
|
||||
try {
|
||||
const md5Hash = library.Md5Hash;
|
||||
if (md5Hash) {
|
||||
const convertedHash = md5Hash(url);
|
||||
resolve(convertedHash);
|
||||
}
|
||||
} catch (error) {
|
||||
resolve(url);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Load SPFx component by id, SPComponentLoader is used to load the SPFx components
|
||||
* @param componentId - componentId, guid of the component library
|
||||
*/
|
||||
export const loadSPComponentById = async (componentId: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
SPComponentLoader.loadComponentById(componentId)
|
||||
.then((component: any) => {
|
||||
resolve(component);
|
||||
})
|
||||
.catch((error) => {});
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Gets image base64
|
||||
* @param pictureUrl
|
||||
* @returns image base64
|
||||
*/
|
||||
export const getImageBase64 = async (pictureUrl: string): Promise<string> => {
|
||||
console.log(pictureUrl);
|
||||
return new Promise((resolve, reject) => {
|
||||
let image = new Image();
|
||||
image.addEventListener("load", () => {
|
||||
let tempCanvas = document.createElement("canvas");
|
||||
(tempCanvas.width = image.width),
|
||||
(tempCanvas.height = image.height),
|
||||
tempCanvas.getContext("2d").drawImage(image, 0, 0);
|
||||
let base64Str;
|
||||
try {
|
||||
base64Str = tempCanvas.toDataURL("image/png");
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
base64Str = base64Str.replace(/^data:image\/png;base64,/, "");
|
||||
resolve(base64Str);
|
||||
});
|
||||
image.src = pictureUrl;
|
||||
});
|
||||
};
|
||||
|
||||
export const zeroPad = (num, places) => {
|
||||
var zero = places - num.toString().length + 1;
|
||||
return Array(+(zero > 0 && zero)).join("0") + num;
|
||||
};
|
||||
|
||||
export const getSiteRegionalSettings = async (): Promise<IRegionalSettingsInfo> => {
|
||||
try {
|
||||
const s = await sp.web.regionalSettings();
|
||||
return s;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const convertTimeTo12H = (
|
||||
_hour: number
|
||||
): Promise<{ hourInTimeFormat: string; current12HTimeFormat: string }> => {
|
||||
console.log("prm", _hour);
|
||||
return new Promise((resolve, rejected) => {
|
||||
let hourInTimeFormat: string = "";
|
||||
let current12HTimeFormat: string = "AM";
|
||||
if (_hour >= 0 && _hour <= 11) {
|
||||
if (_hour === 0) {
|
||||
hourInTimeFormat = "12";
|
||||
} else {
|
||||
hourInTimeFormat = _hour.toString();
|
||||
}
|
||||
current12HTimeFormat = "AM";
|
||||
} else {
|
||||
const _hour12h = _hour - 12;
|
||||
hourInTimeFormat = _hour12h === 0 ? "12" : _hour12h.toString();
|
||||
current12HTimeFormat = "PM";
|
||||
}
|
||||
resolve({ hourInTimeFormat, current12HTimeFormat });
|
||||
});
|
||||
};
|
||||
|
||||
export const convertTimeTo24h = (hour: number, current12HTimeFormat: string): Promise<string> => {
|
||||
return new Promise((resolve, rejected) => {
|
||||
let hourInTimeFormat: string = "";
|
||||
if (current12HTimeFormat === "PM") {
|
||||
if (hour >= 1 && hour <= 11) {
|
||||
if (hour === 12) {
|
||||
hourInTimeFormat = "12";
|
||||
}
|
||||
const _hour24h = hour + 12;
|
||||
hourInTimeFormat = _hour24h.toString();
|
||||
}
|
||||
} else {
|
||||
if (hour === 12) {
|
||||
hourInTimeFormat = "00";
|
||||
} else {
|
||||
hourInTimeFormat = hour.toString();
|
||||
}
|
||||
}
|
||||
resolve(hourInTimeFormat);
|
||||
});
|
||||
};
|
||||
|
||||
/* Check if string is valid date */
|
||||
export const checkIfValidDate = (str:string):boolean => {
|
||||
// Regular expression to check if string is valid date
|
||||
const regexExp = /(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})/gi;
|
||||
|
||||
return regexExp.test(str);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
import * as React from "react";
|
||||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
import { MSGraphClient, AadTokenProvider } from "@microsoft/sp-http";
|
||||
import { IReadonlyTheme } from '@microsoft/sp-component-base';
|
||||
import { IRegionalSettingsInfo} from "@pnp/sp/regional-settings";
|
||||
|
||||
|
||||
export interface IAppContextProps {
|
||||
currentUser:string;
|
||||
msGraphClient:MSGraphClient;
|
||||
locale:string;
|
||||
themeVariant: IReadonlyTheme | undefined;
|
||||
}
|
||||
|
||||
export const AppContext = React.createContext<IAppContextProps>(undefined);
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"themePrimary": "#6264a7",
|
||||
"themeLighterAlt": "#f7f7fb",
|
||||
"themeLighter": "#e1e1f1",
|
||||
"themeLight": "#c8c9e4",
|
||||
"themeTertiary": "#989ac9",
|
||||
"themeSecondary": "#7173b0",
|
||||
"themeDarkAlt": "#585a95",
|
||||
"themeDark": "#4a4c7e",
|
||||
"themeDarker": "#37385d",
|
||||
"neutralLighterAlt": "#0b0b0b",
|
||||
"neutralLighter": "#151515",
|
||||
"neutralLight": "#252525",
|
||||
"neutralQuaternaryAlt": "#2f2f2f",
|
||||
"neutralQuaternary": "#373737",
|
||||
"neutralTertiaryAlt": "#595959",
|
||||
"neutralTertiary": "#c8c8c8",
|
||||
"neutralSecondary": "#d0d0d0",
|
||||
"neutralPrimaryAlt": "#dadada",
|
||||
"neutralPrimary": "#ffffff",
|
||||
"neutralDark": "#f4f4f4",
|
||||
"black": "#f8f8f8",
|
||||
"white": "#000000"
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"themePrimary": "#6264a7",
|
||||
"themeLighterAlt": "#f7f7fb",
|
||||
"themeLighter": "#e1e1f1",
|
||||
"themeLight": "#c8c9e4",
|
||||
"themeTertiary": "#989ac9",
|
||||
"themeSecondary": "#7173b0",
|
||||
"themeDarkAlt": "#585a95",
|
||||
"themeDark": "#4a4c7e",
|
||||
"themeDarker": "#37385d",
|
||||
"neutralLighterAlt": "#2d2c2c",
|
||||
"neutralLighter": "#2c2b2b",
|
||||
"neutralLight": "#2a2929",
|
||||
"neutralQuaternaryAlt": "#272626",
|
||||
"neutralQuaternary": "#252525",
|
||||
"neutralTertiaryAlt": "#242323",
|
||||
"neutralTertiary": "#c8c8c8",
|
||||
"neutralSecondary": "#d0d0d0",
|
||||
"neutralPrimaryAlt": "#dadada",
|
||||
"neutralPrimary": "#ffffff",
|
||||
"neutralDark": "#f4f4f4",
|
||||
"black": "#f8f8f8",
|
||||
"white": "#2d2c2c"
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"themePrimary": "#6264a7",
|
||||
"themeLighterAlt": "#f7f7fb",
|
||||
"themeLighter": "#e1e1f1",
|
||||
"themeLight": "#c8c9e4",
|
||||
"themeTertiary": "#989ac9",
|
||||
"themeSecondary": "#7173b0",
|
||||
"themeDarkAlt": "#585a95",
|
||||
"themeDark": "#4a4c7e",
|
||||
"themeDarker": "#37385d",
|
||||
"neutralLighterAlt": "#ecebe9",
|
||||
"neutralLighter": "#e8e7e6",
|
||||
"neutralLight": "#dedddc",
|
||||
"neutralQuaternaryAlt": "#cfcecd",
|
||||
"neutralQuaternary": "#c6c5c4",
|
||||
"neutralTertiaryAlt": "#bebdbc",
|
||||
"neutralTertiary": "#b5b4b2",
|
||||
"neutralSecondary": "#9d9c9a",
|
||||
"neutralPrimaryAlt": "#868482",
|
||||
"neutralPrimary": "#252423",
|
||||
"neutralDark": "#565453",
|
||||
"black": "#3e3d3b",
|
||||
"white": "#f3f2f1"
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { IDatePickerStrings } from 'office-ui-fabric-react/lib/DatePicker';
|
||||
export const DayPickerStrings: IDatePickerStrings = {
|
||||
months: [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
],
|
||||
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
shortDays: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
|
||||
goToToday: 'Go to today',
|
||||
prevMonthAriaLabel: 'Go to previous month',
|
||||
nextMonthAriaLabel: 'Go to next month',
|
||||
prevYearAriaLabel: 'Go to previous year',
|
||||
nextYearAriaLabel: 'Go to next year',
|
||||
closeButtonAriaLabel: 'Close date picker',
|
||||
monthPickerHeaderAriaLabel: '{0}, select to change the year',
|
||||
yearPickerHeaderAriaLabel: '{0}, select to change the month',
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
$default-background: #f3f2f1;
|
||||
$default-color: #252423;
|
||||
$default-button-background: #6264a7;
|
||||
$default-Button-color: #f3f2f1;
|
||||
|
||||
// dark theme
|
||||
$dark-background: #2d2c2c;
|
||||
$dark-color: #ffffff;
|
||||
$dark-button-background: #6264a7;
|
||||
$dark-button-color: #2d2c2c;
|
||||
|
||||
// contrast theme
|
||||
$contrast-background: #000000;
|
||||
$contrast-color: #ffffff;
|
||||
$contrast-button-background: #b5c01c;
|
||||
$contrast-Button-color: #000000;
|
|
@ -0,0 +1,74 @@
|
|||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.DocumentsLinksAccordion {
|
||||
.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;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.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,356 @@
|
|||
import * as React from "react";
|
||||
import * as strings from "DocumentsLinksAccordionWebPartStrings";
|
||||
import { filter, findIndex, uniqBy } from "lodash";
|
||||
import {
|
||||
Customizer,
|
||||
INavLink,
|
||||
INavLinkGroup,
|
||||
Label,
|
||||
Link,
|
||||
mergeStyleSets,
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
Stack,
|
||||
FontIcon,
|
||||
Text,
|
||||
DetailsList,
|
||||
DetailsListLayoutMode,
|
||||
IColumn,
|
||||
SelectionMode,
|
||||
IDetailsListProps,
|
||||
IStackStyles,
|
||||
IStyle,
|
||||
IDetailsListStyles,
|
||||
} from "office-ui-fabric-react";
|
||||
|
||||
import { getFileTypeIconProps } from "@uifabric/file-type-icons";
|
||||
import { useList } from "../../hooks/useList";
|
||||
import { IDocumentsLinksAccordionState } from "./IDocumentsLinksAccordionState";
|
||||
import { IDocumentsLinksAccordionProps } from "./IDocumentsLinksAccordionProps";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionItemHeading,
|
||||
AccordionItemButton,
|
||||
AccordionItemPanel,
|
||||
} from "@pnp/spfx-controls-react/lib/AccessibleAccordion";
|
||||
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
|
||||
|
||||
const { getGroupItems, getGroupHeaders, getField } = useList();
|
||||
export const DocumentsLinksAccordion: React.FunctionComponent<IDocumentsLinksAccordionProps> = (
|
||||
props: IDocumentsLinksAccordionProps
|
||||
) => {
|
||||
const [state, setState] = React.useState<IDocumentsLinksAccordionState>({
|
||||
navLinkGroups: [],
|
||||
isLoading: false,
|
||||
hasError: false,
|
||||
errorMessage: "",
|
||||
listName: "",
|
||||
});
|
||||
|
||||
const stackItemStyles: Partial<IStackStyles> = React.useMemo(() => {
|
||||
return {
|
||||
root: {
|
||||
padding: 7,
|
||||
} as IStyle,
|
||||
};
|
||||
}, []);
|
||||
|
||||
const listViewStyles: Partial<IDetailsListStyles> = React.useMemo(() => {
|
||||
return {
|
||||
focusZone: {
|
||||
width: "auto",
|
||||
maxHeight: 450,
|
||||
overflowY: "auto",
|
||||
overflowX: "hidden",
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: props.themeVariant?.palette?.neutralLighter,
|
||||
},
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "7.5px",
|
||||
},
|
||||
"scrollbar-color": props.themeVariant?.palette?.neutralLighter,
|
||||
"scrollbar-width": "thin",
|
||||
},
|
||||
root: {
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: props.themeVariant?.palette?.neutralLighter,
|
||||
},
|
||||
"&::-webkit-scrollbar": {
|
||||
height: "7.5px",
|
||||
},
|
||||
"scrollbar-color": props.themeVariant?.palette?.neutralLighter,
|
||||
"scrollbar-width": "thin",
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
|
||||
const classComponent = React.useMemo(() => {
|
||||
return mergeStyleSets({
|
||||
webPartTitle: {
|
||||
fontWeight: 600,
|
||||
overflowX: "hidden",
|
||||
textOverflow: "Ellipsis",
|
||||
fontSize: props.themeVariant.fonts.large.fontSize,
|
||||
marginBottom: 10,
|
||||
},
|
||||
});
|
||||
}, [props.themeVariant]);
|
||||
|
||||
const stateRef = React.useRef(state); // Use to access state on eventListenners
|
||||
|
||||
const columns: IColumn[] = React.useMemo(() => {
|
||||
return [
|
||||
{
|
||||
key: "documentRenderLink",
|
||||
name: "Document",
|
||||
fieldName: "name",
|
||||
minWidth: 400,
|
||||
maxWidth: 800,
|
||||
isResizable: false,
|
||||
data: "string",
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
||||
const onRenderRow: IDetailsListProps["onRenderRow"] = (propsItem) => {
|
||||
if (propsItem.item) {
|
||||
const item = propsItem.item as INavLink;
|
||||
return (
|
||||
<Stack horizontal horizontalAlign="start" tokens={{ childrenGap: 10 }} styles={stackItemStyles}>
|
||||
<FontIcon iconName={item.iconProps.iconName} />
|
||||
<Link href={item.url} target="_blank">
|
||||
<Text variant={"medium"}>{item.name}</Text>
|
||||
</Link>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
if (!props.listId || !props.fieldName) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let _navLinksGroups: INavLinkGroup[] = [];
|
||||
stateRef.current = {
|
||||
...stateRef.current,
|
||||
isLoading: true,
|
||||
navLinkGroups: _navLinksGroups,
|
||||
};
|
||||
setState(stateRef.current);
|
||||
const _groupHeaders = await getGroupHeaders(props.listId, props.fieldName, props.listBaseTemplate);
|
||||
const { fieldName } = props;
|
||||
const _field: any = await getField(props.listId, props.fieldName);
|
||||
|
||||
for (const groupHeader of _groupHeaders) {
|
||||
let _name: any;
|
||||
switch (_field.fieldType) {
|
||||
case "TaxonomyFieldType":
|
||||
_name = groupHeader[fieldName]?.Label ?? "Unassigned";
|
||||
break;
|
||||
case "TaxonomyFieldTypeMulti":
|
||||
_name = groupHeader[fieldName][0]?.Label ?? "Unassigned";
|
||||
break;
|
||||
case "User":
|
||||
if (_name != "Unassigned") {
|
||||
_name = groupHeader[fieldName][0]?.title;
|
||||
}
|
||||
break;
|
||||
case "Lookup":
|
||||
_name =
|
||||
groupHeader[props.fieldName] !== "" &&
|
||||
groupHeader[props.fieldName] !== undefined &&
|
||||
groupHeader[props.fieldName][0].lookupValue !== ""
|
||||
? groupHeader[props.fieldName][0]?.lookupValue
|
||||
: "Unassigned";
|
||||
break;
|
||||
default:
|
||||
_name =
|
||||
groupHeader[props.fieldName] !== "" && groupHeader[props.fieldName] !== undefined
|
||||
? groupHeader[props.fieldName]
|
||||
: "Unassigned";
|
||||
break;
|
||||
}
|
||||
_navLinksGroups.push({
|
||||
name: _name,
|
||||
groupData: _name,
|
||||
collapseByDefault: true,
|
||||
links: [],
|
||||
});
|
||||
// Ensure the groups name are unique!
|
||||
_navLinksGroups = uniqBy(_navLinksGroups, "name");
|
||||
}
|
||||
stateRef.current = {
|
||||
...stateRef.current,
|
||||
hasError: false,
|
||||
errorMessage: "",
|
||||
isLoading: false,
|
||||
listName: _field.fieldScope,
|
||||
navLinkGroups: _navLinksGroups,
|
||||
};
|
||||
|
||||
setState(stateRef.current);
|
||||
} catch (error) {
|
||||
stateRef.current = {
|
||||
...stateRef.current,
|
||||
hasError: true,
|
||||
errorMessage: error.message,
|
||||
};
|
||||
setState(stateRef.current);
|
||||
}
|
||||
})();
|
||||
}, [props.listId, props.fieldName]);
|
||||
|
||||
// On Header click get Items for the header
|
||||
const onGroupHeaderClick = React.useCallback(
|
||||
async (ev: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
try {
|
||||
const _groupName = ev.currentTarget.innerText;
|
||||
const { navLinkGroups } = stateRef.current;
|
||||
|
||||
setState(stateRef.current);
|
||||
const _navGroup = filter(navLinkGroups, { name: _groupName });
|
||||
if (_navGroup?.length && _navGroup[0]?.links?.length === 0) {
|
||||
const _navlinks: INavLink[] = [];
|
||||
const _groupHeaderItems: any[] = await getGroupItems(
|
||||
props.listId,
|
||||
props.fieldName,
|
||||
_groupName,
|
||||
props.listBaseTemplate
|
||||
);
|
||||
if (_groupHeaderItems?.length) {
|
||||
for (const _groupHeaderItem of _groupHeaderItems) {
|
||||
if (props.listBaseTemplate === 0) {
|
||||
// List
|
||||
_navlinks.push({
|
||||
name: _groupHeaderItem.Title,
|
||||
url: `${_groupHeaderItem.FileDirRef}/dispform.aspx?ID=${_groupHeaderItem.Id}`,
|
||||
iconProps: {
|
||||
iconName: "TaskManager",
|
||||
},
|
||||
key: _groupHeaderItem.Id,
|
||||
target: "_blank",
|
||||
isExpanded: false,
|
||||
});
|
||||
}
|
||||
if (props.listBaseTemplate === 1) {
|
||||
// Document Library
|
||||
_navlinks.push({
|
||||
name: _groupHeaderItem.FileLeafRef,
|
||||
url: _groupHeaderItem.FileRef,
|
||||
iconProps: {
|
||||
...getFileTypeIconProps({
|
||||
extension: _groupHeaderItem.DocIcon,
|
||||
size: 16,
|
||||
imageFileType: "svg",
|
||||
}),
|
||||
},
|
||||
key: _groupHeaderItem.title,
|
||||
target: "_blank",
|
||||
isExpanded: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update Navigation with Items of Group
|
||||
|
||||
_navGroup[0].links = _navlinks;
|
||||
}
|
||||
const _index = findIndex(navLinkGroups, { name: _groupName });
|
||||
_navGroup[0].collapseByDefault = true;
|
||||
navLinkGroups[_index] = _navGroup[0];
|
||||
|
||||
stateRef.current = { ...stateRef.current, navLinkGroups: navLinkGroups };
|
||||
setState(stateRef.current);
|
||||
} catch (error) {}
|
||||
},
|
||||
[props]
|
||||
);
|
||||
|
||||
// Show Error if Exists
|
||||
if (state.hasError) {
|
||||
return (
|
||||
<>
|
||||
<MessageBar messageBarType={MessageBarType.error} isMultiline>
|
||||
{state.errorMessage}
|
||||
</MessageBar>
|
||||
</>
|
||||
);
|
||||
}
|
||||
// render component
|
||||
if (!props.listId || !props.fieldName) {
|
||||
return (
|
||||
<Placeholder
|
||||
iconName="Edit"
|
||||
iconText={strings.PlaceHolderIconText}
|
||||
description={strings.PlaceHolderDescription}
|
||||
buttonLabel={strings.PlaceHolderButtonLable}
|
||||
onConfigure={props.onConfigure}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Customizer settings={{ theme: props.themeVariant }}>
|
||||
{state.isLoading ? (
|
||||
<Stack horizontal horizontalAlign="center">
|
||||
<Spinner size={SpinnerSize.medium}></Spinner>
|
||||
</Stack>
|
||||
) : (
|
||||
<>
|
||||
<Stack horizontalAlign="space-between" horizontal tokens={{ childrenGap: 10 }} style={{ width: "100%" }}>
|
||||
<div className={classComponent.webPartTitle}>{props.title}</div>
|
||||
<Link href={state.listName}>{strings.ViewAllLabel}</Link>
|
||||
</Stack>
|
||||
{state.navLinkGroups?.length === 0 ? (
|
||||
<Label
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: props.themeVariant.fonts.small.fontSize,
|
||||
}}
|
||||
>
|
||||
{strings.NodocumentsLabel}
|
||||
</Label>
|
||||
) : (
|
||||
<Accordion allowZeroExpanded allowMultipleExpanded>
|
||||
{state.navLinkGroups.map((item, i) => {
|
||||
return (
|
||||
<AccordionItem>
|
||||
<AccordionItemHeading
|
||||
onClick={async (ev) => {
|
||||
await onGroupHeaderClick(ev);
|
||||
}}
|
||||
>
|
||||
<AccordionItemButton>{item.name}</AccordionItemButton>
|
||||
</AccordionItemHeading>
|
||||
<AccordionItemPanel>
|
||||
<Stack>
|
||||
<DetailsList
|
||||
items={item.links}
|
||||
columns={columns}
|
||||
styles={listViewStyles}
|
||||
compact={true}
|
||||
selectionMode={SelectionMode.none}
|
||||
setKey="none"
|
||||
layoutMode={DetailsListLayoutMode.justified}
|
||||
isHeaderVisible={false}
|
||||
onRenderRow={onRenderRow}
|
||||
/>
|
||||
</Stack>
|
||||
</AccordionItemPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
})}
|
||||
</Accordion>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Customizer>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
import { IReadonlyTheme } from "@microsoft/sp-component-base";
|
||||
import { DisplayMode } from "@microsoft/sp-core-library";
|
||||
|
||||
export interface IDocumentsLinksAccordionProps {
|
||||
title: string;
|
||||
listId:string;
|
||||
listBaseTemplate:number;
|
||||
fieldName:string;
|
||||
locale:string;
|
||||
themeVariant: IReadonlyTheme | undefined;
|
||||
onConfigure: () => void;
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { Nav, INavStyles, INavLinkGroup } from 'office-ui-fabric-react/lib/Nav';
|
||||
export interface IDocumentsLinksAccordionState {
|
||||
navLinkGroups: INavLinkGroup[];
|
||||
isLoading: boolean;
|
||||
hasError:boolean;
|
||||
errorMessage:string;
|
||||
listName:string;
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export interface IListItemsMenu {
|
||||
groupBy:string;
|
||||
id:string;
|
||||
title:string;
|
||||
url:string;
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
import "@pnp/sp/fields";
|
||||
import "@pnp/sp/items";
|
||||
import "@pnp/sp/lists";
|
||||
import "@pnp/sp/webs";
|
||||
|
||||
import { sortBy, uniqBy } from "lodash";
|
||||
|
||||
import { sp } from "@pnp/sp";
|
||||
import { IFieldInfo } from "@pnp/sp/fields";
|
||||
import { IListInfo } from "@pnp/sp/lists";
|
||||
import * as moment from "moment";
|
||||
import { format, parse, parseISO } from "date-fns";
|
||||
import { checkIfValidDate } from "../Utils/Utils";
|
||||
|
||||
export const useList = () => {
|
||||
// Run on useList hook
|
||||
(async () => {})();
|
||||
|
||||
// Get List Columns
|
||||
const getListColumns = async (listId: string): Promise<IFieldInfo[]> => {
|
||||
const _listColumnsResults: IFieldInfo[] = await sp.web.lists.getById(listId).fields.filter("Hidden eq false").get();
|
||||
|
||||
const _wColumns: IFieldInfo[] = uniqBy(sortBy(_listColumnsResults, "Title"), "Title");
|
||||
return _wColumns;
|
||||
};
|
||||
|
||||
const getField = async (listId: string, field: string): Promise<any> => {
|
||||
const _field: IFieldInfo = await sp.web.lists.getById(listId).fields.getByInternalNameOrTitle(field).get();
|
||||
|
||||
const fieldType = _field.TypeAsString;
|
||||
const fieldScope = _field.Scope;
|
||||
return { fieldType, fieldScope };
|
||||
};
|
||||
|
||||
const getGroupHeaders = async (listId: string, groupByField: string, baseTemplate: number): Promise<any[]> => {
|
||||
let _viewXml = `<View Scope='Recursive'>
|
||||
<Query>
|
||||
<GroupBy Collapse="TRUE">
|
||||
<FieldRef Name="${groupByField}"/>
|
||||
</GroupBy>
|
||||
</Query>
|
||||
<RowLimit>1000</RowLimit>
|
||||
</View>`;
|
||||
|
||||
const _groupHeadersResults = await sp.web.lists.getById(listId).renderListDataAsStream({ ViewXml: _viewXml });
|
||||
return uniqBy(_groupHeadersResults.Row, groupByField);
|
||||
};
|
||||
|
||||
const getGroupItems = async (
|
||||
listId: string,
|
||||
groupByField: string,
|
||||
groupFieldValue: string,
|
||||
baseTemplate: number
|
||||
): Promise<any[]> => {
|
||||
const _field: any = await getField(listId, groupByField);
|
||||
|
||||
if (checkIfValidDate(groupFieldValue)) {
|
||||
groupFieldValue = format(new Date(groupFieldValue), "yyyy-MM-dd");
|
||||
}
|
||||
|
||||
switch (_field.fieldType) {
|
||||
case "DateTime":
|
||||
groupFieldValue =
|
||||
groupFieldValue != "Unassigned" ? format(parseISO(groupFieldValue), "yyyy-MM-dd") : "Unassigned";
|
||||
break;
|
||||
case "AllDayEvent":
|
||||
groupFieldValue = groupFieldValue === "No" ? "0" : "1";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
let _viewXml = `<View Scope='Recursive'>
|
||||
<Query>
|
||||
<OrderBy>
|
||||
<FieldRef Name="${groupByField}" Ascending="FALSE"></FieldRef>
|
||||
</OrderBy>
|
||||
</Query>
|
||||
</View>`;
|
||||
|
||||
if (groupFieldValue != "Unassigned") {
|
||||
_viewXml = `<View Scope='Recursive'>
|
||||
<Query>
|
||||
<OrderBy>
|
||||
<FieldRef Name="${groupByField}" Ascending="FALSE"></FieldRef>
|
||||
</OrderBy>
|
||||
<Where>
|
||||
<Eq>
|
||||
<FieldRef Name="${groupByField}"></FieldRef>
|
||||
<Value Type="${_field.fieldType}" IncludeTimeValue="FALSE">${groupFieldValue}</Value>
|
||||
</Eq>
|
||||
</Where>
|
||||
</Query>
|
||||
</View>`;
|
||||
}
|
||||
|
||||
if (groupFieldValue === "Unassigned") {
|
||||
_viewXml = `<View Scope='Recursive'>
|
||||
<Query>
|
||||
<OrderBy>
|
||||
<FieldRef Name="${groupByField}" Ascending="FALSE"></FieldRef>
|
||||
</OrderBy>
|
||||
<Where>
|
||||
<IsNull>
|
||||
<FieldRef Name="${groupByField}"></FieldRef>
|
||||
</IsNull>
|
||||
</Where>
|
||||
</Query>
|
||||
</View>`;
|
||||
}
|
||||
|
||||
const _groupItemsResults = await sp.web.lists.getById(listId).renderListDataAsStream({ ViewXml: _viewXml });
|
||||
|
||||
return _groupItemsResults.Row;
|
||||
};
|
||||
|
||||
// Get Lists
|
||||
const getLists = async (baseTemplate: number): Promise<IListInfo[]> => {
|
||||
let _filter: string = "Hidden eq false and ";
|
||||
if (baseTemplate === 0) {
|
||||
_filter = _filter + " BaseType ne 1";
|
||||
} else {
|
||||
_filter = _filter + " BaseType eq 1";
|
||||
}
|
||||
const _lists: IListInfo[] = await sp.web.lists.filter(_filter).get();
|
||||
return _lists;
|
||||
};
|
||||
|
||||
// Return functions
|
||||
return {
|
||||
getListColumns,
|
||||
getLists,
|
||||
getGroupItems,
|
||||
getGroupHeaders,
|
||||
getField,
|
||||
};
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "a0215264-a8cd-4952-aecd-4e80719de202",
|
||||
"alias": "DocumentsLinksAccordionWebPart",
|
||||
"componentType": "WebPart",
|
||||
"supportsFullBleed": true,
|
||||
// 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", "SharePointFullPage", "TeamsPersonalApp","TeamsTab"],
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
|
||||
"group": { "default": "SPFx Custom Web Parts" },
|
||||
"title": { "default": "Documents Links Accordion" },
|
||||
"description": { "default": "Documents" },
|
||||
"officeFabricIconFontName": "GroupedList",
|
||||
"properties": {
|
||||
"title": "Documents",
|
||||
"listId": "",
|
||||
"fieldName": "",
|
||||
"listBasetemplate": 1
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,302 @@
|
|||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
|
||||
import {
|
||||
loadTheme,
|
||||
MessageBarType,
|
||||
SpinnerSize
|
||||
} from "office-ui-fabric-react";
|
||||
|
||||
import {
|
||||
IReadonlyTheme,
|
||||
ThemeChangedEventArgs,
|
||||
ThemeProvider
|
||||
} from "@microsoft/sp-component-base";
|
||||
import { MSGraphClient } from "@microsoft/sp-http";
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
IPropertyPaneDropdownOption,
|
||||
IPropertyPaneField,
|
||||
IPropertyPaneGroup,
|
||||
IPropertyPanePage,
|
||||
PropertyPaneDropdown,
|
||||
PropertyPaneTextField
|
||||
} from "@microsoft/sp-property-pane";
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import { sp } from "@pnp/sp";
|
||||
import {
|
||||
PropertyFieldListPicker,
|
||||
PropertyFieldListPickerOrderBy
|
||||
} from "@pnp/spfx-property-controls/lib/PropertyFieldListPicker";
|
||||
import {
|
||||
PropertyFieldMessage
|
||||
} from "@pnp/spfx-property-controls/lib/PropertyFieldMessage";
|
||||
import {
|
||||
PropertyFieldSpinner
|
||||
} from "@pnp/spfx-property-controls/lib/PropertyFieldSpinner";
|
||||
|
||||
|
||||
import { useList } from "../../hooks/useList";
|
||||
|
||||
export interface IListItemsMenuWebPartProps {
|
||||
title: string;
|
||||
listId: string;
|
||||
fieldName: string;
|
||||
listBasetemplate: number;
|
||||
}
|
||||
|
||||
const teamsDefaultTheme = require("../../common/TeamsDefaultTheme.json");
|
||||
const teamsDarkTheme = require("../../common/TeamsDarkTheme.json");
|
||||
const teamsContrastTheme = require("../../common/TeamsContrastTheme.json");
|
||||
|
||||
const { getListColumns, getLists } = useList() ;
|
||||
|
||||
import * as strings from 'DocumentsLinksAccordionWebPartStrings';
|
||||
import { IDocumentsLinksAccordionProps } from "../../components/DocumentsLinksAccordion/IDocumentsLinksAccordionProps";
|
||||
import {DocumentsLinksAccordion} from "../../components/DocumentsLinksAccordion/DocumentsLinksAccordion";
|
||||
import { Version } from "@microsoft/sp-core-library";
|
||||
export interface IDocumentsLinksAccordionWebPartProps {
|
||||
title: string;
|
||||
listId: string;
|
||||
fieldName: string;
|
||||
listBasetemplate: number;
|
||||
}
|
||||
|
||||
export default class DocumentsLinksAccordionWebPart extends BaseClientSideWebPart<IDocumentsLinksAccordionWebPartProps> {
|
||||
|
||||
private columns: IPropertyPaneDropdownOption[] = [];
|
||||
private lists: IPropertyPaneDropdownOption[] = [];
|
||||
private _themeProvider: ThemeProvider;
|
||||
private _themeVariant: IReadonlyTheme | undefined;
|
||||
private _msgGraphclient: MSGraphClient;
|
||||
private _hasError: boolean = false;
|
||||
private _messageError: string = undefined;
|
||||
|
||||
|
||||
protected async onInit(): Promise<void> {
|
||||
|
||||
sp.setup({
|
||||
spfxContext: this.context,
|
||||
});
|
||||
|
||||
this._msgGraphclient = await this.context.msGraphClientFactory.getClient();
|
||||
|
||||
this._themeProvider = this.context.serviceScope.consume(
|
||||
ThemeProvider.serviceKey
|
||||
);
|
||||
// If it exists, get the theme variant
|
||||
this._themeVariant = this._themeProvider.tryGetTheme();
|
||||
// Register a handler to be notified if the theme variant changes
|
||||
this._themeProvider.themeChangedEvent.add(
|
||||
this,
|
||||
this._handleThemeChangedEvent
|
||||
);
|
||||
|
||||
if (this.context.sdks.microsoftTeams) {
|
||||
// in teams ?
|
||||
const context = this.context.sdks.microsoftTeams!.context;
|
||||
this._applyTheme(context.theme || "default");
|
||||
this.context.sdks.microsoftTeams.teamsJs.registerOnThemeChangeHandler(
|
||||
this._applyTheme
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current theme variant reference and re-render.
|
||||
*
|
||||
* @param args The new theme
|
||||
*/
|
||||
private _handleThemeChangedEvent(args: ThemeChangedEventArgs): void {
|
||||
this._themeVariant = args.theme;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
// Apply theme id in Teams
|
||||
private _applyTheme = (theme: string): void => {
|
||||
this.context.domElement.setAttribute("data-theme", theme);
|
||||
document.body.setAttribute("data-theme", theme);
|
||||
|
||||
if (theme == "dark") {
|
||||
loadTheme({
|
||||
palette: teamsDarkTheme,
|
||||
});
|
||||
}
|
||||
|
||||
if (theme == "default") {
|
||||
loadTheme({
|
||||
palette: teamsDefaultTheme,
|
||||
});
|
||||
}
|
||||
|
||||
if (theme == "contrast") {
|
||||
loadTheme({
|
||||
palette: teamsContrastTheme,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
|
||||
const element: React.ReactElement<IDocumentsLinksAccordionProps> = React.createElement(
|
||||
DocumentsLinksAccordion,
|
||||
{
|
||||
title: this.properties.title,
|
||||
listId: this.properties.listId,
|
||||
fieldName: this.properties.fieldName,
|
||||
themeVariant: this._themeVariant,
|
||||
locale: this.context.pageContext.cultureInfo.currentUICultureName,
|
||||
listBaseTemplate: this.properties.listBasetemplate ?? 1,
|
||||
onConfigure: () =>{ this.context.propertyPane.open(); },
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get disableReactivePropertyChanges() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async onPropertyPaneConfigurationStart() {
|
||||
if (
|
||||
this.properties.fieldName &&
|
||||
this.properties.listId &&
|
||||
this.columns.length === 0
|
||||
) {
|
||||
await this.addListColumns(this.properties.listId);
|
||||
this.context.propertyPane.refresh();
|
||||
}
|
||||
|
||||
if ( this.properties.listId && this.lists.length === 0){
|
||||
await this.addLists('1');
|
||||
this.context.propertyPane.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
protected async onPropertyPaneFieldChanged(
|
||||
propertyPath: string,
|
||||
oldValue: any,
|
||||
newValue: any
|
||||
) {
|
||||
|
||||
if (propertyPath === "listId" && newValue != oldValue) {
|
||||
this.columns = [];
|
||||
|
||||
this.context.propertyPane.refresh();
|
||||
await this.addListColumns(newValue);
|
||||
this.context.propertyPane.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private async addLists(newValue: any) {
|
||||
try {
|
||||
this.lists = [];
|
||||
const _lists = await getLists(newValue);
|
||||
for (const _list of _lists) {
|
||||
this.lists.push({ key: _list.Id, text: _list.Title });
|
||||
}
|
||||
} catch (error) {
|
||||
this._hasError = true;
|
||||
this._messageError =error.message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async addListColumns(newValue: any) {
|
||||
try {
|
||||
this.columns = [];
|
||||
const _listColumns = await getListColumns(newValue);
|
||||
for (const _column of _listColumns) {
|
||||
this.columns.push({ key: _column.InternalName, text: _column.Title });
|
||||
}
|
||||
} catch (error) {
|
||||
this._hasError = true;
|
||||
this._messageError = error.message;
|
||||
}
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
const _pages: IPropertyPanePage[] = [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField("title", {
|
||||
label: strings.DescriptionFieldLabel,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
let groups: IPropertyPaneGroup = _pages[0].groups[0] as IPropertyPaneGroup;
|
||||
let groupFields: IPropertyPaneField<any>[] = groups.groupFields;
|
||||
groupFields.push(
|
||||
PropertyFieldListPicker("listId", {
|
||||
label: "Select Document Library",
|
||||
selectedList: this.properties.listId ,
|
||||
includeHidden: false,
|
||||
baseTemplate: 101,
|
||||
orderBy: PropertyFieldListPickerOrderBy.Title,
|
||||
disabled: false,
|
||||
onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
|
||||
properties: this.properties,
|
||||
context: this.context,
|
||||
onGetErrorMessage: null,
|
||||
deferredValidationTime: 0,
|
||||
key: "listPickerFieldId",
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
|
||||
if (this.properties.listId) {
|
||||
groupFields.push(
|
||||
PropertyFieldSpinner("", {
|
||||
key: "sp1",
|
||||
size: SpinnerSize.medium,
|
||||
isVisible: (this.columns.length || this._hasError) ? false : true,
|
||||
label: "Loading ...",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Show Columns
|
||||
if (this.columns.length > 0) {
|
||||
groupFields.push(
|
||||
PropertyPaneDropdown("fieldName", {
|
||||
label: "Select field to group by documents",
|
||||
options: this.columns,
|
||||
selectedKey: this.properties.fieldName,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Show Error
|
||||
if (this._hasError) {
|
||||
groupFields.push(
|
||||
PropertyFieldMessage("", {
|
||||
key: "msgError",
|
||||
messageType: MessageBarType.error,
|
||||
multiline: true,
|
||||
text: this._messageError,
|
||||
isVisible: this._hasError,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const _panelConfiguration: IPropertyPaneConfiguration = { pages: _pages };
|
||||
return _panelConfiguration;
|
||||
}
|
||||
}
|
12
samples/react-document-links-accordion/src/webparts/DocumentsLinksAccordion/loc/en-us.js
vendored
Normal file
12
samples/react-document-links-accordion/src/webparts/DocumentsLinksAccordion/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
define([], function() {
|
||||
return {
|
||||
PlaceHolderButtonLable: "Configure",
|
||||
PlaceHolderDescription: "Please configure the web part.",
|
||||
PlaceHolderIconText: "Configure Documents Links Accordion Web Part",
|
||||
"PropertyPaneDescription": "Show documents grouped by field",
|
||||
"BasicGroupName": "Properties",
|
||||
"DescriptionFieldLabel": "Title",
|
||||
"NodocumentsLabel":"No documents found in library",
|
||||
"ViewAllLabel": "View All"
|
||||
}
|
||||
});
|
15
samples/react-document-links-accordion/src/webparts/DocumentsLinksAccordion/loc/mystrings.d.ts
vendored
Normal file
15
samples/react-document-links-accordion/src/webparts/DocumentsLinksAccordion/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
declare interface IDocumentsLinksAccordionWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
ViewAllLabel:string;
|
||||
NodocumentsLabel:string;
|
||||
PlaceHolderButtonLable: string;
|
||||
PlaceHolderDescription: string;
|
||||
PlaceHolderIconText: string;
|
||||
}
|
||||
|
||||
declare module 'DocumentsLinksAccordionWebPartStrings' {
|
||||
const strings: IDocumentsLinksAccordionWebPartStrings;
|
||||
export = strings;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 383 B |
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"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": [
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection",
|
||||
"es2015.promise"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"extends": "./node_modules/@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-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue