Merge pull request #2075 from joaojmendes/react-documents-links-accordion
New sample react-documents-links-accordion
This commit is contained in:
commit
aec067b288
|
@ -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.12.1-green.svg)
|
||||||
|
![Node.js LTS v14 | LTS v12 | LTS v10](https://img.shields.io/badge/Node.js-LTS%20v14%20%7C%20LTS%20v12%20%7C%20LTS%20v10-green.svg)
|
||||||
|
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
|
||||||
|
![Does not work with SharePoint 2019](https://img.shields.io/badge/SharePoint%20Server%202019-Incompatible-red.svg "SharePoint Server 2019 requires SPFx 1.4.1 or lower")
|
||||||
|
![Does not work with SharePoint 2016 (Feature Pack 2)](https://img.shields.io/badge/SharePoint%20Server%202016%20(Feature%20Pack%202)-Incompatible-red.svg "SharePoint Server 2016 Feature Pack 2 requires SPFx 1.1")
|
||||||
|
![Local Workbench Incompatible](https://img.shields.io/badge/Local%20Workbench-Incompatible-red.svg)
|
||||||
|
![Hosted Workbench Compatible](https://img.shields.io/badge/Hosted%20Workbench-Compatible-green.svg)
|
||||||
|
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
## Web Part Properties
|
||||||
|
|
||||||
|
Property |Type|Required| comments
|
||||||
|
--------------------|----|--------|----------
|
||||||
|
Web part 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.**
|
||||||
|
|
||||||
|
|
||||||
|
## Help
|
||||||
|
|
||||||
|
We do not support samples, but we this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.
|
||||||
|
|
||||||
|
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,89 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "pnp-sp-dev-spfx-web-parts-react-documents-links-accordion",
|
||||||
|
"source": "pnp",
|
||||||
|
"title": "Documents Links Accordion",
|
||||||
|
"shortDescription": "This web part allows user create a accordion with documents links grouped by any column of document library.",
|
||||||
|
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-documents-links-accordion",
|
||||||
|
"longDescription": [
|
||||||
|
"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."
|
||||||
|
],
|
||||||
|
"creationDateTime": "2021-10-10",
|
||||||
|
"updateDateTime": "2021-10-10",
|
||||||
|
"products": [
|
||||||
|
"SharePoint",
|
||||||
|
"Office"
|
||||||
|
],
|
||||||
|
"metadata": [
|
||||||
|
{
|
||||||
|
"key": "CLIENT-SIDE-DEV",
|
||||||
|
"value": "React"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SPFX-VERSION",
|
||||||
|
"value": "1.12.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SPFX-FULLPAGEAPP",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SPFX-TEAMSTAB",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SPFX-TEAMSPERSONALAPP",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "REACT-HOOKS",
|
||||||
|
"value": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "PNPCONTROLS",
|
||||||
|
"value": "PropertyFieldListPicker, PropertyFieldMessage, PropertyFieldSpinner, Placeholder, AccessibleAccordion"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thumbnails": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"order": 100,
|
||||||
|
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-documents-links-accordion/assets/documentsLinksAccordion.gif",
|
||||||
|
"alt": "Web part in action"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"order": 101,
|
||||||
|
"url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-documents-links-accordion/assets/documentsLinksAccordion1.png",
|
||||||
|
"alt": "Screen shot of web part"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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