New feature Expand/Collapse all upgrd SPFx 1.15.0
This commit is contained in:
parent
fa3a66492d
commit
91b09cb163
|
@ -0,0 +1,10 @@
|
||||||
|
require('@rushstack/eslint-config/patch/modern-module-resolution');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extends: ['@microsoft/eslint-config-spfx/lib/profiles/react'],
|
||||||
|
parserOptions: { tsconfigRootDir: __dirname },
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/typedef": "off",
|
||||||
|
"@microsoft/spfx/no-async-await": "off"
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,4 +1,35 @@
|
||||||
# .CER Certificates
|
# Logs
|
||||||
*.cer
|
logs
|
||||||
# .PEM Certificates
|
*.log
|
||||||
*.pem
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Build generated files
|
||||||
|
dist
|
||||||
|
lib
|
||||||
|
release
|
||||||
|
solution
|
||||||
|
temp
|
||||||
|
*.sppkg
|
||||||
|
|
||||||
|
.heft
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"plusBeta": false,
|
"plusBeta": false,
|
||||||
"isCreatingSolution": true,
|
"isCreatingSolution": true,
|
||||||
"environment": "spo",
|
"environment": "spo",
|
||||||
"version": "1.13.1",
|
"version": "1.15.0",
|
||||||
"libraryName": "react-taxonomy-file-explorer",
|
"libraryName": "react-taxonomy-file-explorer",
|
||||||
"libraryId": "5697a573-1bd1-4ff3-96f1-82263c8eb008",
|
"libraryId": "5697a573-1bd1-4ff3-96f1-82263c8eb008",
|
||||||
"packageManager": "npm",
|
"packageManager": "npm",
|
||||||
|
|
|
@ -22,12 +22,15 @@ Copy:
|
||||||
|
|
||||||
![Copying the file to a new one with (only) the target term in the managed metadata column (Copy)](./assets/05Copy.gif)
|
![Copying the file to a new one with (only) the target term in the managed metadata column (Copy)](./assets/05Copy.gif)
|
||||||
|
|
||||||
For further details see the author's [blog post](https://mmsharepoint.wordpress.com/2021/12/23/a-sharepoint-file-explorer-based-on-managed-metadata-and-spfx/)
|
[UPDATE] Collapse and expand all:
|
||||||
|
|
||||||
|
![Collapse and expand all](./assets/ExpandCollapseAll.gif)
|
||||||
|
|
||||||
|
For further details see the author's [blog post](https://mmsharepoint.wordpress.com/2021/12/23/a-sharepoint-file-explorer-based-on-managed-metadata-and-spfx/)
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
![SPFx 1.13.0](https://img.shields.io/badge/SPFx-1.13.0-green.svg)
|
![SPFx 1.15.0](https://img.shields.io/badge/version-1.15-green.svg)
|
||||||
![Node.js v14 | v12 | v10](https://img.shields.io/badge/Node.js-v14%20%7C%20v12%20%7C%20v10-green.svg)
|
![Node.js v14 | v12 | v10](https://img.shields.io/badge/Node.js-v14%20%7C%20v12%20%7C%20v10-green.svg)
|
||||||
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-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 2019](https://img.shields.io/badge/SharePoint%20Server%202019-Incompatible-red.svg "SharePoint Server 2019 requires SPFx 1.4.1 or lower")
|
||||||
|
@ -36,7 +39,6 @@ For further details see the author's [blog post](https://mmsharepoint.wordpress.
|
||||||
![Hosted Workbench Compatible](https://img.shields.io/badge/Hosted%20Workbench-Compatible-green.svg)
|
![Hosted Workbench Compatible](https://img.shields.io/badge/Hosted%20Workbench-Compatible-green.svg)
|
||||||
![Compatible with Remote Containers](https://img.shields.io/badge/Remote%20Containers-Compatible-green.svg)
|
![Compatible with Remote Containers](https://img.shields.io/badge/Remote%20Containers-Compatible-green.svg)
|
||||||
|
|
||||||
|
|
||||||
## Applies to
|
## Applies to
|
||||||
|
|
||||||
- [SharePoint Framework](https://aka.ms/spfx)
|
- [SharePoint Framework](https://aka.ms/spfx)
|
||||||
|
@ -60,6 +62,7 @@ react-taxonomy-file-explorer| [Markus Moeller](https://github.com/mmsharepoint)
|
||||||
Version|Date|Comments
|
Version|Date|Comments
|
||||||
-------|----|--------
|
-------|----|--------
|
||||||
1.0|December 26, 2021|Initial release
|
1.0|December 26, 2021|Initial release
|
||||||
|
1.1|July 16, 2021|Added expand/collapse all, upgraded to SPFx 1.15.0, upgraded to PnPJS V3.5.1
|
||||||
|
|
||||||
|
|
||||||
## Minimal Path to Awesome
|
## Minimal Path to Awesome
|
||||||
|
|
|
@ -7,12 +7,31 @@
|
||||||
"includeClientSideAssets": true,
|
"includeClientSideAssets": true,
|
||||||
"isDomainIsolated": false,
|
"isDomainIsolated": false,
|
||||||
"developer": {
|
"developer": {
|
||||||
"name": "",
|
"name": "Markus Moeller",
|
||||||
"websiteUrl": "",
|
"websiteUrl": "",
|
||||||
"privacyUrl": "",
|
"privacyUrl": "",
|
||||||
"termsOfUseUrl": "",
|
"termsOfUseUrl": "",
|
||||||
"mpnId": "Undefined-1.13.1"
|
"mpnId": "Undefined-1.13.1"
|
||||||
}
|
},
|
||||||
|
"metadata": {
|
||||||
|
"shortDescription": {
|
||||||
|
"default": "react-taxonomy-file-explorer description"
|
||||||
|
},
|
||||||
|
"longDescription": {
|
||||||
|
"default": "react-taxonomy-file-explorer description"
|
||||||
|
},
|
||||||
|
"screenshotPaths": [],
|
||||||
|
"videoUrl": "",
|
||||||
|
"categories": []
|
||||||
|
},
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"title": "react-taxonomy-file-explorer Feature",
|
||||||
|
"description": "The feature that activates elements of the react-taxonomy-file-explorer solution.",
|
||||||
|
"id": "9804fbff-b443-42e1-86f4-fc261980d916",
|
||||||
|
"version": "1.0.0.0"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"zippedPackage": "solution/react-taxonomy-file-explorer.sppkg"
|
"zippedPackage": "solution/react-taxonomy-file-explorer.sppkg"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,26 +9,32 @@
|
||||||
"test": "gulp test"
|
"test": "gulp test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluentui/react-file-type-icons": "^8.5.7",
|
"@fluentui/react-file-type-icons": "^8.6.11",
|
||||||
"@microsoft/sp-core-library": "1.13.1",
|
"@microsoft/sp-core-library": "1.15.0",
|
||||||
"@microsoft/sp-lodash-subset": "1.13.1",
|
"@microsoft/sp-lodash-subset": "1.15.0",
|
||||||
"@microsoft/sp-office-ui-fabric-core": "1.13.1",
|
"@microsoft/sp-office-ui-fabric-core": "1.15.0",
|
||||||
"@microsoft/sp-property-pane": "1.13.1",
|
"@microsoft/sp-property-pane": "1.15.0",
|
||||||
"@microsoft/sp-webpart-base": "1.13.1",
|
"@microsoft/sp-webpart-base": "1.15.0",
|
||||||
"@pnp/sp": "^2.11.0",
|
"@pnp/sp": "^3.5.1",
|
||||||
"office-ui-fabric-react": "7.174.1",
|
"office-ui-fabric-react": "7.185.7",
|
||||||
"react": "16.13.1",
|
"react": "16.13.1",
|
||||||
"react-dom": "16.13.1"
|
"react-dom": "16.13.1",
|
||||||
|
"tslib": "2.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@microsoft/eslint-config-spfx": "1.15.0",
|
||||||
|
"@microsoft/eslint-plugin-spfx": "1.15.0",
|
||||||
|
"@microsoft/rush-stack-compiler-3.9": "0.4.47",
|
||||||
|
"@microsoft/rush-stack-compiler-4.5": "0.2.2",
|
||||||
|
"@microsoft/sp-build-web": "1.15.0",
|
||||||
|
"@microsoft/sp-module-interfaces": "1.15.0",
|
||||||
|
"@rushstack/eslint-config": "2.5.1",
|
||||||
"@types/react": "16.9.51",
|
"@types/react": "16.9.51",
|
||||||
"@types/react-dom": "16.9.8",
|
"@types/react-dom": "16.9.8",
|
||||||
"@microsoft/sp-build-web": "1.13.1",
|
"@types/webpack-env": "1.15.2",
|
||||||
"@microsoft/sp-tslint-rules": "1.13.1",
|
"ajv": "6.12.5",
|
||||||
"@microsoft/sp-module-interfaces": "1.13.1",
|
"eslint": "8.7.0",
|
||||||
"@microsoft/rush-stack-compiler-3.9": "0.4.47",
|
"eslint-plugin-react-hooks": "4.3.0",
|
||||||
"gulp": "~4.0.2",
|
"gulp": "~4.0.2"
|
||||||
"ajv": "~6.12.3",
|
|
||||||
"@types/webpack-env": "1.13.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,35 @@
|
||||||
import { sp } from "@pnp/sp";
|
import { ServiceScope } from "@microsoft/sp-core-library";
|
||||||
|
import { PageContext } from "@microsoft/sp-page-context";
|
||||||
|
import { SPFI, spfi, SPFx } from "@pnp/sp";
|
||||||
|
import "@pnp/sp/webs";
|
||||||
|
import "@pnp/sp/lists";
|
||||||
|
import "@pnp/sp/items";
|
||||||
|
import "@pnp/sp/items/get-all";
|
||||||
|
import "@pnp/sp/files";
|
||||||
|
import "@pnp/sp/folders";
|
||||||
|
|
||||||
import { IFileItem } from "../model/IFileItem";
|
import { IFileItem } from "../model/IFileItem";
|
||||||
|
import { IItem } from "@pnp/sp/items";
|
||||||
|
|
||||||
export class SPService {
|
export class SPService {
|
||||||
private listName: string;
|
private _listName: string;
|
||||||
private fieldName: string;
|
private _fieldName: string;
|
||||||
|
private _sp: SPFI;
|
||||||
|
|
||||||
constructor (listname: string, fieldname: string) {
|
public constructor (serviceScope: ServiceScope, listname: string, fieldname: string) {
|
||||||
this.listName = listname;
|
this._listName = listname;
|
||||||
this.fieldName = fieldname;
|
this._fieldName = fieldname;
|
||||||
|
serviceScope.whenFinished(() => {
|
||||||
|
const pageContext: PageContext = serviceScope.consume(PageContext.serviceKey);
|
||||||
|
this._sp = spfi().using(SPFx({ pageContext }));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getItems (termsetID: string): Promise<IFileItem[]> {
|
public async getItems (termsetID: string): Promise<IFileItem[]> {
|
||||||
const items: any[] = await sp.web.lists.getByTitle(this.listName).items.select('Id', this.fieldName).expand('File').get();
|
const items: any[] = await this._sp.web.lists.getByTitle(this._listName).items.select('Id', this._fieldName).expand('File').getAll();
|
||||||
const files: IFileItem[] = [];
|
const files: IFileItem[] = [];
|
||||||
items.forEach(i => {
|
items.forEach(i => {
|
||||||
const nameparts = i.File.Name.split('.');
|
const nameparts: string[] = i.File.Name.split('.');
|
||||||
const file: IFileItem = {
|
const file: IFileItem = {
|
||||||
title: i.File.Name,
|
title: i.File.Name,
|
||||||
extension: nameparts[nameparts.length - 1],
|
extension: nameparts[nameparts.length - 1],
|
||||||
|
@ -23,10 +38,10 @@ export class SPService {
|
||||||
taxValue: [""],
|
taxValue: [""],
|
||||||
url: i.File.LinkingUrl
|
url: i.File.LinkingUrl
|
||||||
};
|
};
|
||||||
if (Array.isArray(i[this.fieldName])) {
|
if (Array.isArray(i[this._fieldName])) {
|
||||||
const termguids: string[] = [];
|
const termguids: string[] = [];
|
||||||
const taxvalues: string[] = [];
|
const taxvalues: string[] = [];
|
||||||
i[this.fieldName].forEach(f => {
|
i[this._fieldName].forEach(f => {
|
||||||
termguids.push(f.TermGuid.toLowerCase());
|
termguids.push(f.TermGuid.toLowerCase());
|
||||||
taxvalues.push(`${f.Label}|${f.TermGuid}`);
|
taxvalues.push(`${f.Label}|${f.TermGuid}`);
|
||||||
});
|
});
|
||||||
|
@ -34,21 +49,21 @@ export class SPService {
|
||||||
file.taxValue = taxvalues;
|
file.taxValue = taxvalues;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
file.termGuid = i[this.fieldName] ? [i[this.fieldName].TermGuid.toLowerCase()]:[""];
|
file.termGuid = i[this._fieldName] ? [i[this._fieldName].TermGuid.toLowerCase()]:[""];
|
||||||
file.taxValue = i[this.fieldName] ? [`${i[this.fieldName].Label.toLowerCase()}|${i[this.fieldName].TermGuid.toLowerCase()}`]:[""];
|
file.taxValue = i[this._fieldName] ? [`${i[this._fieldName].Label.toLowerCase()}|${i[this._fieldName].TermGuid.toLowerCase()}`]:[""];
|
||||||
}
|
}
|
||||||
files.push(file);
|
files.push(file);
|
||||||
});
|
});
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateTaxonomyItemByAdd (file: IFileItem, fieldName: string, newTaxonomyValue: string) {
|
public async updateTaxonomyItemByAdd(file: IFileItem, fieldName: string, newTaxonomyValue: string): Promise<void> {
|
||||||
const itemID: number = parseInt(file.id);
|
const itemID: number = parseInt(file.id);
|
||||||
let fieldValues = file.taxValue.join(';');
|
let fieldValues = file.taxValue.join(';');
|
||||||
fieldValues += `;${newTaxonomyValue}`;
|
fieldValues += `;${newTaxonomyValue}`;
|
||||||
|
|
||||||
// https://blog.aterentiev.com/how-to-easily-update-managed-metadata
|
// https://blog.aterentiev.com/how-to-easily-update-managed-metadata
|
||||||
await sp.web.lists.getByTitle(this.listName).items.getById(itemID).validateUpdateListItem([{
|
await this._sp.web.lists.getByTitle(this._listName).items.getById(itemID).validateUpdateListItem([{
|
||||||
ErrorMessage: null,
|
ErrorMessage: null,
|
||||||
FieldName: fieldName,
|
FieldName: fieldName,
|
||||||
FieldValue: fieldValues,
|
FieldValue: fieldValues,
|
||||||
|
@ -56,10 +71,10 @@ export class SPService {
|
||||||
}]);
|
}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateTaxonomyItemByReplace (file: IFileItem, fieldName: string, newTaxonomyValue: string) {
|
public async updateTaxonomyItemByReplace (file: IFileItem, fieldName: string, newTaxonomyValue: string): Promise<void> {
|
||||||
const itemID: number = parseInt(file.id);
|
const itemID: number = parseInt(file.id);
|
||||||
|
|
||||||
await sp.web.lists.getByTitle(this.listName).items.getById(itemID).validateUpdateListItem([{
|
await this._sp.web.lists.getByTitle(this._listName).items.getById(itemID).validateUpdateListItem([{
|
||||||
ErrorMessage: null,
|
ErrorMessage: null,
|
||||||
FieldName: fieldName,
|
FieldName: fieldName,
|
||||||
FieldValue: newTaxonomyValue,
|
FieldValue: newTaxonomyValue,
|
||||||
|
@ -69,16 +84,15 @@ export class SPService {
|
||||||
|
|
||||||
public async newTaxonomyItemByCopy (file: IFileItem, fieldName: string, newTaxonomyValue: string): Promise<IFileItem> {
|
public async newTaxonomyItemByCopy (file: IFileItem, fieldName: string, newTaxonomyValue: string): Promise<IFileItem> {
|
||||||
const fileUrl: URL = new URL(file.url);
|
const fileUrl: URL = new URL(file.url);
|
||||||
const currentFileNamePart = file.title.replace(`.${file.extension}`, '');
|
const currentFileNamePart: string = file.title.replace(`.${file.extension}`, '');
|
||||||
const newFilename = `${currentFileNamePart}_Copy.${file.extension}`;
|
const newFilename: string = `${currentFileNamePart}_Copy.${file.extension}`;
|
||||||
const destinationUrl = decodeURI(fileUrl.pathname).replace(file.title, newFilename);
|
const destinationUrl: string = decodeURI(fileUrl.pathname).replace(file.title, newFilename);
|
||||||
await sp.web.getFileByServerRelativePath(decodeURI(fileUrl.pathname)).copyByPath(destinationUrl, false, true);
|
await this._sp.web.getFileByServerRelativePath(decodeURI(fileUrl.pathname)).copyByPath(destinationUrl, false, true);
|
||||||
const newFileItemPromise = await sp.web.getFileByServerRelativePath(destinationUrl).getItem();
|
const newFileItemPromise: IItem = await this._sp.web.getFileByServerRelativePath(destinationUrl).getItem();
|
||||||
const newFileItem = await newFileItemPromise.get();
|
const newFileItem = await newFileItemPromise.file.getItem<{ Id: number }>("Id");
|
||||||
console.log(newFileItem);
|
const itemID: number = newFileItem.Id;
|
||||||
const itemID: number = parseInt(newFileItem.Id);
|
|
||||||
|
|
||||||
await sp.web.lists.getByTitle(this.listName).items.getById(itemID).validateUpdateListItem([{
|
await this._sp.web.lists.getByTitle(this._listName).items.getById(itemID).validateUpdateListItem([{
|
||||||
ErrorMessage: null,
|
ErrorMessage: null,
|
||||||
FieldName: fieldName,
|
FieldName: fieldName,
|
||||||
FieldValue: newTaxonomyValue,
|
FieldValue: newTaxonomyValue,
|
||||||
|
@ -94,4 +108,31 @@ export class SPService {
|
||||||
};
|
};
|
||||||
return newFile;
|
return newFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async newTaxonomyItemByUpload (file: any, fieldName: string, newTaxonomyValue: string): Promise<IFileItem> {
|
||||||
|
const libraryRoot = await this._sp.web.lists.getByTitle(this._listName).rootFolder();
|
||||||
|
// Assuming small file size, otherwise use chunks
|
||||||
|
const result = await this._sp.web.getFolderByServerRelativePath(libraryRoot.ServerRelativeUrl).files.addUsingPath(file.name, file, { Overwrite: true });
|
||||||
|
const fileNameParts: string[] = result.data.Name.split('.');
|
||||||
|
const newFileItemPromise = await this._sp.web.getFileByServerRelativePath(result.data.ServerRelativeUrl).getItem();
|
||||||
|
const newFileItem = await newFileItemPromise.file.getItem<{ Id: number }>("Id");
|
||||||
|
|
||||||
|
const itemID: number = newFileItem.Id;
|
||||||
|
await this._sp.web.lists.getByTitle(this._listName).items.getById(itemID).validateUpdateListItem([{
|
||||||
|
ErrorMessage: null,
|
||||||
|
FieldName: fieldName,
|
||||||
|
FieldValue: newTaxonomyValue,
|
||||||
|
HasException: false
|
||||||
|
}]);
|
||||||
|
|
||||||
|
const newFile: IFileItem = {
|
||||||
|
extension: fileNameParts[fileNameParts.length - 1],
|
||||||
|
id: itemID.toString(),
|
||||||
|
taxValue: [newTaxonomyValue],
|
||||||
|
termGuid: [newTaxonomyValue.split('|')[1]],
|
||||||
|
title: file.name,
|
||||||
|
url: result.data.ServerRelativeUrl
|
||||||
|
};
|
||||||
|
return newFile;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,29 +1,42 @@
|
||||||
import { sp } from "@pnp/sp";
|
import { ServiceScope } from "@microsoft/sp-core-library";
|
||||||
import { IOrderedTermInfo } from "@pnp/sp/taxonomy";
|
import { PageContext } from "@microsoft/sp-page-context";
|
||||||
|
import { SPFI, spfi, SPFx } from "@pnp/sp";
|
||||||
|
import "@pnp/sp/taxonomy";
|
||||||
|
import { IOrderedTermInfo, ITermInfo } from "@pnp/sp/taxonomy";
|
||||||
|
import "@pnp/sp/fields";
|
||||||
import { IFileItem } from "../model/IFileItem";
|
import { IFileItem } from "../model/IFileItem";
|
||||||
import { ITermNode } from "../model/ITermNode";
|
import { ITermNode } from "../model/ITermNode";
|
||||||
|
|
||||||
export class TaxonomyService {
|
export class TaxonomyService {
|
||||||
|
private _sp: SPFI;
|
||||||
|
|
||||||
|
public constructor (serviceScope: ServiceScope) {
|
||||||
|
serviceScope.whenFinished(() => {
|
||||||
|
const pageContext: PageContext = serviceScope.consume(PageContext.serviceKey);
|
||||||
|
this._sp = spfi().using(SPFx({ pageContext }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async getTermsetInfo (fieldName: string): Promise<string> {
|
public async getTermsetInfo (fieldName: string): Promise<string> {
|
||||||
const mmFieldInfo = await sp.web.fields.getByInternalNameOrTitle(fieldName).get();
|
const mmFieldInfo = await this._sp.web.fields.getByInternalNameOrTitle(fieldName)();
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const xmlField = parser.parseFromString(mmFieldInfo.SchemaXml, "text/xml");
|
const xmlField = parser.parseFromString(mmFieldInfo.SchemaXml, "text/xml");
|
||||||
const properties = xmlField.getElementsByTagName("ArrayOfProperty")[0].childNodes;
|
const properties = xmlField.getElementsByTagName("ArrayOfProperty")[0].childNodes;
|
||||||
let termsetID: string = "";
|
let termsetID: string = "";
|
||||||
properties.forEach(prop => {
|
properties.forEach(prop => {
|
||||||
if (prop.childNodes[0].textContent == "TermSetId") {
|
if (prop.childNodes[0].textContent === "TermSetId") {
|
||||||
termsetID = prop.childNodes[1].textContent;
|
termsetID = prop.childNodes[1].textContent;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return termsetID;
|
return termsetID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getTermset (termsetID: string) {
|
public async getTermset(termsetID: string): Promise<ITermNode[]> {
|
||||||
// list all the terms available in this term set by term set id
|
// list all the terms available in this term set by term set id
|
||||||
const termset: IOrderedTermInfo[] = await sp.termStore.sets.getById(termsetID).getAllChildrenAsOrderedTree();
|
const termset: IOrderedTermInfo[] = await this._sp.termStore.sets.getById(termsetID).getAllChildrenAsOrderedTree();
|
||||||
const termnodes: ITermNode[] = [];
|
const termnodes: ITermNode[] = [];
|
||||||
termset.forEach(async ti => {
|
termset.forEach(async ti => {
|
||||||
const tn = this.getTermnode(ti);
|
const tn = this._getTermnode(ti);
|
||||||
termnodes.push(tn);
|
termnodes.push(tn);
|
||||||
});
|
});
|
||||||
return termnodes;
|
return termnodes;
|
||||||
|
@ -31,23 +44,23 @@ export class TaxonomyService {
|
||||||
|
|
||||||
public incorporateFiles (terms: ITermNode[], files: IFileItem[]): ITermNode[] {
|
public incorporateFiles (terms: ITermNode[], files: IFileItem[]): ITermNode[] {
|
||||||
terms.forEach(term => {
|
terms.forEach(term => {
|
||||||
term = this.incorporateFilesIntoTerm(term, files);
|
term = this._incorporateFilesIntoTerm(term, files);
|
||||||
});
|
});
|
||||||
return terms;
|
return terms;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTermnode (term: IOrderedTermInfo): ITermNode {
|
private _getTermnode (term: ITermInfo): ITermNode {
|
||||||
const node: ITermNode = {
|
const node: ITermNode = {
|
||||||
guid: term.id,
|
guid: term.id,
|
||||||
childDocuments: 0,
|
childDocuments: 0,
|
||||||
name: term.defaultLabel,
|
name: term.labels.filter(i => i.isDefault === true)[0].name,
|
||||||
children: [],
|
children: [],
|
||||||
subFiles: []
|
subFiles: []
|
||||||
};
|
};
|
||||||
if (term.childrenCount > 0) {
|
if (term.childrenCount > 0) {
|
||||||
const ctnodes: ITermNode[] = [];
|
const ctnodes: ITermNode[] = [];
|
||||||
term.children.forEach(ct => {
|
term.children.forEach(ct => {
|
||||||
const ctnode: ITermNode = this.getTermnode(ct);
|
const ctnode: ITermNode = this._getTermnode(ct);
|
||||||
node.childDocuments += ctnode.childDocuments;
|
node.childDocuments += ctnode.childDocuments;
|
||||||
ctnodes.push(ctnode);
|
ctnodes.push(ctnode);
|
||||||
});
|
});
|
||||||
|
@ -56,12 +69,12 @@ export class TaxonomyService {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private incorporateFilesIntoTerm (term: ITermNode, files: IFileItem[]): ITermNode {
|
private _incorporateFilesIntoTerm (term: ITermNode, files: IFileItem[]): ITermNode {
|
||||||
term.childDocuments = 0;
|
term.childDocuments = 0;
|
||||||
term.subFiles = [];
|
term.subFiles = [];
|
||||||
if (term.children.length > 0) {
|
if (term.children.length > 0) {
|
||||||
term.children.forEach(ct => {
|
term.children.forEach(ct => {
|
||||||
ct = this.incorporateFilesIntoTerm(ct, files);
|
ct = this._incorporateFilesIntoTerm(ct, files);
|
||||||
term.childDocuments += ct.childDocuments;
|
term.childDocuments += ct.childDocuments;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"requiresCustomScript": false,
|
"requiresCustomScript": false,
|
||||||
"supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"],
|
"supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"],
|
||||||
"supportsThemeVariants": true,
|
"supportsThemeVariants": true,
|
||||||
|
"hiddenFromToolbox": false,
|
||||||
"preconfiguredEntries": [{
|
"preconfiguredEntries": [{
|
||||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||||
"group": { "default": "Other" },
|
"group": { "default": "Other" },
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
IPropertyPaneConfiguration,
|
IPropertyPaneConfiguration,
|
||||||
PropertyPaneTextField
|
PropertyPaneTextField
|
||||||
} from '@microsoft/sp-property-pane';
|
} from '@microsoft/sp-property-pane';
|
||||||
import { sp } from "@pnp/sp/presets/all";
|
|
||||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||||
|
|
||||||
import * as strings from 'TaxonomyFileExplorerWebPartStrings';
|
import * as strings from 'TaxonomyFileExplorerWebPartStrings';
|
||||||
|
@ -18,18 +17,11 @@ export interface ITaxonomyFileExplorerWebPartProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TaxonomyFileExplorerWebPart extends BaseClientSideWebPart<ITaxonomyFileExplorerWebPartProps> {
|
export default class TaxonomyFileExplorerWebPart extends BaseClientSideWebPart<ITaxonomyFileExplorerWebPartProps> {
|
||||||
protected onInit(): Promise<void> {
|
|
||||||
return super.onInit().then(_ => {
|
|
||||||
sp.setup({
|
|
||||||
spfxContext: this.context
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): void {
|
public render(): void {
|
||||||
const element: React.ReactElement<ITaxonomyFileExplorerProps> = React.createElement(
|
const element: React.ReactElement<ITaxonomyFileExplorerProps> = React.createElement(
|
||||||
TaxonomyFileExplorer,
|
TaxonomyFileExplorer,
|
||||||
{
|
{
|
||||||
|
serviceScope: this.context.serviceScope,
|
||||||
fieldName: this.properties.fieldName,
|
fieldName: this.properties.fieldName,
|
||||||
listName: this.properties.listName
|
listName: this.properties.listName
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ import { IFileLabelProps } from './IFileLabelProps';
|
||||||
initializeFileTypeIcons(undefined);
|
initializeFileTypeIcons(undefined);
|
||||||
|
|
||||||
export const FileLabel: React.FC<IFileLabelProps> = (props) => {
|
export const FileLabel: React.FC<IFileLabelProps> = (props) => {
|
||||||
const drag = (ev) => {
|
const drag = React.useCallback((ev)=> {
|
||||||
ev.dataTransfer.setData("text/plain", JSON.stringify(props.file));
|
ev.dataTransfer.setData("text/plain", JSON.stringify(props.file));
|
||||||
};
|
}, [props.file]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={styles.fileLabel} draggable={true} onDragStart={drag}>
|
<li className={styles.fileLabel} draggable={true} onDragStart={drag}>
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { ServiceScope } from "@microsoft/sp-core-library";
|
||||||
|
|
||||||
export interface ITaxonomyFileExplorerProps {
|
export interface ITaxonomyFileExplorerProps {
|
||||||
|
serviceScope: ServiceScope;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
listName: string;
|
listName: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,12 @@ import { ITermNode } from "../../../model/ITermNode";
|
||||||
export interface ITermLabelProps {
|
export interface ITermLabelProps {
|
||||||
node: ITermNode;
|
node: ITermNode;
|
||||||
selectedNode: string;
|
selectedNode: string;
|
||||||
|
collapseAll: boolean;
|
||||||
|
expandAll: boolean;
|
||||||
renderFiles: (files: IFileItem[]) => void;
|
renderFiles: (files: IFileItem[]) => void;
|
||||||
resetChecked: (s: string) => void;
|
resetChecked: (s: string) => void;
|
||||||
addTerm: (file: IFileItem, newValue: string) => void;
|
addTerm: (file: IFileItem, newValue: string) => void;
|
||||||
replaceTerm: (file: IFileItem, newValue: string) => void;
|
replaceTerm: (file: IFileItem, newValue: string) => void;
|
||||||
copyFile: (file: IFileItem, newValue: string) => void;
|
copyFile: (file: IFileItem, newValue: string) => void;
|
||||||
|
uploadFile: (file: any, newValue: string) => void;
|
||||||
}
|
}
|
|
@ -10,14 +10,17 @@
|
||||||
.row {
|
.row {
|
||||||
@include ms-Grid-row;
|
@include ms-Grid-row;
|
||||||
color: "[theme: themePrimary, default: #0078d7]";
|
color: "[theme: themePrimary, default: #0078d7]";
|
||||||
padding: 20px;
|
padding: 8px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
@include ms-Grid-col;
|
@include ms-Grid-col;
|
||||||
@include ms-lg6;
|
@include ms-lg6;
|
||||||
}
|
}
|
||||||
|
.icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding-inline-start: 0px;
|
padding-inline-start: 0px;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { TaxonomyService } from '../../../services/TaxonomyService';
|
||||||
import { SPService } from '../../../services/SPService';
|
import { SPService } from '../../../services/SPService';
|
||||||
import { FileLabel } from './FileLabel';
|
import { FileLabel } from './FileLabel';
|
||||||
import { TermLabel } from './TermLabel';
|
import { TermLabel } from './TermLabel';
|
||||||
|
import { Icon } from 'office-ui-fabric-react';
|
||||||
|
|
||||||
export const TaxonomyFileExplorer: React.FC<ITaxonomyFileExplorerProps> = (props) => {
|
export const TaxonomyFileExplorer: React.FC<ITaxonomyFileExplorerProps> = (props) => {
|
||||||
const [spSvc, setSpSvc] = React.useState<SPService>();
|
const [spSvc, setSpSvc] = React.useState<SPService>();
|
||||||
|
@ -14,12 +15,14 @@ export const TaxonomyFileExplorer: React.FC<ITaxonomyFileExplorerProps> = (props
|
||||||
const [terms, setTerms] = React.useState<ITermNode[]>([]);
|
const [terms, setTerms] = React.useState<ITermNode[]>([]);
|
||||||
const [shownFiles, setShownFiles] = React.useState<IFileItem[]>([]);
|
const [shownFiles, setShownFiles] = React.useState<IFileItem[]>([]);
|
||||||
const [selectedTermnode, setSelectedTermnode] = React.useState<string>("");
|
const [selectedTermnode, setSelectedTermnode] = React.useState<string>("");
|
||||||
|
const [collapseAll, setCollapseAll] = React.useState<boolean>(false);
|
||||||
|
const [expandAll, setExpandAll] = React.useState<boolean>(false);
|
||||||
|
|
||||||
const buildTree = async () => {
|
const buildTree = async () => {
|
||||||
const taxSvc: TaxonomyService = new TaxonomyService();
|
const taxSvc: TaxonomyService = new TaxonomyService(props.serviceScope);
|
||||||
const termsetID = await taxSvc.getTermsetInfo(props.fieldName);
|
const termsetID: string = await taxSvc.getTermsetInfo(props.fieldName);
|
||||||
let termnodetree: ITermNode[];
|
let termnodetree: ITermNode[];
|
||||||
const termnodetreeStr = sessionStorage.getItem(`Termtree_${termsetID}`);
|
const termnodetreeStr: string = sessionStorage.getItem(`Termtree_${termsetID}`);
|
||||||
if (termnodetreeStr === null) {
|
if (termnodetreeStr === null) {
|
||||||
termnodetree = await taxSvc.getTermset(termsetID);
|
termnodetree = await taxSvc.getTermset(termsetID);
|
||||||
sessionStorage.setItem(`Termtree_${termsetID}`, JSON.stringify(termnodetree));
|
sessionStorage.setItem(`Termtree_${termsetID}`, JSON.stringify(termnodetree));
|
||||||
|
@ -28,26 +31,26 @@ export const TaxonomyFileExplorer: React.FC<ITaxonomyFileExplorerProps> = (props
|
||||||
termnodetree = JSON.parse(termnodetreeStr);
|
termnodetree = JSON.parse(termnodetreeStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
const spSrvc: SPService = new SPService(props.listName, props.fieldName);
|
const spSrvc: SPService = new SPService(props.serviceScope, props.listName, props.fieldName);
|
||||||
const files = await spSrvc.getItems(termsetID);
|
const files: IFileItem[] = await spSrvc.getItems(termsetID);
|
||||||
setSpSvc(spSrvc);
|
setSpSvc(spSrvc);
|
||||||
updateFiles(files, termnodetree);
|
updateFiles(files, termnodetree);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateFiles = (files: IFileItem[], termnodetree: ITermNode[]) => {
|
const updateFiles = (files: IFileItem[], termnodetree: ITermNode[]) => {
|
||||||
const taxSvc: TaxonomyService = new TaxonomyService();
|
const taxSvc: TaxonomyService = new TaxonomyService(props.serviceScope);
|
||||||
termnodetree = taxSvc.incorporateFiles(termnodetree, files);
|
termnodetree = taxSvc.incorporateFiles(termnodetree, files);
|
||||||
setFileItems(files);
|
setFileItems(files);
|
||||||
setTerms(termnodetree);
|
setTerms(termnodetree);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderFiles = (files: IFileItem[]) => {
|
const renderFiles = React.useCallback((files: IFileItem[]) => {
|
||||||
setShownFiles(files);
|
setShownFiles(files);
|
||||||
};
|
},[setShownFiles]);
|
||||||
|
|
||||||
const resetChecked = (newNodeID: string) => {
|
const resetChecked = React.useCallback((newNodeID: string) => {
|
||||||
setSelectedTermnode(newNodeID);
|
setSelectedTermnode(newNodeID);
|
||||||
};
|
},[setSelectedTermnode]);
|
||||||
|
|
||||||
const reloadFiles = (file: IFileItem) => {
|
const reloadFiles = (file: IFileItem) => {
|
||||||
const newFiles: IFileItem[] = [];
|
const newFiles: IFileItem[] = [];
|
||||||
|
@ -67,45 +70,66 @@ export const TaxonomyFileExplorer: React.FC<ITaxonomyFileExplorerProps> = (props
|
||||||
updateFiles(newFiles, terms);
|
updateFiles(newFiles, terms);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addTerm = (file: IFileItem, newTaxonomyValue: string) => {
|
const addTerm = React.useCallback((file: IFileItem, newTaxonomyValue: string) => {
|
||||||
spSvc.updateTaxonomyItemByAdd(file, props.fieldName, newTaxonomyValue);
|
spSvc.updateTaxonomyItemByAdd(file, props.fieldName, newTaxonomyValue);
|
||||||
reloadFiles(file);
|
reloadFiles(file);
|
||||||
};
|
},[spSvc, fileItems, terms]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const replaceTerm = (file: IFileItem, newTaxonomyValue: string) => {
|
const replaceTerm = React.useCallback((file: IFileItem, newTaxonomyValue: string) => {
|
||||||
spSvc.updateTaxonomyItemByReplace(file, props.fieldName, newTaxonomyValue);
|
spSvc.updateTaxonomyItemByReplace(file, props.fieldName, newTaxonomyValue);
|
||||||
reloadFiles(file);
|
reloadFiles(file);
|
||||||
};
|
},[spSvc, fileItems, terms]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const copyFile = async (file: IFileItem, newTaxonomyValue: string) => {
|
const copyFile = React.useCallback(async (file: IFileItem, newTaxonomyValue: string) => {
|
||||||
const newFile = await spSvc.newTaxonomyItemByCopy(file, props.fieldName, newTaxonomyValue);
|
const newFile = await spSvc.newTaxonomyItemByCopy(file, props.fieldName, newTaxonomyValue);
|
||||||
loadNewFiles(newFile);
|
loadNewFiles(newFile);
|
||||||
};
|
},[spSvc, fileItems, terms]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const uploadFile = React.useCallback(async (file: any, newTaxonomyValue: string) => {
|
||||||
|
const newFile = await spSvc.newTaxonomyItemByUpload(file, props.fieldName, newTaxonomyValue)
|
||||||
|
loadNewFiles(newFile);
|
||||||
|
},[spSvc, fileItems, terms]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const expandAllTerms = React.useCallback(() => {
|
||||||
|
setExpandAll(true);
|
||||||
|
setCollapseAll(false);
|
||||||
|
},[setExpandAll, setCollapseAll]);
|
||||||
|
|
||||||
|
const collapseAllTerms = React.useCallback(() => {
|
||||||
|
setCollapseAll(true);
|
||||||
|
setExpandAll(false);
|
||||||
|
},[setExpandAll, setCollapseAll]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
buildTree();
|
buildTree(); // eslint-disable-line @typescript-eslint/no-floating-promises
|
||||||
}, []);
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.taxonomyFileExplorer }>
|
<div className={ styles.taxonomyFileExplorer }>
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<div className={ styles.row }>
|
<div className={ styles.row }>
|
||||||
<div className={ styles.column }>
|
<div className={ styles.column }>
|
||||||
|
<Icon className={styles.icon} iconName="ExploreContent" onClick={expandAllTerms} /> {/* Alt: DoubleChevronRight */}
|
||||||
|
<Icon className={styles.icon} iconName="CollapseContent" onClick={collapseAllTerms} /> {/* Alt: DoubleChevronDown */}
|
||||||
<ul>
|
<ul>
|
||||||
{terms.map(nc => { return <TermLabel node={nc}
|
{terms.map(nc => { return <TermLabel node={nc}
|
||||||
|
key={nc.guid}
|
||||||
renderFiles={renderFiles}
|
renderFiles={renderFiles}
|
||||||
resetChecked={resetChecked}
|
resetChecked={resetChecked}
|
||||||
selectedNode={selectedTermnode}
|
selectedNode={selectedTermnode}
|
||||||
|
collapseAll={collapseAll}
|
||||||
|
expandAll={expandAll}
|
||||||
addTerm={addTerm}
|
addTerm={addTerm}
|
||||||
replaceTerm={replaceTerm}
|
replaceTerm={replaceTerm}
|
||||||
copyFile={copyFile} />; })}
|
copyFile={copyFile}
|
||||||
|
uploadFile={uploadFile} />; })}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.column }>
|
<div className={ styles.column }>
|
||||||
{shownFiles.length > 0 &&
|
{shownFiles.length > 0 &&
|
||||||
<ul>
|
<ul>
|
||||||
{shownFiles.map(f => {
|
{shownFiles.map(f => {
|
||||||
return <FileLabel file={f} />;
|
return <FileLabel file={f} key={f.id} />;
|
||||||
})}
|
})}
|
||||||
</ul>}
|
</ul>}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
.checkedLabel {
|
.checkedLabel {
|
||||||
border: 1px dotted "[theme: themePrimary, default: #0078d7]";
|
border: 1px dotted "[theme: themePrimary, default: #0078d7]";
|
||||||
}
|
}
|
||||||
|
.dragEnter {
|
||||||
|
background-color: "[theme: themeLight, default: #c7e0f4]";
|
||||||
|
}
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -11,47 +11,70 @@ export const TermLabel: React.FC<ITermLabelProps> = (props) => {
|
||||||
const [countDocuments, setCountDocuments] = React.useState<number>(props.node.childDocuments);
|
const [countDocuments, setCountDocuments] = React.useState<number>(props.node.childDocuments);
|
||||||
const [showContextualMenu, setShowContextualMenu] = React.useState<boolean>(false);
|
const [showContextualMenu, setShowContextualMenu] = React.useState<boolean>(false);
|
||||||
const [droppedFile, setDroppedFile] = React.useState<IFileItem>();
|
const [droppedFile, setDroppedFile] = React.useState<IFileItem>();
|
||||||
|
const [dragEntered, setDragEntered] = React.useState<boolean>(false);
|
||||||
|
|
||||||
const toggleIcon = () => {
|
const toggleIcon = React.useCallback(() => {
|
||||||
setShowChildren(!showChildren);
|
setShowChildren(!showChildren);
|
||||||
};
|
},[setShowChildren,showChildren]);
|
||||||
|
|
||||||
const nodeSelected = () => {
|
const nodeSelected = React.useCallback(() => {
|
||||||
props.resetChecked(props.node.guid);
|
props.resetChecked(props.node.guid);
|
||||||
props.renderFiles(props.node.subFiles);
|
props.renderFiles(props.node.subFiles);
|
||||||
};
|
},[]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const hideContextualMenu = () => {
|
const hideContextualMenu = React.useCallback(() => {
|
||||||
setShowContextualMenu(false);
|
setShowContextualMenu(false);
|
||||||
};
|
},[setShowContextualMenu]);
|
||||||
|
|
||||||
const drop = (ev) => {
|
const uploadWithNewTerm = React.useCallback((file: any) => {
|
||||||
ev.preventDefault();
|
const newTaxonomyValue: string = `${props.node.name}|${props.node.guid}`;
|
||||||
var data = ev.dataTransfer.getData("text");
|
props.uploadFile(file, newTaxonomyValue);
|
||||||
const file: IFileItem = JSON.parse(data);
|
},[]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
setDroppedFile(file);
|
|
||||||
if (ev.ctrlKey) {
|
|
||||||
setShowContextualMenu(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
addNewTerm(file); // Default option: Simply add the new (target) term to existing ones
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const dragOver = (ev) => {
|
const addNewTerm = React.useCallback((file: IFileItem) => {
|
||||||
ev.preventDefault();
|
const newTaxonomyValue: string = `${props.node.name}|${props.node.guid}`;
|
||||||
};
|
|
||||||
|
|
||||||
const addNewTerm = (file: IFileItem) => {
|
|
||||||
const newTaxonomyValue = `${props.node.name}|${props.node.guid}`;
|
|
||||||
file.termGuid.push(props.node.guid);
|
file.termGuid.push(props.node.guid);
|
||||||
file.taxValue.push(newTaxonomyValue);
|
file.taxValue.push(newTaxonomyValue);
|
||||||
console.log(file);
|
|
||||||
props.addTerm(file, newTaxonomyValue);
|
props.addTerm(file, newTaxonomyValue);
|
||||||
};
|
},[]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const drop = React.useCallback((ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
// Drop is a file or a FileLabel
|
||||||
|
if (ev.dataTransfer.types.indexOf('Files') > -1) {
|
||||||
|
const dt = ev.dataTransfer;
|
||||||
|
const files = Array.prototype.slice.call(dt.files);
|
||||||
|
files.forEach(fileToUpload => {
|
||||||
|
uploadWithNewTerm(fileToUpload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const data: string = ev.dataTransfer.getData("text");
|
||||||
|
const file: IFileItem = JSON.parse(data);
|
||||||
|
setDroppedFile(file);
|
||||||
|
if (ev.ctrlKey) {
|
||||||
|
setShowContextualMenu(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addNewTerm(file); // Default option: Simply add the new (target) term to existing ones
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},[uploadWithNewTerm, addNewTerm]);
|
||||||
|
|
||||||
|
const dragOver = React.useCallback((ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
},[]);
|
||||||
|
|
||||||
|
const dragEnter = React.useCallback((ev) => {
|
||||||
|
setDragEntered(true);
|
||||||
|
},[setDragEntered]);
|
||||||
|
|
||||||
|
const dragLeave = React.useCallback((ev) => {
|
||||||
|
setDragEntered(false);
|
||||||
|
},[setDragEntered]);
|
||||||
|
|
||||||
const replaceByNewTerm = (file: IFileItem) => {
|
const replaceByNewTerm = (file: IFileItem) => {
|
||||||
const newTaxonomyValue = `${props.node.name}|${props.node.guid}`;
|
const newTaxonomyValue: string = `${props.node.name}|${props.node.guid}`;
|
||||||
file.termGuid = [props.node.guid];
|
file.termGuid = [props.node.guid];
|
||||||
file.taxValue = [newTaxonomyValue];
|
file.taxValue = [newTaxonomyValue];
|
||||||
console.log(file);
|
console.log(file);
|
||||||
|
@ -59,12 +82,11 @@ export const TermLabel: React.FC<ITermLabelProps> = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyWithNewTerm = (file: IFileItem) => {
|
const copyWithNewTerm = (file: IFileItem) => {
|
||||||
const newTaxonomyValue = `${props.node.name}|${props.node.guid}`;
|
const newTaxonomyValue: string = `${props.node.name}|${props.node.guid}`;
|
||||||
console.log(file);
|
|
||||||
props.copyFile(file, newTaxonomyValue);
|
props.copyFile(file, newTaxonomyValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentExpandIcon = showChildren? <Icon className={styles.icon} iconName="ChevronDown" onClick={toggleIcon} />:<Icon className={styles.icon} iconName="ChevronRight" onClick={toggleIcon} />;
|
const currentExpandIcon: JSX.Element = showChildren? <Icon className={styles.icon} iconName="ChevronDown" onClick={toggleIcon} />:<Icon className={styles.icon} iconName="ChevronRight" onClick={toggleIcon} />;
|
||||||
const menuItems: IContextualMenuItem[] = [
|
const menuItems: IContextualMenuItem[] = [
|
||||||
{
|
{
|
||||||
key: 'copyItem',
|
key: 'copyItem',
|
||||||
|
@ -81,6 +103,14 @@ export const TermLabel: React.FC<ITermLabelProps> = (props) => {
|
||||||
text: 'Add new term (Link)',
|
text: 'Add new term (Link)',
|
||||||
onClick: () => addNewTerm(droppedFile)
|
onClick: () => addNewTerm(droppedFile)
|
||||||
}];
|
}];
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (props.expandAll) {
|
||||||
|
setShowChildren(true);
|
||||||
|
}
|
||||||
|
if (props.collapseAll) {
|
||||||
|
setShowChildren(false);
|
||||||
|
}
|
||||||
|
}, [props.collapseAll, props.expandAll]);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (props.selectedNode===props.node.guid) {
|
if (props.selectedNode===props.node.guid) {
|
||||||
props.renderFiles(props.node.subFiles);
|
props.renderFiles(props.node.subFiles);
|
||||||
|
@ -88,32 +118,42 @@ export const TermLabel: React.FC<ITermLabelProps> = (props) => {
|
||||||
if (props.node.childDocuments !== countDocuments) {
|
if (props.node.childDocuments !== countDocuments) {
|
||||||
setCountDocuments(props.node.childDocuments);
|
setCountDocuments(props.node.childDocuments);
|
||||||
}
|
}
|
||||||
}, [props.node.subFiles]);
|
}, [props.node.subFiles]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
return (
|
return (
|
||||||
<li className={styles.termLabel}>
|
<li className={styles.termLabel}>
|
||||||
<div ref={linkRef} className={`${styles.label} ${props.selectedNode===props.node.guid ? styles.checkedLabel : ""}`} onClick={nodeSelected} onDrop={drop} onDragOver={dragOver}>
|
<div ref={linkRef} className={`${styles.label} ${props.selectedNode===props.node.guid ? styles.checkedLabel : ""}
|
||||||
<label>
|
${dragEntered ? styles.dragEnter : ""}`}
|
||||||
{props.node.children.length > 0 ? currentExpandIcon : <i className={styles.emptyicon}> </i>}
|
onClick={nodeSelected}
|
||||||
<Icon className={styles.icon} iconName="FabricFolder" />
|
onDrop={drop}
|
||||||
{props.node.name}{countDocuments>0?<span className={styles.fileCount}>{countDocuments}</span>:""}
|
onDragOver={dragOver}
|
||||||
</label>
|
onDragEnter={dragEnter}
|
||||||
</div>
|
onDragLeave={dragLeave}>
|
||||||
<ContextualMenu
|
<label>
|
||||||
items={menuItems}
|
{props.node.children.length > 0 ? currentExpandIcon : <i className={styles.emptyicon}> </i>}
|
||||||
hidden={!showContextualMenu}
|
<Icon className={styles.icon} iconName="FabricFolder" />
|
||||||
target={linkRef}
|
{props.node.name}{countDocuments>0?<span className={styles.fileCount}>{countDocuments}</span>:""}
|
||||||
onItemClick={hideContextualMenu}
|
</label>
|
||||||
onDismiss={hideContextualMenu}
|
</div>
|
||||||
/>
|
<ContextualMenu
|
||||||
{showChildren && <ul className={`${props.node.children.length > 0 ? styles.liFilled : ""}`}>
|
items={menuItems}
|
||||||
{props.node.children.map(nc => { return <TermLabel node={nc}
|
hidden={!showContextualMenu}
|
||||||
renderFiles={props.renderFiles}
|
target={linkRef}
|
||||||
resetChecked={props.resetChecked}
|
onItemClick={hideContextualMenu}
|
||||||
selectedNode={props.selectedNode}
|
onDismiss={hideContextualMenu}
|
||||||
addTerm={props.addTerm}
|
/>
|
||||||
replaceTerm={props.replaceTerm}
|
{showChildren && <ul className={`${props.node.children.length > 0 ? styles.liFilled : ""}`}>
|
||||||
copyFile={props.copyFile} />; })}
|
{props.node.children.map(nc => { return <TermLabel node={nc}
|
||||||
</ul>}
|
key={nc.guid}
|
||||||
</li>
|
renderFiles={props.renderFiles}
|
||||||
|
resetChecked={props.resetChecked}
|
||||||
|
selectedNode={props.selectedNode}
|
||||||
|
collapseAll={props.collapseAll}
|
||||||
|
expandAll={props.expandAll}
|
||||||
|
addTerm={props.addTerm}
|
||||||
|
replaceTerm={props.replaceTerm}
|
||||||
|
copyFile={props.copyFile}
|
||||||
|
uploadFile={props.uploadFile} />; })}
|
||||||
|
</ul>}
|
||||||
|
</li>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.9/includes/tsconfig-web.json",
|
"extends": "./node_modules/@microsoft/rush-stack-compiler-4.5/includes/tsconfig-web.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
|
Loading…
Reference in New Issue