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