From 3db28055a3ab8ad3ba7ca415e9943d68a02ee19f Mon Sep 17 00:00:00 2001 From: Alex Terentiev Date: Sun, 11 Jun 2023 14:07:10 -0400 Subject: [PATCH 01/15] pnp v3 in progress --- package-lock.json | 155 ++- package.json | 3 +- src/common/Interfaces.ts | 3 +- src/common/utilities/FieldRendererHelper.ts | 5 +- src/common/utilities/PnPJSConfig.ts | 18 + src/common/utilities/SPHelper.ts | 905 ++++++++++-------- src/controls/carousel/Carousel.tsx | 2 +- src/controls/dynamicForm/DynamicForm.tsx | 51 +- .../dynamicForm/dynamicField/DynamicField.tsx | 7 - .../FieldLookupRenderer.tsx | 5 +- .../fieldUserRenderer/FieldUserRenderer.tsx | 341 ++++--- .../ModernTaxonomyPicker.tsx | 2 - src/services/FolderExplorerService.ts | 16 +- src/services/PeopleSearchService.ts | 13 +- src/services/SPTaxonomyService.ts | 12 +- 15 files changed, 880 insertions(+), 658 deletions(-) create mode 100644 src/common/utilities/PnPJSConfig.ts diff --git a/package-lock.json b/package-lock.json index 070067bcb..ddd42b6cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,8 @@ "@microsoft/sp-property-pane": "1.17.3", "@microsoft/sp-webpart-base": "1.17.3", "@monaco-editor/loader": "^1.3.1", - "@pnp/sp": "2.5.0", + "@pnp/queryable": "^3.16.0", + "@pnp/sp": "^3.16.0", "@pnp/telemetry-js": "2.0.0", "@popperjs/core": "2.5.4", "@uifabric/icons": "7.9.5", @@ -9682,68 +9683,59 @@ "node": ">= 8" } }, - "node_modules/@pnp/common": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@pnp/common/-/common-2.5.0.tgz", - "integrity": "sha512-ea4zTNC3sjLolrHZXP+/2SrJM+yC8PygmPW/yRfgbErdvdwYMUSogT69dW+NUaqhkfYZfkkAoWn42irlLMSpdw==", + "node_modules/@pnp/core": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@pnp/core/-/core-3.16.0.tgz", + "integrity": "sha512-nKUAhyIn2NDvsG9BHcRjdUfeql814s57R9aM+SvqF10i2B7PzSyEnqZpbKuc3e/4K9ZSrXlA539B6aNGGRNmFw==", "dependencies": { - "tslib": "2.2.0" + "tslib": "2.4.1" }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/patrick-rodgers/" - } - }, - "node_modules/@pnp/common/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" - }, - "node_modules/@pnp/logging": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@pnp/logging/-/logging-2.5.0.tgz", - "integrity": "sha512-SnmMCN6oADjiHKAIR23FfTqXeQZeXPBnWeVfyZAgzJfRn9uEQoUlkyET3jHjl9kkrFOVkiOD1CRI7TWMIxURbA==", - "dependencies": { - "tslib": "2.2.0" + "engines": { + "node": ">=14.15.1" }, "funding": { "type": "individual", "url": "https://github.com/sponsors/patrick-rodgers/" } }, - "node_modules/@pnp/logging/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "node_modules/@pnp/core/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, - "node_modules/@pnp/odata": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@pnp/odata/-/odata-2.5.0.tgz", - "integrity": "sha512-AeP01jDvnkiUVn7V+4FT07chz+G/yzrJDH0Gk+qzujJ393ZO6FwJpJEiOCRh9cxF48gqSj/f7r/IIyDHe0+IpQ==", + "node_modules/@pnp/queryable": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@pnp/queryable/-/queryable-3.16.0.tgz", + "integrity": "sha512-drKaMGKaFfBbuR2oHWi2EHMImFr3hzf6tM7a64BJNbwyeVNyzhMert/lMsdyV/PmfNR6fIiIjw3lE8XkHFKOmg==", "dependencies": { - "@pnp/common": "2.5.0", - "@pnp/logging": "2.5.0", - "tslib": "2.2.0" + "@pnp/core": "3.16.0", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=14.15.1" }, "funding": { "type": "individual", "url": "https://github.com/sponsors/patrick-rodgers/" } }, - "node_modules/@pnp/odata/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "node_modules/@pnp/queryable/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, "node_modules/@pnp/sp": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@pnp/sp/-/sp-2.5.0.tgz", - "integrity": "sha512-4s2p+X5qvkXR72NViKb8DIfC+pvj/a3psZ3Im5PRIan2ErMtu9ch3Lb9nkSaMCF3NTJxWOhkUQ/R6tx8ApaUkg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@pnp/sp/-/sp-3.16.0.tgz", + "integrity": "sha512-wD3vzmfZcl9CShctuT/rDWs4jePpKgvlIoyevQV0xSNkyhSaqbf71GgA+JTvPOHutRvUlyOhN1g7wNCKaMxWCw==", + "hasInstallScript": true, "dependencies": { - "@pnp/common": "2.5.0", - "@pnp/logging": "2.5.0", - "@pnp/odata": "2.5.0", - "tslib": "2.2.0" + "@pnp/core": "3.16.0", + "@pnp/queryable": "3.16.0", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=14.15.1" }, "funding": { "type": "individual", @@ -9751,9 +9743,9 @@ } }, "node_modules/@pnp/sp/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, "node_modules/@pnp/telemetry-js": { "version": "2.0.0", @@ -51269,68 +51261,51 @@ } } }, - "@pnp/common": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@pnp/common/-/common-2.5.0.tgz", - "integrity": "sha512-ea4zTNC3sjLolrHZXP+/2SrJM+yC8PygmPW/yRfgbErdvdwYMUSogT69dW+NUaqhkfYZfkkAoWn42irlLMSpdw==", + "@pnp/core": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@pnp/core/-/core-3.16.0.tgz", + "integrity": "sha512-nKUAhyIn2NDvsG9BHcRjdUfeql814s57R9aM+SvqF10i2B7PzSyEnqZpbKuc3e/4K9ZSrXlA539B6aNGGRNmFw==", "requires": { - "tslib": "2.2.0" + "tslib": "2.4.1" }, "dependencies": { "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" - } - } - }, - "@pnp/logging": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@pnp/logging/-/logging-2.5.0.tgz", - "integrity": "sha512-SnmMCN6oADjiHKAIR23FfTqXeQZeXPBnWeVfyZAgzJfRn9uEQoUlkyET3jHjl9kkrFOVkiOD1CRI7TWMIxURbA==", - "requires": { - "tslib": "2.2.0" - }, - "dependencies": { - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" } } }, - "@pnp/odata": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@pnp/odata/-/odata-2.5.0.tgz", - "integrity": "sha512-AeP01jDvnkiUVn7V+4FT07chz+G/yzrJDH0Gk+qzujJ393ZO6FwJpJEiOCRh9cxF48gqSj/f7r/IIyDHe0+IpQ==", + "@pnp/queryable": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@pnp/queryable/-/queryable-3.16.0.tgz", + "integrity": "sha512-drKaMGKaFfBbuR2oHWi2EHMImFr3hzf6tM7a64BJNbwyeVNyzhMert/lMsdyV/PmfNR6fIiIjw3lE8XkHFKOmg==", "requires": { - "@pnp/common": "2.5.0", - "@pnp/logging": "2.5.0", - "tslib": "2.2.0" + "@pnp/core": "3.16.0", + "tslib": "2.4.1" }, "dependencies": { "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" } } }, "@pnp/sp": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@pnp/sp/-/sp-2.5.0.tgz", - "integrity": "sha512-4s2p+X5qvkXR72NViKb8DIfC+pvj/a3psZ3Im5PRIan2ErMtu9ch3Lb9nkSaMCF3NTJxWOhkUQ/R6tx8ApaUkg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@pnp/sp/-/sp-3.16.0.tgz", + "integrity": "sha512-wD3vzmfZcl9CShctuT/rDWs4jePpKgvlIoyevQV0xSNkyhSaqbf71GgA+JTvPOHutRvUlyOhN1g7wNCKaMxWCw==", "requires": { - "@pnp/common": "2.5.0", - "@pnp/logging": "2.5.0", - "@pnp/odata": "2.5.0", - "tslib": "2.2.0" + "@pnp/core": "3.16.0", + "@pnp/queryable": "3.16.0", + "tslib": "2.4.1" }, "dependencies": { "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" } } }, diff --git a/package.json b/package.json index 9e623c04d..91d4fc3b9 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "@microsoft/sp-property-pane": "1.17.3", "@microsoft/sp-webpart-base": "1.17.3", "@monaco-editor/loader": "^1.3.1", - "@pnp/sp": "2.5.0", + "@pnp/queryable": "^3.16.0", + "@pnp/sp": "^3.16.0", "@pnp/telemetry-js": "2.0.0", "@popperjs/core": "2.5.4", "@uifabric/icons": "7.9.5", diff --git a/src/common/Interfaces.ts b/src/common/Interfaces.ts index 0809f722a..ef7a9f4f3 100644 --- a/src/common/Interfaces.ts +++ b/src/common/Interfaces.ts @@ -1,4 +1,5 @@ import { SPHttpClient } from '@microsoft/sp-http'; +import { BaseComponentContext } from '@microsoft/sp-component-base'; import { PageContext, SPField } from '@microsoft/sp-page-context'; import { ListViewAccessor } from "@microsoft/sp-listview-extensibility"; import { ISPField } from './SPEntities'; @@ -26,7 +27,7 @@ export interface IFields { * Parent of all props interfaces that needs context */ export interface IProps { - context: IContext; + context: BaseComponentContext; } export interface IHubSiteData { diff --git a/src/common/utilities/FieldRendererHelper.ts b/src/common/utilities/FieldRendererHelper.ts index c3ac979b4..d7953a22a 100644 --- a/src/common/utilities/FieldRendererHelper.ts +++ b/src/common/utilities/FieldRendererHelper.ts @@ -2,11 +2,10 @@ import * as React from 'react'; import { ISPFieldLookupValue, ITerm, IPrincipal } from '../SPEntities'; import { FieldTextRenderer } from '../../controls/fields/fieldTextRenderer/FieldTextRenderer'; import { FieldDateRenderer } from '../../controls/fields/fieldDateRenderer/FieldDateRenderer'; -import { ListItemAccessor } from '@microsoft/sp-listview-extensibility'; +import { FieldCustomizerContext, ListItemAccessor } from '@microsoft/sp-listview-extensibility'; import { SPHelper } from './SPHelper'; import { FieldTitleRenderer } from '../../controls/fields/fieldTitleRenderer/FieldTitleRenderer'; import { SPField } from '@microsoft/sp-page-context'; -import { IContext } from '../Interfaces'; import { GeneralHelper } from './GeneralHelper'; import { FieldLookupRenderer } from '../../controls/fields/fieldLookupRenderer/FieldLookupRenderer'; import { FieldUrlRenderer } from '../../controls/fields/fieldUrlRenderer/FieldUrlRenderer'; @@ -29,7 +28,7 @@ export class FieldRendererHelper { * @param listItem Current list item * @param context Customizer context */ - public static getFieldRenderer(fieldValue: any, props: IFieldRendererProps, listItem: ListItemAccessor, context: IContext): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any + public static getFieldRenderer(fieldValue: any, props: IFieldRendererProps, listItem: ListItemAccessor, context: FieldCustomizerContext): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any return new Promise(resolve => { const field: SPField = context.field; const listId: string = context.pageContext.list.id.toString(); diff --git a/src/common/utilities/PnPJSConfig.ts b/src/common/utilities/PnPJSConfig.ts new file mode 100644 index 000000000..c6651c409 --- /dev/null +++ b/src/common/utilities/PnPJSConfig.ts @@ -0,0 +1,18 @@ +import { BaseComponentContext } from "@microsoft/sp-component-base"; + +// import pnp +import { ISPFXContext, spfi, SPFI, SPFx } from "@pnp/sp"; +import "@pnp/sp/webs"; +import "@pnp/sp/lists"; +import "@pnp/sp/items"; +import "@pnp/sp/batching"; + +// eslint-disable-next-line no-var +let _sp: SPFI = null; + +export const getSP = (context?: BaseComponentContext | ISPFXContext, webAbsoluteUrl?: string): SPFI => { + if (!!context) { + _sp = spfi(webAbsoluteUrl).using(SPFx(context)); + } + return _sp; +}; \ No newline at end of file diff --git a/src/common/utilities/SPHelper.ts b/src/common/utilities/SPHelper.ts index 6cdeb9ed4..89ba6f5a5 100644 --- a/src/common/utilities/SPHelper.ts +++ b/src/common/utilities/SPHelper.ts @@ -1,17 +1,28 @@ -import { IContext, IFields } from '../Interfaces'; -import { GeneralHelper } from './GeneralHelper'; -import { ISPField, ISPFieldLookupValue, IPrincipal, ITerm } from '../SPEntities'; -import * as Constants from '../Constants'; -import { ListItemAccessor } from '@microsoft/sp-listview-extensibility'; -import { SPField } from '@microsoft/sp-page-context'; -import { sp } from '@pnp/sp'; -import '@pnp/sp/fields'; -import { SPHttpClient } from '@microsoft/sp-http'; -import { IFieldInfo } from '@pnp/sp/fields'; -import '@pnp/sp/site-users/web'; -import '@pnp/sp/webs'; +import { IFields } from "../Interfaces"; +import { GeneralHelper } from "./GeneralHelper"; +import { + ISPField, + ISPFieldLookupValue, + IPrincipal, + ITerm, +} from "../SPEntities"; +import * as Constants from "../Constants"; +import { + FieldCustomizerContext, + ListItemAccessor, +} from "@microsoft/sp-listview-extensibility"; +import { SPField } from "@microsoft/sp-page-context"; +import { SPFI } from "@pnp/sp"; +import "@pnp/sp/fields"; +import { SPHttpClient } from "@microsoft/sp-http"; +import { IFieldInfo } from "@pnp/sp/fields"; +import "@pnp/sp/site-users/web"; +import "@pnp/sp/sites"; +import "@pnp/sp/webs"; import "@pnp/sp/lists"; -import { ISiteUserInfo } from '@pnp/sp/site-users/types'; +import { ISiteUserInfo } from "@pnp/sp/site-users/types"; +import { getSP } from "./PnPJSConfig"; +import { BaseComponentContext } from "@microsoft/sp-component-base"; interface IFieldLookupInfo extends IFieldInfo { LookupWebId: string; @@ -22,411 +33,521 @@ interface IFieldLookupInfo extends IFieldInfo { * Helper class to work with SharePoint objects and entities */ export class SPHelper { + /** + * Gets field's Real Name from FieldNamesMapping + * @param columnName current field name + */ + public static getStoredFieldName(columnName: string): string { + if (!columnName) return ""; + + return Constants.FieldNamesMapping[columnName] + ? Constants.FieldNamesMapping[columnName].storedName + : columnName; + } + + /** + * Gets Field's text + * @param fieldValue field value as it appears in Field Customizer's onRenderCell event + * @param listItem List Item accessor + * @param context Customizer's context + */ + public static getFieldText( + fieldValue: any, // eslint-disable-line @typescript-eslint/no-explicit-any + listItem: ListItemAccessor, + context: BaseComponentContext + ): Promise { + // eslint-disable-line @typescript-eslint/no-explicit-any + return new Promise((resolve) => { + const fieldCustomizerContext: FieldCustomizerContext = + context as FieldCustomizerContext; + const field: SPField = fieldCustomizerContext.field; + + if (!field) { + resolve(""); + return; + } - /** - * Gets field's Real Name from FieldNamesMapping - * @param columnName current field name - */ - public static getStoredFieldName(columnName: string): string { - if (!columnName) - return ''; + const fieldName: string = field.internalName; //this.getFieldNameById(field.id.toString()); + const fieldType: string = field.fieldType; + const strFieldValue: string = fieldValue ? fieldValue.toString() : ""; + let friendlyDisplay: string | undefined; + let titles: string[] | undefined; + let users: IPrincipal[] | undefined; + let lookupValues: ISPFieldLookupValue[] | undefined; + let lookupTexts: string[] | undefined; + let isImage: boolean = false; + let terms: ITerm[] | undefined; + let termTexts: string[] | undefined; + let storedName: string | undefined; + + switch (fieldType) { + case "Note": + SPHelper.getFieldProperty( + field.id.toString(), + "RichText", + context, + false + ) + .then((richText) => { + const isRichText: boolean = richText === "TRUE"; + if (isRichText) { + resolve(GeneralHelper.getTextFromHTML(strFieldValue)); + } + resolve(fieldValue); + }) + .catch(() => { + /* no-op; */ + }); + break; + case "DateTime": + friendlyDisplay = SPHelper.getRowItemValueByName( + listItem, + `${fieldName}.FriendlyDisplay` + ); + resolve( + friendlyDisplay + ? GeneralHelper.getRelativeDateTimeString(friendlyDisplay) + : strFieldValue + ); + break; + case "User": + case "UserMulti": + titles = []; + users = fieldValue; + + if (!users) { + resolve(""); + } + + for (let i = 0, len = users.length; i < len; i++) { + titles.push(users[i].title); + } + resolve(titles.join("\n")); + break; + case "Lookup": + case "LookupMulti": + lookupValues = fieldValue as ISPFieldLookupValue[]; + + if (!lookupValues) { + resolve(""); + } + + lookupTexts = []; + for (let i = 0, len = lookupValues.length; i < len; i++) { + lookupTexts.push(lookupValues[i].lookupValue); + } + resolve(lookupTexts.join("\n")); + break; + case "URL": + SPHelper.getFieldProperty( + field.id.toString(), + "Format", + context, + true + ) + .then((format) => { + isImage = format === "Image"; + if (isImage) { + resolve(""); + } + resolve( + SPHelper.getRowItemValueByName(listItem, `${fieldName}.desc`) + ); + }) + .catch(() => { + /* no-op; */ + }); + break; + case "Taxonomy": + case "TaxonomyFieldType": + case "TaxonomyFieldTypeMulti": + terms = Array.isArray(fieldValue) + ? fieldValue + : [fieldValue]; + + if (!terms) { + resolve(""); + } + + termTexts = []; + for (let i = 0, len = terms.length; i < len; i++) { + termTexts.push(terms[i].Label); + } + resolve(termTexts.join("\n")); + break; + case "Attachments": + resolve(""); + break; + case "Computed": + storedName = this.getStoredFieldName(fieldName); + if (storedName === "URL") { + resolve( + this.getRowItemValueByName(listItem, "URL.desc") || strFieldValue + ); + } + resolve(strFieldValue); + break; + default: + resolve(strFieldValue); + } + }); + } + + /** + * Gets property of the Field by Field's ID and Property Name + * @param fieldId Field's ID + * @param propertyName Property name + * @param context SPFx context + * @param fromSchemaXml true if the field should be read from Field Schema Xml + */ + public static getFieldProperty( + fieldId: string, + propertyName: string, + context: BaseComponentContext, + fromSchemaXml: boolean + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return new Promise((resolve) => { + let loadedViewFields: { [viewId: string]: IFields } = + SPHelper._getLoadedViewFieldsFromStorage(); + const viewId: string = SPHelper.getPageViewId(context); + + if (!loadedViewFields) { + loadedViewFields = {}; + } - return Constants.FieldNamesMapping[columnName] ? Constants.FieldNamesMapping[columnName].storedName : columnName; - } + if (!loadedViewFields[viewId]) { + loadedViewFields[viewId] = {}; + } - /** - * Gets Field's text - * @param fieldValue field value as it appears in Field Customizer's onRenderCell event - * @param listItem List Item accessor - * @param context Customizer's context - */ - public static getFieldText(fieldValue: any, listItem: ListItemAccessor, context: IContext): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any - return new Promise(resolve => { - const field: SPField = context.field; - - if (!field) { - resolve(''); - return; - } + let field: ISPField = loadedViewFields[viewId][fieldId]; + if (!field) { + field = { + Id: fieldId, + }; + } - const fieldName: string = field.internalName; //this.getFieldNameById(field.id.toString()); - const fieldType: string = field.fieldType; - const strFieldValue: string = fieldValue ? fieldValue.toString() : ''; - let friendlyDisplay: string | undefined; - let titles: string[] | undefined; - let users: IPrincipal[] | undefined; - let lookupValues: ISPFieldLookupValue[] | undefined; - let lookupTexts: string[] | undefined; - let isImage: boolean = false; - let terms: ITerm[] | undefined; - let termTexts: string[] | undefined; - let storedName: string | undefined; - - switch (fieldType) { - case 'Note': - SPHelper.getFieldProperty(field.id.toString(), "RichText", context, false).then(richText => { - const isRichText: boolean = richText === 'TRUE'; - if (isRichText) { - resolve(GeneralHelper.getTextFromHTML(strFieldValue)); - } - resolve(fieldValue); - }).catch(() => { /* no-op; */ }); - break; - case 'DateTime': - friendlyDisplay = SPHelper.getRowItemValueByName(listItem, `${fieldName}.FriendlyDisplay`); - resolve(friendlyDisplay ? GeneralHelper.getRelativeDateTimeString(friendlyDisplay) : strFieldValue); - break; - case 'User': - case 'UserMulti': - titles = []; - users = fieldValue; - - if (!users) { - resolve(''); - } - - for (let i = 0, len = users.length; i < len; i++) { - titles.push(users[i].title); - } - resolve(titles.join('\n')); - break; - case "Lookup": - case "LookupMulti": - lookupValues = fieldValue as ISPFieldLookupValue[]; - - if (!lookupValues) { - resolve(''); - } - - lookupTexts = []; - for (let i = 0, len = lookupValues.length; i < len; i++) { - lookupTexts.push(lookupValues[i].lookupValue); - } - resolve(lookupTexts.join('\n')); - break; - case 'URL': - SPHelper.getFieldProperty(field.id.toString(), 'Format', context, true).then(format => { - isImage = format === 'Image'; - if (isImage) { - resolve(''); - } - resolve(SPHelper.getRowItemValueByName(listItem, `${fieldName}.desc`)); - }).catch(() => { /* no-op; */ }); - break; - case 'Taxonomy': - case 'TaxonomyFieldType': - case 'TaxonomyFieldTypeMulti': - terms = Array.isArray(fieldValue) ? fieldValue : [fieldValue]; - - if (!terms) { - resolve(''); - } - - termTexts = []; - for (let i = 0, len = terms.length; i < len; i++) { - termTexts.push(terms[i].Label); - } - resolve(termTexts.join('\n')); - break; - case 'Attachments': - resolve(''); - break; - case 'Computed': - storedName = this.getStoredFieldName(fieldName); - if (storedName === 'URL') { - resolve(this.getRowItemValueByName(listItem, 'URL.desc') || strFieldValue); - } - resolve(strFieldValue); - break; - default: - resolve(strFieldValue); - } - }); - } + if (GeneralHelper.isDefined(field[propertyName])) { + resolve(field[propertyName]); + return; + } - /** - * Gets property of the Field by Field's ID and Property Name - * @param fieldId Field's ID - * @param propertyName Property name - * @param context SPFx context - * @param fromSchemaXml true if the field should be read from Field Schema Xml - */ - public static getFieldProperty(fieldId: string, propertyName: string, context: IContext, fromSchemaXml: boolean): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any - return new Promise(resolve => { // eslint-disable-line @typescript-eslint/no-explicit-any - let loadedViewFields: { [viewId: string]: IFields } = SPHelper._getLoadedViewFieldsFromStorage(); - const viewId: string = SPHelper.getPageViewId(context); - - if (!loadedViewFields) { - loadedViewFields = {}; + const sp: SPFI = getSP(context); + + if (fromSchemaXml) { + SPHelper.getFieldSchemaXmlById( + fieldId, + context.pageContext.list.title, + context + ).then( + (schemaXml) => { + let fieldValue: string; + const xml: Document = GeneralHelper.parseXml(schemaXml); + const fieldEls = xml.getElementsByTagName("Field"); + if (fieldEls.length) { + const fieldEl = fieldEls[0]; + fieldValue = fieldEl.getAttribute(propertyName); + if (!GeneralHelper.isDefined(fieldValue)) { + fieldValue = fieldEl.textContent; + } } - - if (!loadedViewFields[viewId]) { - loadedViewFields[viewId] = {}; + if (!GeneralHelper.isDefined(fieldValue)) { + fieldValue = ""; } - - let field: ISPField = loadedViewFields[viewId][fieldId]; - if (!field) { - field = { - Id: fieldId - }; + field[propertyName] = fieldValue; + SPHelper._updateFieldInSessionStorage(field, context); + }, + (error) => { + resolve(""); + } + ); + } else { + sp.web.lists + .getByTitle(context.pageContext.list.title) + .fields.getById(fieldId) + .select(propertyName)() + .then( + (f) => { + field[propertyName] = f[propertyName]; + + loadedViewFields[viewId][field.Id] = field; + + SPHelper._updateSessionStorageLoadedViewFields(loadedViewFields); + resolve(field[propertyName]); + }, + (error) => { + resolve(""); } + ); + } + }); + } + + /** + * Asynchronously gets the Diplay Form Url for the Lookup field + * @param fieldId Field Id + * @param context SPFx Context + */ + public static getLookupFieldListDispFormUrl( + fieldId: string, + context: BaseComponentContext + ): Promise { + return new Promise((resolve, reject) => { + let loadedViewFields: { [viewId: string]: IFields } = + SPHelper._getLoadedViewFieldsFromStorage(); + const viewId: string = SPHelper.getPageViewId(context); + + if (!loadedViewFields) { + loadedViewFields = {}; + } + if (!loadedViewFields[viewId]) { + loadedViewFields[viewId] = {}; + } - if (GeneralHelper.isDefined(field[propertyName])) { - resolve(field[propertyName]); - return; - } + let field: ISPField = loadedViewFields[viewId][fieldId]; + if (!field) { + field = { + Id: fieldId, + }; + } - sp.setup({ - spfxContext: context - }); + if (GeneralHelper.isDefined(field.LookupDisplayUrl)) { + resolve(field.LookupDisplayUrl); + return; + } - if (fromSchemaXml) { - SPHelper.getFieldSchemaXmlById(fieldId, context.pageContext.list.title, context).then(schemaXml => { - let fieldValue: string; - const xml: Document = GeneralHelper.parseXml(schemaXml); - const fieldEls = xml.getElementsByTagName('Field'); - if (fieldEls.length) { - const fieldEl = fieldEls[0]; - fieldValue = fieldEl.getAttribute(propertyName); - if (!GeneralHelper.isDefined(fieldValue)) { - fieldValue = fieldEl.textContent; - } - } - if (!GeneralHelper.isDefined(fieldValue)) { - fieldValue = ''; - } - field[propertyName] = fieldValue; + const sp: SPFI = getSP(context); + + sp.web.lists + .getByTitle(context.pageContext.list.title) + .fields.getById(fieldId) + .select("LookupWebId", "LookupList")() + .then((f: IFieldLookupInfo) => { + sp.site + .openWebById(f.LookupWebId) + .then((openedWeb) => { + openedWeb.web + .select("Url")() + .then( + (w) => { + field.LookupDisplayUrl = `${w.Url}/_layouts/15/listform.aspx?PageType=4&ListId=${f.LookupList}`; SPHelper._updateFieldInSessionStorage(field, context); - }, (error) => { - resolve(''); - }); - } - else { - sp.web.lists.getByTitle(context.pageContext.list.title).fields.getById(fieldId).select(propertyName).get().then(f => { - field[propertyName] = f[propertyName]; - - loadedViewFields[viewId][field.Id] = field; - - SPHelper._updateSessionStorageLoadedViewFields(loadedViewFields); - resolve(field[propertyName]); - }, (error) => { - resolve(''); - }); - } - }); - } - - /** - * Asynchronously gets the Diplay Form Url for the Lookup field - * @param fieldId Field Id - * @param context SPFx Context - */ - public static getLookupFieldListDispFormUrl(fieldId: string, context: IContext): Promise { - return new Promise((resolve, reject) => { - let loadedViewFields: { [viewId: string]: IFields } = SPHelper._getLoadedViewFieldsFromStorage(); - const viewId: string = SPHelper.getPageViewId(context); - - if (!loadedViewFields) { - loadedViewFields = {}; - } - - if (!loadedViewFields[viewId]) { - loadedViewFields[viewId] = {}; - } - - let field: ISPField = loadedViewFields[viewId][fieldId]; - if (!field) { - field = { - Id: fieldId - }; - } - - if (GeneralHelper.isDefined(field.LookupDisplayUrl)) { - resolve(field.LookupDisplayUrl); - return; - } - sp.setup({ - spfxContext: context + resolve(field.LookupDisplayUrl); + }, + (error) => { + reject(error); + } + ); + }) + .catch(() => { + /* no-op; */ }); - sp.web.lists.getByTitle(context.pageContext.list.title).fields.getById(fieldId).select('LookupWebId', 'LookupList').get().then((f: IFieldLookupInfo) => { - sp.site.openWebById(f.LookupWebId).then(openedWeb => { - openedWeb.web.select('Url').get().then(w => { - field.LookupDisplayUrl = `${w.Url}/_layouts/15/listform.aspx?PageType=4&ListId=${f.LookupList}`; - SPHelper._updateFieldInSessionStorage(field, context); - resolve(field.LookupDisplayUrl); - }, (error) => { - reject(error); - }); - }).catch(() => { /* no-op; */ }); - }).catch(() => { /* no-op; */ }); + }) + .catch(() => { + /* no-op; */ }); + }); + } + + /** + * Gets column's value for the row using List Item Accessor. + * This method works with private property _values of List Item Accessor to get such values as FriendlyDisplay text for Date, and more. + * @param listItem List Item Accessor + * @param itemName column name + */ + /* eslint-disable @typescript-eslint/no-explicit-any */ + public static getRowItemValueByName( + listItem: ListItemAccessor, + itemName: string + ): any { + const _values: any = (listItem)._values; + + if (_values) { + return (_values as Map).get(itemName); + } else { + // + // TODO: here we should call make a POST request to _api/web/GetList(@listUrl)/RenderListDataAsStream with correct parameters to get correct data + // the parameters should contain view, folder, pagination data, etc. + // I hope that Dev team will expose this data in API before I implement that because it's pretty complicated and they already have it in place + // + + return null; } + } + /* eslint-enable @typescript-eslint/no-explicit-any */ + + /** + * Gets SchemaXml for the field by List Title and Field Id + * @param fieldId Field's Id + * @param listTitle List Title + * @param context Customizer's context + */ + public static getFieldSchemaXmlById( + fieldId: string, + listTitle: string, + context: BaseComponentContext + ): Promise { + return new Promise((resolve) => { + let loadedViewFields: { [viewId: string]: IFields } = + SPHelper._getLoadedViewFieldsFromStorage(); + const viewId: string = SPHelper.getPageViewId(context); + + if (!loadedViewFields) { + loadedViewFields = {}; + } - /** - * Gets column's value for the row using List Item Accessor. - * This method works with private property _values of List Item Accessor to get such values as FriendlyDisplay text for Date, and more. - * @param listItem List Item Accessor - * @param itemName column name - */ - /* eslint-disable @typescript-eslint/no-explicit-any */ - public static getRowItemValueByName(listItem: ListItemAccessor, itemName: string): any { - const _values: any = (listItem)._values; - - if (_values) { - return (_values as Map).get(itemName); - } - else { - // - // TODO: here we should call make a POST request to _api/web/GetList(@listUrl)/RenderListDataAsStream with correct parameters to get correct data - // the parameters should contain view, folder, pagination data, etc. - // I hope that Dev team will expose this data in API before I implement that because it's pretty complicated and they already have it in place - // - - return null; - } - } - /* eslint-enable @typescript-eslint/no-explicit-any */ - - /** - * Gets SchemaXml for the field by List Title and Field Id - * @param fieldId Field's Id - * @param listTitle List Title - * @param context Customizer's context - */ - public static getFieldSchemaXmlById(fieldId: string, listTitle: string, context: IContext): Promise { - return new Promise((resolve) => { - let loadedViewFields: { [viewId: string]: IFields } = SPHelper._getLoadedViewFieldsFromStorage(); - const viewId: string = SPHelper.getPageViewId(context); - - if (!loadedViewFields) { - loadedViewFields = {}; - } - - if (!loadedViewFields[viewId]) { - loadedViewFields[viewId] = {}; - } - - let field: ISPField = loadedViewFields[viewId][fieldId]; - if (!field) { - field = { - Id: fieldId - }; - } - - - if (GeneralHelper.isDefined(field.SchemaXml)) { - resolve(field.SchemaXml); - return; - } - sp.setup({ - spfxContext: context - }); + if (!loadedViewFields[viewId]) { + loadedViewFields[viewId] = {}; + } - sp.web.lists.getByTitle(listTitle).fields.getById(fieldId).select('SchemaXml').get().then((f) => { - field.SchemaXml = f && f.SchemaXml; + let field: ISPField = loadedViewFields[viewId][fieldId]; + if (!field) { + field = { + Id: fieldId, + }; + } - loadedViewFields[viewId][field.Id] = field; + if (GeneralHelper.isDefined(field.SchemaXml)) { + resolve(field.SchemaXml); + return; + } - SPHelper._updateSessionStorageLoadedViewFields(loadedViewFields); - resolve(f ? f.SchemaXml : ''); - }, (error) => { - resolve(''); - }); - }); + const sp: SPFI = getSP(context); + + sp.web.lists + .getByTitle(listTitle) + .fields.getById(fieldId) + .select("SchemaXml")() + .then( + (f) => { + field.SchemaXml = f && f.SchemaXml; + + loadedViewFields[viewId][field.Id] = field; + + SPHelper._updateSessionStorageLoadedViewFields(loadedViewFields); + resolve(f ? f.SchemaXml : ""); + }, + (error) => { + resolve(""); + } + ); + }); + } + + /** + * Gets correct view id from the page + * @param context SPFx Context + */ + public static getPageViewId(context: BaseComponentContext): string { + const urlParams: URLSearchParams = new URLSearchParams(location.search); + let viewIdQueryParam: string = urlParams.get("viewid"); + if (viewIdQueryParam && viewIdQueryParam.indexOf("{") !== 0) { + viewIdQueryParam = `{${viewIdQueryParam}}`; } - - /** - * Gets correct view id from the page - * @param context SPFx Context - */ - public static getPageViewId(context: IContext): string { - const urlParams: URLSearchParams = new URLSearchParams(location.search); - let viewIdQueryParam: string = urlParams.get('viewid'); - if (viewIdQueryParam && viewIdQueryParam.indexOf('{') !== 0) { - viewIdQueryParam = `{${viewIdQueryParam}}`; - } - return viewIdQueryParam || context.pageContext.legacyPageContext.viewId; + return viewIdQueryParam || context.pageContext.legacyPageContext.viewId; + } + + /** + * Returns the user corresponding to the specified member identifier for the current site + * @param id user id + * @param context SPFx context + */ + public static async getUserById( + id: number, + context: BaseComponentContext + ): Promise { + const sp: SPFI = getSP(context); + + return sp.web.getUserById(id)(); + } + + /** + * Returns user profile properties + * @param loginName User's login name + * @param context SPFx context + */ + public static async getUserProperties( + loginName: string, + context: BaseComponentContext + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): Promise { + let url: string; + url = context.pageContext.web.absoluteUrl; + url = GeneralHelper.trimSlash(url); + + url += + "/_api/SP.UserProfiles.PeopleManager/GetPropertiesFor(accountName=@v)?@v='" + + encodeURIComponent(loginName) + + "'"; + return context.spHttpClient + .get(url, SPHttpClient.configurations.v1) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .then((response): Promise => { + return response.json(); + }) + .then((value) => { + return value; + }); + } + + public static isTextFieldType(fieldType?: string): boolean { + if (!fieldType) { + return true; } - - /** - * Returns the user corresponding to the specified member identifier for the current site - * @param id user id - * @param context SPFx context - */ - public static async getUserById(id: number, context: IContext): Promise { - sp.setup({ - spfxContext: context - }); - - return sp.web.getUserById(id).get(); + const lowercasedFieldType = fieldType.toLowerCase(); + return lowercasedFieldType === "text" || lowercasedFieldType === "note"; + } + + private static _updateFieldInSessionStorage( + field: ISPField, + context: BaseComponentContext + ): void { + let loadedViewFields: { [viewId: string]: IFields } = + SPHelper._getLoadedViewFieldsFromStorage(); + if (!loadedViewFields) { + loadedViewFields = {}; } - - /** - * Returns user profile properties - * @param loginName User's login name - * @param context SPFx context - */ - public static async getUserProperties(loginName: string, context: IContext): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any - let url: string; - url = context.pageContext.web.absoluteUrl; - url = GeneralHelper.trimSlash(url); - - url += "/_api/SP.UserProfiles.PeopleManager/GetPropertiesFor(accountName=@v)?@v='" + encodeURIComponent(loginName) + "'"; - return context.spHttpClient.get(url, SPHttpClient.configurations.v1) - .then((response): Promise => { // eslint-disable-line @typescript-eslint/no-explicit-any - return response.json(); - }) - .then((value) => { - return value; - }); + const viewId: string = SPHelper.getPageViewId(context); + if (!loadedViewFields[viewId]) { + loadedViewFields[viewId] = {}; } - - public static isTextFieldType(fieldType?: string): boolean { - if (!fieldType) { - return true; + loadedViewFields[viewId][field.Id] = field; + SPHelper._updateSessionStorageLoadedViewFields(loadedViewFields); + } + + private static _updateSessionStorageLoadedViewFields(loadedViewFields: { + [viewId: string]: IFields; + }): void { + try { + if (window.sessionStorage) { + window.sessionStorage.setItem( + Constants.LoadedViewFieldsKey, + JSON.stringify(loadedViewFields) + ); } - const lowercasedFieldType = fieldType.toLowerCase(); - return lowercasedFieldType === 'text' || lowercasedFieldType === 'note'; + } catch (error) { + // do nothing, no need to stop fn execution } - - - private static _updateFieldInSessionStorage(field: ISPField, context: IContext): void { - let loadedViewFields: { [viewId: string]: IFields } = SPHelper._getLoadedViewFieldsFromStorage(); - if (!loadedViewFields) { - loadedViewFields = {}; - } - const viewId: string = SPHelper.getPageViewId(context); - if (!loadedViewFields[viewId]) { - loadedViewFields[viewId] = {}; - } - loadedViewFields[viewId][field.Id] = field; - SPHelper._updateSessionStorageLoadedViewFields(loadedViewFields); - } - - private static _updateSessionStorageLoadedViewFields(loadedViewFields: { [viewId: string]: IFields }): void { - try { - if (window.sessionStorage) { - window.sessionStorage.setItem(Constants.LoadedViewFieldsKey, JSON.stringify(loadedViewFields)); - } - } catch (error) { - // do nothing, no need to stop fn execution - } - } - - private static _getLoadedViewFieldsFromStorage(): { [viewId: string]: IFields } { - try { - if (window.sessionStorage) { - const loadedViewFields = sessionStorage.getItem(Constants.LoadedViewFieldsKey); - if (loadedViewFields) { - return JSON.parse(loadedViewFields); - } - } - else { - return null; - } - } catch (error) { - return null; + } + + private static _getLoadedViewFieldsFromStorage(): { + [viewId: string]: IFields; + } { + try { + if (window.sessionStorage) { + const loadedViewFields = sessionStorage.getItem( + Constants.LoadedViewFieldsKey + ); + if (loadedViewFields) { + return JSON.parse(loadedViewFields); } + } else { + return null; + } + } catch (error) { + return null; } + } } diff --git a/src/controls/carousel/Carousel.tsx b/src/controls/carousel/Carousel.tsx index 120c22216..bb529ae7d 100644 --- a/src/controls/carousel/Carousel.tsx +++ b/src/controls/carousel/Carousel.tsx @@ -10,7 +10,7 @@ import { ICarouselState } from "./ICarouselState"; import { css, ICssInput } from "@uifabric/utilities/lib"; import { ProcessingState } from "./ICarouselState"; import { Spinner } from "office-ui-fabric-react/lib/Spinner"; -import { isArray } from "@pnp/common"; +import { isArray } from "@pnp/core"; import * as telemetry from '../../common/telemetry'; import CarouselImage, { ICarouselImageProps } from "./CarouselImage"; import { CarouselIndicatorsDisplay } from "./ICarouselProps"; diff --git a/src/controls/dynamicForm/DynamicForm.tsx b/src/controls/dynamicForm/DynamicForm.tsx index cc454348d..e08a0c2c6 100644 --- a/src/controls/dynamicForm/DynamicForm.tsx +++ b/src/controls/dynamicForm/DynamicForm.tsx @@ -1,6 +1,5 @@ /* eslint-disable @microsoft/spfx/no-async-await */ import { SPHttpClient } from "@microsoft/sp-http"; -import { sp } from "@pnp/sp/presets/all"; import * as strings from "ControlStrings"; import { DefaultButton, @@ -32,6 +31,8 @@ import "@pnp/sp/lists"; import "@pnp/sp/content-types"; import "@pnp/sp/folders"; import "@pnp/sp/items"; +import { SPFI, spfi } from "@pnp/sp"; +import { getSP } from "../../common/utilities/PnPJSConfig"; const stackTokens: IStackTokens = { childrenGap: 20 }; @@ -43,27 +44,18 @@ export class DynamicForm extends React.Component< IDynamicFormState > { private _spService: SPservice; - private webURL = this.props.webAbsoluteUrl + private _webURL = this.props.webAbsoluteUrl ? this.props.webAbsoluteUrl : this.props.context.pageContext.web.absoluteUrl; + private readonly _sp: SPFI; constructor(props: IDynamicFormProps) { super(props); // Initialize pnp sp - if (this.props.webAbsoluteUrl) { - sp.setup({ - sp: { - headers: { - Accept: "application/json;odata=verbose", - }, - baseUrl: this.props.webAbsoluteUrl, - }, - }); + this._sp = getSP(this.props.context, this.props.webAbsoluteUrl); } else { - sp.setup({ - spfxContext: { pageContext: this.props.context.pageContext }, - }); + this._sp = getSP(this.props.context); } // Initialize state @@ -304,7 +296,7 @@ export class DynamicForm extends React.Component< let newETag: string | undefined = undefined; if (listItemId) { try { - const iur = await sp.web.lists + const iur = await this._sp.web.lists .getById(listId) .items.getById(listItemId) .update(objects, this.state.etag); @@ -332,7 +324,7 @@ export class DynamicForm extends React.Component< ) { // We are adding a new list item try { - const iar = await sp.web.lists.getById(listId).items.add(objects); + const iar = await this._sp.web.lists.getById(listId).items.add(objects); if (onSubmitted) { onSubmitted( iar.data, @@ -354,7 +346,7 @@ export class DynamicForm extends React.Component< const titleField = "Title"; const contentTypeIdField = "ContentTypeId"; - const library = await sp.web.lists.getById(listId); + const library = await this._sp.web.lists.getById(listId); const folderTitle = objects[titleField] !== undefined && objects[titleField] !== "" ? (objects[titleField] as string).replace( @@ -430,7 +422,7 @@ export class DynamicForm extends React.Component< if (user.indexOf("@") === -1) { user = newValue[0].loginName; } - const result = await sp.web.ensureUser(user); + const result = await this._sp.web.ensureUser(user); field.newValue = result.data.Id; // eslint-disable-line require-atomic-updates } else { field.newValue = newValue[0].id; @@ -447,7 +439,7 @@ export class DynamicForm extends React.Component< if (user.indexOf("@") === -1) { user = element.loginName; } - const result = await sp.web.ensureUser(user); + const result = await this._sp.web.ensureUser(user); field.newValue.push(result.data.Id); } else { field.newValue.push(element.id); @@ -470,11 +462,11 @@ export class DynamicForm extends React.Component< } = this.props; let contentTypeId = this.props.contentTypeId; try { - const spList = await sp.web.lists.getById(listId); + const spList = await this._sp.web.lists.getById(listId); let item = null; let etag: string | undefined = undefined; if (listItemId !== undefined && listItemId !== null && listItemId !== 0) { - item = await spList.items.getById(listItemId).get(); + item = await spList.items.getById(listItemId)(); if (onListItemLoaded) { await onListItemLoaded(item); @@ -487,14 +479,13 @@ export class DynamicForm extends React.Component< if (contentTypeId === undefined || contentTypeId === "") { const defaultContentType = await spList.contentTypes - .select("Id", "Name") - .get(); + .select("Id", "Name")(); contentTypeId = defaultContentType[0].Id.StringValue; } const listFields = await this.getFormFields( listId, contentTypeId, - this.webURL + this._webURL ); const tempFields: IDynamicFieldProps[] = []; let order: number = 0; @@ -541,7 +532,7 @@ export class DynamicForm extends React.Component< listItemId, field.EntityPropertyName, lookupField, - this.webURL + this._webURL ); } else { defaultValue = []; @@ -555,7 +546,7 @@ export class DynamicForm extends React.Component< listItemId, field.EntityPropertyName, lookupField, - this.webURL + this._webURL ); } else { defaultValue = []; @@ -564,7 +555,7 @@ export class DynamicForm extends React.Component< const response = await this._spService.getTaxonomyFieldInternalName( this.props.listId, field.TextField, - this.webURL + this._webURL ); hiddenName = response.value; termSetId = field.TermSetId; @@ -640,7 +631,7 @@ export class DynamicForm extends React.Component< listId, listItemId, field.InternalName, - this.webURL + this._webURL ); else { defaultValue = []; @@ -662,7 +653,7 @@ export class DynamicForm extends React.Component< listId, listItemId, field.InternalName, - this.webURL + this._webURL )) + "" ); defaultValue = userEmails; @@ -764,7 +755,7 @@ export class DynamicForm extends React.Component< // eslint-disable-line @typescript-eslint/no-explicit-any try { const { context } = this.props; - const webAbsoluteUrl = !webUrl ? this.webURL : webUrl; + const webAbsoluteUrl = !webUrl ? this._webURL : webUrl; let apiUrl = ""; if (contentTypeId !== undefined && contentTypeId !== "") { if (contentTypeId.startsWith("0x0120")) { diff --git a/src/controls/dynamicForm/dynamicField/DynamicField.tsx b/src/controls/dynamicForm/dynamicField/DynamicField.tsx index 6c5ad2bb6..9fb932e54 100644 --- a/src/controls/dynamicForm/dynamicField/DynamicField.tsx +++ b/src/controls/dynamicForm/dynamicField/DynamicField.tsx @@ -1,6 +1,3 @@ -import '@pnp/sp/folders'; -import { sp } from '@pnp/sp/presets/all'; -import '@pnp/sp/webs'; import * as strings from 'ControlStrings'; import { ActionButton } from 'office-ui-fabric-react/lib/Button'; import { Dropdown, IDropdownOption, IDropdownProps } from 'office-ui-fabric-react/lib/components/Dropdown'; @@ -25,12 +22,8 @@ import { IDynamicFieldState } from './IDynamicFieldState'; export class DynamicField extends React.Component { - constructor(props: IDynamicFieldProps) { super(props); - sp.setup({ - spfxContext: { pageContext: this.props.context.pageContext } - }); this.state = { changedValue: props.fieldType === 'Thumbnail' ? props.fieldDefaultValue : null }; diff --git a/src/controls/fields/fieldLookupRenderer/FieldLookupRenderer.tsx b/src/controls/fields/fieldLookupRenderer/FieldLookupRenderer.tsx index 043a5b274..1783c899b 100644 --- a/src/controls/fields/fieldLookupRenderer/FieldLookupRenderer.tsx +++ b/src/controls/fields/fieldLookupRenderer/FieldLookupRenderer.tsx @@ -4,6 +4,8 @@ import { Dialog, DialogType } from 'office-ui-fabric-react/lib/Dialog'; import { Link } from 'office-ui-fabric-react/lib/Link'; import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner'; +import { BaseComponentContext } from '@microsoft/sp-component-base'; + import { ISPFieldLookupValue } from "../../../common/SPEntities"; import { IFieldRendererProps } from '../fieldCommon/IFieldRendererProps'; import * as telemetry from '../../../common/telemetry'; @@ -11,7 +13,6 @@ import * as telemetry from '../../../common/telemetry'; import styles from './FieldLookupRenderer.module.scss'; import { IFrameDialog } from '../../iFrameDialog/IFrameDialog'; import { SPHelper } from '../../../Utilities'; -import { IContext } from '../../../Common'; /** * Field Lookup Renderer Props @@ -40,7 +41,7 @@ export interface IFieldLookupRendererProps extends IFieldRendererProps { /** * Customizer context. Must be providede if fieldId is set */ - context?: IContext; + context?: BaseComponentContext; } /** diff --git a/src/controls/fields/fieldUserRenderer/FieldUserRenderer.tsx b/src/controls/fields/fieldUserRenderer/FieldUserRenderer.tsx index f17e8404c..53fb8c448 100644 --- a/src/controls/fields/fieldUserRenderer/FieldUserRenderer.tsx +++ b/src/controls/fields/fieldUserRenderer/FieldUserRenderer.tsx @@ -1,25 +1,37 @@ -import * as React from 'react'; -import { css } from 'office-ui-fabric-react/lib/Utilities'; -import clone from 'lodash/clone'; -import { IExpandingCardProps } from 'office-ui-fabric-react/lib/HoverCard'; -import { DirectionalHint } from 'office-ui-fabric-react/lib/common/DirectionalHint'; -import { Persona, PersonaSize } from 'office-ui-fabric-react/lib/Persona'; -import { IconButton, Button, ButtonType } from 'office-ui-fabric-react/lib/Button'; -import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner'; -import { Link } from 'office-ui-fabric-react/lib/Link'; -import { Icon } from 'office-ui-fabric-react/lib/Icon'; - -import { IPrincipal, IUserProfileProperties, IODataKeyValuePair } from '../../../common/SPEntities'; -import { IFieldRendererProps } from '../fieldCommon/IFieldRendererProps'; - -import styles from './FieldUserRenderer.module.scss'; -import { IContext } from '../../../common/Interfaces'; -import { GeneralHelper } from '../../../common/utilities/GeneralHelper'; -import FieldUserHoverCard, { IFieldUserHoverCardProps } from './FieldUserHoverCard'; -import * as telemetry from '../../../common/telemetry'; - -import * as strings from 'ControlStrings'; -import { SPHelper } from '../../../common/utilities'; +import * as React from "react"; +import { css } from "office-ui-fabric-react/lib/Utilities"; +import clone from "lodash/clone"; +import { IExpandingCardProps } from "office-ui-fabric-react/lib/HoverCard"; +import { DirectionalHint } from "office-ui-fabric-react/lib/common/DirectionalHint"; +import { Persona, PersonaSize } from "office-ui-fabric-react/lib/Persona"; +import { + IconButton, + Button, + ButtonType, +} from "office-ui-fabric-react/lib/Button"; +import { Spinner, SpinnerSize } from "office-ui-fabric-react/lib/Spinner"; +import { Link } from "office-ui-fabric-react/lib/Link"; +import { Icon } from "office-ui-fabric-react/lib/Icon"; + +import { BaseComponentContext } from "@microsoft/sp-component-base"; + +import { + IPrincipal, + IUserProfileProperties, + IODataKeyValuePair, +} from "../../../common/SPEntities"; +import { IFieldRendererProps } from "../fieldCommon/IFieldRendererProps"; + +import styles from "./FieldUserRenderer.module.scss"; +import { IContext } from "../../../common/Interfaces"; +import { GeneralHelper } from "../../../common/utilities/GeneralHelper"; +import FieldUserHoverCard, { + IFieldUserHoverCardProps, +} from "./FieldUserHoverCard"; +import * as telemetry from "../../../common/telemetry"; + +import * as strings from "ControlStrings"; +import { SPHelper } from "../../../common/utilities"; export interface IFieldUserRendererProps extends IFieldRendererProps { /** @@ -29,7 +41,7 @@ export interface IFieldUserRendererProps extends IFieldRendererProps { /** * Customizer context */ - context: IContext; + context: BaseComponentContext; } /** @@ -91,41 +103,58 @@ export interface IFieldUserRendererState { * Used for: * - People and Groups */ -export class FieldUserRenderer extends React.Component { - +export class FieldUserRenderer extends React.Component< + IFieldUserRendererProps, + IFieldUserRendererState +> { // cached user profiles private _loadedUserProfiles: { [id: string]: IUserProfileProperties } = {}; private _userUrlTemplate: string; private _userImageUrl: string; - - public constructor(props: IFieldUserRendererProps, state: IFieldUserRendererState) { + public constructor( + props: IFieldUserRendererProps, + state: IFieldUserRendererState + ) { super(props, state); - telemetry.track('FieldUserRenderer', {}); + telemetry.track("FieldUserRenderer", {}); - this._userImageUrl = `${GeneralHelper.trimSlash(props.context.pageContext.web.absoluteUrl)}/_layouts/15/userphoto.aspx?size=L&accountname={0}`; + this._userImageUrl = `${GeneralHelper.trimSlash( + props.context.pageContext.web.absoluteUrl + )}/_layouts/15/userphoto.aspx?size=L&accountname={0}`; - const users: IFieldUser[] = props.users ? props.users.map(user => { - return this._getUserFromPrincipalAndProps(user, {}); - }) : []; + const users: IFieldUser[] = props.users + ? props.users.map((user) => { + return this._getUserFromPrincipalAndProps(user, {}); + }) + : []; this.state = { - users: users + users: users, }; } - public UNSAFE_componentWillReceiveProps(nextProps: IFieldUserRendererProps): void { - const currentPrincipals = this.props.users ? this.props.users.map(u => u.id) : []; - const newPrincipals = nextProps.users ? nextProps.users.map(u => u.id) : []; - - if (currentPrincipals.length !== newPrincipals.length - || currentPrincipals.filter(cp => newPrincipals.indexOf(cp) === -1).length - || newPrincipals.filter(np => currentPrincipals.indexOf(np) === -1).length) { + public UNSAFE_componentWillReceiveProps( + nextProps: IFieldUserRendererProps + ): void { + const currentPrincipals = this.props.users + ? this.props.users.map((u) => u.id) + : []; + const newPrincipals = nextProps.users + ? nextProps.users.map((u) => u.id) + : []; + + if ( + currentPrincipals.length !== newPrincipals.length || + currentPrincipals.filter((cp) => newPrincipals.indexOf(cp) === -1) + .length || + newPrincipals.filter((np) => currentPrincipals.indexOf(np) === -1).length + ) { this.setState({ - users: nextProps.users.map(user => { + users: nextProps.users.map((user) => { return this._getUserFromPrincipalAndProps(user, {}); - }) + }), }); } } @@ -133,21 +162,29 @@ export class FieldUserRenderer extends React.Component { const expandingCardProps: IExpandingCardProps = { - onRenderCompactCard: (user.email ? this._onRenderCompactCard.bind(this, index) : null), - onRenderExpandedCard: (user.email ? this._onRenderExpandedCard.bind(this) : null), + onRenderCompactCard: user.email + ? this._onRenderCompactCard.bind(this, index) + : null, + onRenderExpandedCard: user.email + ? this._onRenderExpandedCard.bind(this) + : null, renderData: user, directionalHint: DirectionalHint.bottomLeftEdge, gapSpace: 1, - expandedCardHeight: 150 + expandedCardHeight: 150, }; const hoverCardProps: IFieldUserHoverCardProps = { expandingCardProps: expandingCardProps, displayName: user.displayName, - cssProps: this.props.cssProps + cssProps: this.props.cssProps, }; return ; }); - return
{userEls}
; + return ( +
+ {userEls} +
+ ); } /** @@ -156,30 +193,58 @@ export class FieldUserRenderer extends React.Component { /* no-op; */ }).catch(() => { /* no-op; */ }); + this._requestUserProfile(user, index) + .then(() => { + /* no-op; */ + }) + .catch(() => { + /* no-op; */ + }); const sip: string = user.sip || user.email; let actionsEl: JSX.Element; if (user.currentUser) { - actionsEl =
- -
; - } - else { - actionsEl =
- - -
; + actionsEl = ( +
+ +
+ ); + } else { + actionsEl = ( +
+ + +
+ ); } - return
- - {actionsEl} -
; + return ( +
+ + {actionsEl} +
+ ); } /** @@ -188,29 +253,54 @@ export class FieldUserRenderer extends React.Component -
  • -
    {strings.Contact}
    -
    - - {user.email} -
    - {user.workPhone && -
    - - {user.workPhone} + return ( +
      +
    • +
      + {strings.Contact}{" "} +
      - } - {user.cellPhone &&
      - - {user.cellPhone} + + + {user.email} +
      - } -
    • -
    ; - } - else { + {user.workPhone && ( +
    + + + {user.workPhone} + +
    + )} + {user.cellPhone && ( +
    + + + {user.cellPhone} + +
    + )} +
  • + + ); + } else { return ; } } @@ -220,24 +310,35 @@ export class FieldUserRenderer extends React.Component { + private async _requestUserProfile( + user: IFieldUser, + index: number + ): Promise { if (this._loadedUserProfiles[user.id]) { return; // we've already have the profile info } - const context: IContext = this.props.context; + const { context } = this.props; const siteUser = await SPHelper.getUserById(parseInt(user.id), context); const value = await SPHelper.getUserProperties(siteUser.LoginName, context); - const mthumbStr = 'MThumb.jpg'; + const mthumbStr = "MThumb.jpg"; const userProfileProps: IUserProfileProperties = { displayName: value.DisplayName, email: value.Email, jobTitle: value.Title, userUrl: value.UserUrl, - pictureUrl: value.PictureUrl && value.PictureUrl.toString().indexOf(mthumbStr) === value.PictureUrl.toString().length - mthumbStr.length ? '' : value.PictureUrl //this._userImageUrl.replace('{0}', user.email) + pictureUrl: + value.PictureUrl && + value.PictureUrl.toString().indexOf(mthumbStr) === + value.PictureUrl.toString().length - mthumbStr.length + ? "" + : value.PictureUrl, //this._userImageUrl.replace('{0}', user.email) }; - const props: IODataKeyValuePair[] = value.UserProfileProperties as IODataKeyValuePair[]; + const props: IODataKeyValuePair[] = + value.UserProfileProperties as IODataKeyValuePair[]; let foundPropsCount: number = 0; for (let i = 0, len = props.length; i < len; i++) { const prop: IODataKeyValuePair = props[i]; switch (prop.Key) { - case 'WorkPhone': + case "WorkPhone": userProfileProps.workPhone = prop.Value; foundPropsCount++; break; - case 'Department': + case "Department": userProfileProps.department = prop.Value; foundPropsCount++; break; - case 'SPS-SipAddress': + case "SPS-SipAddress": userProfileProps.sip = prop.Value; foundPropsCount++; break; - case 'CellPhone': + case "CellPhone": userProfileProps.cellPhone = prop.Value; foundPropsCount++; break; @@ -304,12 +416,19 @@ export class FieldUserRenderer extends React.Component { - const newUsers = clone(prevState.users); - newUsers[index] = this._getUserFromPrincipalAndProps(this.props.users[index], userProfileProps); - - return { users: newUsers }; - - }); + this.setState( + ( + prevState: IFieldUserRendererState, + componentProps: IFieldUserRendererProps + ) => { + const newUsers = clone(prevState.users); + newUsers[index] = this._getUserFromPrincipalAndProps( + this.props.users[index], + userProfileProps + ); + + return { users: newUsers }; + } + ); } } diff --git a/src/controls/modernTaxonomyPicker/ModernTaxonomyPicker.tsx b/src/controls/modernTaxonomyPicker/ModernTaxonomyPicker.tsx index 144c231c0..a26d4de25 100644 --- a/src/controls/modernTaxonomyPicker/ModernTaxonomyPicker.tsx +++ b/src/controls/modernTaxonomyPicker/ModernTaxonomyPicker.tsx @@ -1,6 +1,5 @@ import { BaseComponentContext, IReadonlyTheme } from '@microsoft/sp-component-base'; import { Guid } from '@microsoft/sp-core-library'; -import { sp } from '@pnp/sp'; import { ITermInfo, ITermSetInfo, @@ -76,7 +75,6 @@ export function ModernTaxonomyPicker(props: IModernTaxonomyPickerProps): JSX.Ele const [currentLanguageTag, setCurrentLanguageTag] = React.useState(""); React.useEffect(() => { - sp.setup({ pageContext: props.context.pageContext }); taxonomyService.getTermStoreInfo() .then((termStoreInfo) => { setCurrentTermStoreInfo(termStoreInfo); diff --git a/src/services/FolderExplorerService.ts b/src/services/FolderExplorerService.ts index 3b350cd87..a326b16c4 100644 --- a/src/services/FolderExplorerService.ts +++ b/src/services/FolderExplorerService.ts @@ -2,27 +2,27 @@ import { ServiceKey, ServiceScope } from "@microsoft/sp-core-library"; import { PageContext } from "@microsoft/sp-page-context"; import { IFolderExplorerService } from "./IFolderExplorerService"; import { IFolder } from "./IFolderExplorerService"; -import { sp } from "@pnp/sp"; import "@pnp/sp/webs"; import { Web } from "@pnp/sp/webs"; import "@pnp/sp/folders"; import "@pnp/sp/lists"; import { IFolderAddResult } from "@pnp/sp/folders"; import { IFileInfo, IFiles } from "@pnp/sp/files"; +import { SPFI } from "@pnp/sp"; +import { getSP } from "../common/utilities/PnPJSConfig"; export class FolderExplorerService implements IFolderExplorerService { public static readonly serviceKey: ServiceKey = ServiceKey.create('SPFx:SPService', FolderExplorerService); + private _sp: SPFI; constructor(serviceScope: ServiceScope) { serviceScope.whenFinished(() => { const pageContext = serviceScope.consume(PageContext.serviceKey); - sp.setup({ - sp: { - baseUrl: pageContext.web.absoluteUrl - } + this._sp = getSP({ + pageContext: pageContext }); }); } @@ -44,7 +44,7 @@ export class FolderExplorerService implements IFolderExplorerService { try { const web = Web(webAbsoluteUrl); // eslint-disable-next-line @typescript-eslint/no-explicit-any - const libraries: any[] = await web.lists.filter('BaseTemplate eq 101 and Hidden eq false').expand('RootFolder').select('Title', 'RootFolder/ServerRelativeUrl').orderBy('Title').get(); + const libraries: any[] = await web.lists.filter('BaseTemplate eq 101 and Hidden eq false').expand('RootFolder').select('Title', 'RootFolder/ServerRelativeUrl').orderBy('Title')(); results = libraries.map((library): IFolder => { return { Name: library.Title, ServerRelativeUrl: library.RootFolder.ServerRelativeUrl }; @@ -84,7 +84,7 @@ export class FolderExplorerService implements IFolderExplorerService { try { const web = Web(webAbsoluteUrl); //folderRelativeUrl = folderRelativeUrl.replace(/'/ig, "''"); - const foldersResult: IFolder[] = await web.getFolderByServerRelativePath(folderRelativeUrl).folders.select('Name', 'ServerRelativeUrl').orderBy(orderby, orderAscending).get(); + const foldersResult: IFolder[] = await web.getFolderByServerRelativePath(folderRelativeUrl).folders.select('Name', 'ServerRelativeUrl').orderBy(orderby, orderAscending)(); results = foldersResult.filter(f => f.Name !== "Forms"); } catch (error) { console.error('Error loading folders', error); @@ -102,7 +102,7 @@ export class FolderExplorerService implements IFolderExplorerService { try { const web = Web(webAbsoluteUrl); folderRelativeUrl = folderRelativeUrl.replace(/'/ig, "''"); - const filesResult = await web.getFolderByServerRelativePath(folderRelativeUrl).files.select('Name', 'ServerRelativeUrl', 'UniqueId', 'Length').orderBy(orderby, orderAscending).get(); + const filesResult = await web.getFolderByServerRelativePath(folderRelativeUrl).files.select('Name', 'ServerRelativeUrl', 'UniqueId', 'Length').orderBy(orderby, orderAscending)(); results = filesResult; } catch (error) { console.error('Error loading files', error); diff --git a/src/services/PeopleSearchService.ts b/src/services/PeopleSearchService.ts index 04d85b2b5..edbd147ee 100644 --- a/src/services/PeopleSearchService.ts +++ b/src/services/PeopleSearchService.ts @@ -1,13 +1,14 @@ import { BaseComponentContext } from '@microsoft/sp-component-base'; import { ISPHttpClientOptions, SPHttpClient } from '@microsoft/sp-http'; import { findIndex } from "@microsoft/sp-lodash-subset"; -import { sp } from '@pnp/sp'; import "@pnp/sp/site-users/web"; import "@pnp/sp/sputilities"; import "@pnp/sp/webs"; import { Web } from "@pnp/sp/webs"; import { IUserInfo } from "../controls/peoplepicker/IUsers"; import { IPeoplePickerUserItem, PrincipalType } from "../PeoplePicker"; +import { SPFI } from '@pnp/sp'; +import { getSP } from '../common/utilities/PnPJSConfig'; /** * Service implementation to search people in SharePoint @@ -16,6 +17,8 @@ export default class SPPeopleSearchService { private cachedPersonas: { [property: string]: IUserInfo[] }; private cachedLocalUsers: { [siteUrl: string]: IUserInfo[] }; + private readonly _sp: SPFI; + /** * Service constructor */ @@ -24,7 +27,7 @@ export default class SPPeopleSearchService { this.cachedLocalUsers = {}; this.cachedLocalUsers[this.context.pageContext.web.absoluteUrl] = []; // Setup PnPjs - sp.setup({ pageContext: this.context.pageContext }); + this._sp = getSP(this.context); } /** @@ -143,14 +146,14 @@ export default class SPPeopleSearchService { // Get user loginName from user email const _users = []; - const batch = Web(this.context.pageContext.web.absoluteUrl).createBatch(); + const [batch, execute] = Web(this.context.pageContext.web.absoluteUrl).batched(); for (const value of graphUserResponse.value) { - sp.web.inBatch(batch).ensureUser(value.userPrincipalName).then(u => _users.push(u.data)).catch(() => { + batch.ensureUser(value.userPrincipalName).then(u => _users.push(u.data)).catch(() => { // no-op }); } - await batch.execute(); + await execute(); const userResult: IPeoplePickerUserItem[] = []; for (const user of _users) { diff --git a/src/services/SPTaxonomyService.ts b/src/services/SPTaxonomyService.ts index 6092a51aa..b0817d3bd 100644 --- a/src/services/SPTaxonomyService.ts +++ b/src/services/SPTaxonomyService.ts @@ -1,14 +1,16 @@ import { BaseComponentContext } from '@microsoft/sp-component-base'; import { Guid } from '@microsoft/sp-core-library'; -import { LambdaParser } from '@pnp/odata/parsers'; -import { SharePointQueryableCollection, sp } from '@pnp/sp'; +import { SPFI } from '@pnp/sp'; import '@pnp/sp/taxonomy'; import { ITermInfo, ITermSetInfo, ITermStoreInfo } from '@pnp/sp/taxonomy'; +import { getSP } from '../common/utilities/PnPJSConfig'; export class SPTaxonomyService { - constructor(private context: BaseComponentContext) { + private readonly _sp: SPFI; + constructor(private context: BaseComponentContext) { + this._sp = getSP(context); } public async getTerms(termSetId: Guid, parentTermId?: Guid, skiptoken?: string, hideDeprecatedTerms?: boolean, pageSize: number = 50): Promise<{ value: ITermInfo[], skiptoken: string }> { @@ -27,10 +29,10 @@ export class SPTaxonomyService { let legacyChildrenUrlAndQuery = ''; if (parentTermId && parentTermId !== Guid.empty) { - legacyChildrenUrlAndQuery = sp.termStore.sets.getById(termSetId.toString()).terms.getById(parentTermId.toString()).concat('/getLegacyChildren').toUrl(); + legacyChildrenUrlAndQuery = this._sp.termStore.sets.getById(termSetId.toString()).terms.getById(parentTermId.toString()).concat('/getLegacyChildren').toUrl(); } else { - legacyChildrenUrlAndQuery = sp.termStore.sets.getById(termSetId.toString()).concat('/getLegacyChildren').toUrl(); + legacyChildrenUrlAndQuery = this._sp.termStore.sets.getById(termSetId.toString()).concat('/getLegacyChildren').toUrl(); } let legacyChildrenQueryable = SharePointQueryableCollection(legacyChildrenUrlAndQuery).top(pageSize).usingParser(parser); if (hideDeprecatedTerms) { From eb6428382bab31a333f4cbc38cfa842d00a357f3 Mon Sep 17 00:00:00 2001 From: Alex Terentiev Date: Sun, 11 Jun 2023 14:07:25 -0400 Subject: [PATCH 02/15] pre-commit --- CHANGELOG.md | 7 ++++++- docs/documentation/docs/about/release-notes.md | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98b9dc42b..fb58ea01a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 3.15.0 +### New control(s) + +- `TermSetNavigation`: new control TermSetNavigation [#1527](https://github.com/pnp/sp-dev-fx-controls-react/pull/1527) + ### Enhancements - `FolderExplorer`: show files on folder explorer control [#1502](https://github.com/pnp/sp-dev-fx-controls-react/pull/1502) @@ -13,10 +17,11 @@ - `FieldPicker`: Changed react import to fix `cannot be used as a JSX component` error [#1500](https://github.com/pnp/sp-dev-fx-controls-react/pull/1500) - `Localization`: Fixes to Italian localization [#1532](https://github.com/pnp/sp-dev-fx-controls-react/pull/1532) - `Localization`: Fixes to Netherlands localization [#1537](https://github.com/pnp/sp-dev-fx-controls-react/pull/1537) +- `ListItemAttachments`: Fix the OnClick handler when clicking on the document card [#1541](https://github.com/pnp/sp-dev-fx-controls-react/issues/1541) ### Contributors -Special thanks to our contributors (in alphabetical order): [Andreas Omayrat](https://github.com/andreasomayrat), [Ayoub](https://github.com/ayoubqrt), [Guido Zambarda](https://github.com/GuidoZam), [Sharepointalist](https://github.com/sharepointalist). +Special thanks to our contributors (in alphabetical order): [Andreas Omayrat](https://github.com/andreasomayrat), [Ayoub](https://github.com/ayoubqrt), [Guido Zambarda](https://github.com/GuidoZam), [Nishkalank Bezawada](https://github.com/NishkalankBezawada), [Sharepointalist](https://github.com/sharepointalist). ## 3.14.0 diff --git a/docs/documentation/docs/about/release-notes.md b/docs/documentation/docs/about/release-notes.md index 98b9dc42b..fb58ea01a 100644 --- a/docs/documentation/docs/about/release-notes.md +++ b/docs/documentation/docs/about/release-notes.md @@ -2,6 +2,10 @@ ## 3.15.0 +### New control(s) + +- `TermSetNavigation`: new control TermSetNavigation [#1527](https://github.com/pnp/sp-dev-fx-controls-react/pull/1527) + ### Enhancements - `FolderExplorer`: show files on folder explorer control [#1502](https://github.com/pnp/sp-dev-fx-controls-react/pull/1502) @@ -13,10 +17,11 @@ - `FieldPicker`: Changed react import to fix `cannot be used as a JSX component` error [#1500](https://github.com/pnp/sp-dev-fx-controls-react/pull/1500) - `Localization`: Fixes to Italian localization [#1532](https://github.com/pnp/sp-dev-fx-controls-react/pull/1532) - `Localization`: Fixes to Netherlands localization [#1537](https://github.com/pnp/sp-dev-fx-controls-react/pull/1537) +- `ListItemAttachments`: Fix the OnClick handler when clicking on the document card [#1541](https://github.com/pnp/sp-dev-fx-controls-react/issues/1541) ### Contributors -Special thanks to our contributors (in alphabetical order): [Andreas Omayrat](https://github.com/andreasomayrat), [Ayoub](https://github.com/ayoubqrt), [Guido Zambarda](https://github.com/GuidoZam), [Sharepointalist](https://github.com/sharepointalist). +Special thanks to our contributors (in alphabetical order): [Andreas Omayrat](https://github.com/andreasomayrat), [Ayoub](https://github.com/ayoubqrt), [Guido Zambarda](https://github.com/GuidoZam), [Nishkalank Bezawada](https://github.com/NishkalankBezawada), [Sharepointalist](https://github.com/sharepointalist). ## 3.14.0 From be6395d7d6c390931ddcfb026d2f7406484bec85 Mon Sep 17 00:00:00 2001 From: Alex Terentiev Date: Sun, 16 Jul 2023 12:28:35 -0400 Subject: [PATCH 03/15] pre-commit --- CHANGELOG.md | 5 ++++- docs/documentation/docs/about/release-notes.md | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bef95533..a9681c284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - `ModernTaxonomyPicker`: can't find term when UI is in language not supported by term store [#1573](https://github.com/pnp/sp-dev-fx-controls-react/issues/1573) - `AdaptiveCardHost`: Add null check for adaptive card elements [#1574](https://github.com/pnp/sp-dev-fx-controls-react/pull/1574) - `ControlsTestWebPart`: Updated the ControlsTestWebPart to show the controls filtered by control type [#1547](https://github.com/pnp/sp-dev-fx-controls-react/pull/1547) +- `fast-serve`: Fast-serve updated to the latest version and serve warnings fixed [#1589](https://github.com/pnp/sp-dev-fx-controls-react/pull/1589) +- `DynamicForm`: DynamicForm Number min max [#1585](https://github.com/pnp/sp-dev-fx-controls-react/pull/1585) ### Fixes @@ -30,10 +32,11 @@ - `PeoplePicker`: Shows wrong value in Dynamic Form when null is provided [#1421](https://github.com/pnp/sp-dev-fx-controls-react/issues/1421) - `DynamicForm`: Error on save when clearing person from Person or Group field and leaving it blank [#1578](https://github.com/pnp/sp-dev-fx-controls-react/issues/1578) - `DynamicForm`: Number validation is not working, if the field is set to minimum and maximum value [#1571](https://github.com/pnp/sp-dev-fx-controls-react/issues/1571) +- `DynamicForm`: controls are shown with error messages even if the values are assigned [#1133](https://github.com/pnp/sp-dev-fx-controls-react/issues/1586) ### Contributors -Special thanks to our contributors (in alphabetical order): [Andreas Omayrat](https://github.com/andreasomayrat), [Ayoub](https://github.com/ayoubqrt), [Desislav](https://github.com/DMichev), [Guido Zambarda](https://github.com/GuidoZam), [Nishkalank Bezawada](https://github.com/NishkalankBezawada), [Patrik Hellgren](https://github.com/patrikhellgren), [Rico van de Ven](https://github.com/RicoNL), [Sharepointalist](https://github.com/sharepointalist), [Zhephyr](https://github.com/Zhephyr54). +Special thanks to our contributors (in alphabetical order): [Andreas Omayrat](https://github.com/andreasomayrat), [Ayoub](https://github.com/ayoubqrt), [Desislav](https://github.com/DMichev), [Guido Zambarda](https://github.com/GuidoZam), [João Mendes](https://github.com/joaojmendes), [Nishkalank Bezawada](https://github.com/NishkalankBezawada), [Patrik Hellgren](https://github.com/patrikhellgren), [Rico van de Ven](https://github.com/RicoNL), [Sergei Sergeev](https://github.com/s-KaiNet), [Sharepointalist](https://github.com/sharepointalist), [Zhephyr](https://github.com/Zhephyr54). ## 3.14.0 diff --git a/docs/documentation/docs/about/release-notes.md b/docs/documentation/docs/about/release-notes.md index 6bef95533..a9681c284 100644 --- a/docs/documentation/docs/about/release-notes.md +++ b/docs/documentation/docs/about/release-notes.md @@ -15,6 +15,8 @@ - `ModernTaxonomyPicker`: can't find term when UI is in language not supported by term store [#1573](https://github.com/pnp/sp-dev-fx-controls-react/issues/1573) - `AdaptiveCardHost`: Add null check for adaptive card elements [#1574](https://github.com/pnp/sp-dev-fx-controls-react/pull/1574) - `ControlsTestWebPart`: Updated the ControlsTestWebPart to show the controls filtered by control type [#1547](https://github.com/pnp/sp-dev-fx-controls-react/pull/1547) +- `fast-serve`: Fast-serve updated to the latest version and serve warnings fixed [#1589](https://github.com/pnp/sp-dev-fx-controls-react/pull/1589) +- `DynamicForm`: DynamicForm Number min max [#1585](https://github.com/pnp/sp-dev-fx-controls-react/pull/1585) ### Fixes @@ -30,10 +32,11 @@ - `PeoplePicker`: Shows wrong value in Dynamic Form when null is provided [#1421](https://github.com/pnp/sp-dev-fx-controls-react/issues/1421) - `DynamicForm`: Error on save when clearing person from Person or Group field and leaving it blank [#1578](https://github.com/pnp/sp-dev-fx-controls-react/issues/1578) - `DynamicForm`: Number validation is not working, if the field is set to minimum and maximum value [#1571](https://github.com/pnp/sp-dev-fx-controls-react/issues/1571) +- `DynamicForm`: controls are shown with error messages even if the values are assigned [#1133](https://github.com/pnp/sp-dev-fx-controls-react/issues/1586) ### Contributors -Special thanks to our contributors (in alphabetical order): [Andreas Omayrat](https://github.com/andreasomayrat), [Ayoub](https://github.com/ayoubqrt), [Desislav](https://github.com/DMichev), [Guido Zambarda](https://github.com/GuidoZam), [Nishkalank Bezawada](https://github.com/NishkalankBezawada), [Patrik Hellgren](https://github.com/patrikhellgren), [Rico van de Ven](https://github.com/RicoNL), [Sharepointalist](https://github.com/sharepointalist), [Zhephyr](https://github.com/Zhephyr54). +Special thanks to our contributors (in alphabetical order): [Andreas Omayrat](https://github.com/andreasomayrat), [Ayoub](https://github.com/ayoubqrt), [Desislav](https://github.com/DMichev), [Guido Zambarda](https://github.com/GuidoZam), [João Mendes](https://github.com/joaojmendes), [Nishkalank Bezawada](https://github.com/NishkalankBezawada), [Patrik Hellgren](https://github.com/patrikhellgren), [Rico van de Ven](https://github.com/RicoNL), [Sergei Sergeev](https://github.com/s-KaiNet), [Sharepointalist](https://github.com/sharepointalist), [Zhephyr](https://github.com/Zhephyr54). ## 3.14.0 From 9e27cfe9e72e760635bc540f7fbf8129d2278375 Mon Sep 17 00:00:00 2001 From: Alex Terentiev Date: Sun, 16 Jul 2023 13:11:41 -0400 Subject: [PATCH 04/15] building code --- src/controls/dynamicForm/DynamicForm.tsx | 6 +- .../fieldUserRenderer/FieldUserRenderer.tsx | 1 - .../components/Customizer/OotbFields.tsx | 4 +- src/services/FolderExplorerService.ts | 2 +- src/services/SPTaxonomyService.ts | 79 +++++++++---------- 5 files changed, 45 insertions(+), 47 deletions(-) diff --git a/src/controls/dynamicForm/DynamicForm.tsx b/src/controls/dynamicForm/DynamicForm.tsx index 33948131a..c666e116a 100644 --- a/src/controls/dynamicForm/DynamicForm.tsx +++ b/src/controls/dynamicForm/DynamicForm.tsx @@ -31,7 +31,7 @@ import "@pnp/sp/lists"; import "@pnp/sp/content-types"; import "@pnp/sp/folders"; import "@pnp/sp/items"; -import { SPFI, spfi } from "@pnp/sp"; +import { SPFI } from "@pnp/sp"; import { getSP } from "../../common/utilities/PnPJSConfig"; const stackTokens: IStackTokens = { childrenGap: 20 }; @@ -406,7 +406,7 @@ export class DynamicForm extends React.Component< // trigger when the user change any value in the form private onChange = async ( internalName: string, - newValue: any, + newValue: any, // eslint-disable-line @typescript-eslint/no-explicit-any additionalData?: FieldChangeAdditionalData ): Promise => { // eslint-disable-line @typescript-eslint/no-explicit-any @@ -765,8 +765,8 @@ export class DynamicForm extends React.Component< listId: string, contentTypeId: string | undefined, webUrl?: string + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise => { - // eslint-disable-line @typescript-eslint/no-explicit-any try { const { context } = this.props; const webAbsoluteUrl = !webUrl ? this._webURL : webUrl; diff --git a/src/controls/fields/fieldUserRenderer/FieldUserRenderer.tsx b/src/controls/fields/fieldUserRenderer/FieldUserRenderer.tsx index 53fb8c448..22d7d4031 100644 --- a/src/controls/fields/fieldUserRenderer/FieldUserRenderer.tsx +++ b/src/controls/fields/fieldUserRenderer/FieldUserRenderer.tsx @@ -23,7 +23,6 @@ import { import { IFieldRendererProps } from "../fieldCommon/IFieldRendererProps"; import styles from "./FieldUserRenderer.module.scss"; -import { IContext } from "../../../common/Interfaces"; import { GeneralHelper } from "../../../common/utilities/GeneralHelper"; import FieldUserHoverCard, { IFieldUserHoverCardProps, diff --git a/src/extensions/ootbFields/components/Customizer/OotbFields.tsx b/src/extensions/ootbFields/components/Customizer/OotbFields.tsx index d0dec4540..253736537 100644 --- a/src/extensions/ootbFields/components/Customizer/OotbFields.tsx +++ b/src/extensions/ootbFields/components/Customizer/OotbFields.tsx @@ -2,7 +2,7 @@ import { Log } from '@microsoft/sp-core-library'; import * as React from 'react'; import styles from './OotbFields.module.scss'; -import { ListItemAccessor } from '@microsoft/sp-listview-extensibility'; +import { FieldCustomizerContext, ListItemAccessor } from '@microsoft/sp-listview-extensibility'; import { FieldRendererHelper } from '../../../../common/utilities/FieldRendererHelper'; import { IProps } from '../../../../common/Interfaces'; import { IFieldRendererProps } from '../../../../controls/fields/fieldCommon/IFieldRendererProps'; @@ -34,7 +34,7 @@ export default class OotbFields extends React.Component { + }, this.props.listItem, this.props.context as FieldCustomizerContext).then(fieldRenderer => { this.setState({ fieldRenderer: fieldRenderer }); diff --git a/src/services/FolderExplorerService.ts b/src/services/FolderExplorerService.ts index a326b16c4..015edda19 100644 --- a/src/services/FolderExplorerService.ts +++ b/src/services/FolderExplorerService.ts @@ -7,7 +7,7 @@ import { Web } from "@pnp/sp/webs"; import "@pnp/sp/folders"; import "@pnp/sp/lists"; import { IFolderAddResult } from "@pnp/sp/folders"; -import { IFileInfo, IFiles } from "@pnp/sp/files"; +import { IFileInfo } from "@pnp/sp/files"; import { SPFI } from "@pnp/sp"; import { getSP } from "../common/utilities/PnPJSConfig"; diff --git a/src/services/SPTaxonomyService.ts b/src/services/SPTaxonomyService.ts index b0817d3bd..ba00fa82e 100644 --- a/src/services/SPTaxonomyService.ts +++ b/src/services/SPTaxonomyService.ts @@ -2,7 +2,8 @@ import { BaseComponentContext } from '@microsoft/sp-component-base'; import { Guid } from '@microsoft/sp-core-library'; import { SPFI } from '@pnp/sp'; import '@pnp/sp/taxonomy'; -import { ITermInfo, ITermSetInfo, ITermStoreInfo } from '@pnp/sp/taxonomy'; +import { JSONParse } from '@pnp/queryable'; +import { ITermInfo, ITermSetInfo, ITermStoreInfo, ITerms } from '@pnp/sp/taxonomy'; import { getSP } from '../common/utilities/PnPJSConfig'; export class SPTaxonomyService { @@ -14,38 +15,37 @@ export class SPTaxonomyService { } public async getTerms(termSetId: Guid, parentTermId?: Guid, skiptoken?: string, hideDeprecatedTerms?: boolean, pageSize: number = 50): Promise<{ value: ITermInfo[], skiptoken: string }> { - try { - const parser = new LambdaParser(async (r: Response) => { - const json = await r.json(); - let newSkiptoken=''; - if(json['@odata.nextLink']) { - const urlParams = new URLSearchParams(json['@odata.nextLink'].split('?')[1]); - if(urlParams.has('$skiptoken')) { - newSkiptoken = urlParams.get('$skiptoken'); - } - } - return { value: json.value, skiptoken: newSkiptoken }; - }); - - let legacyChildrenUrlAndQuery = ''; - if (parentTermId && parentTermId !== Guid.empty) { - legacyChildrenUrlAndQuery = this._sp.termStore.sets.getById(termSetId.toString()).terms.getById(parentTermId.toString()).concat('/getLegacyChildren').toUrl(); - } - else { - legacyChildrenUrlAndQuery = this._sp.termStore.sets.getById(termSetId.toString()).concat('/getLegacyChildren').toUrl(); - } - let legacyChildrenQueryable = SharePointQueryableCollection(legacyChildrenUrlAndQuery).top(pageSize).usingParser(parser); - if (hideDeprecatedTerms) { - legacyChildrenQueryable = legacyChildrenQueryable.filter('isDeprecated eq false'); - } - if (skiptoken && skiptoken !== '') { - legacyChildrenQueryable.query.set('$skiptoken', skiptoken); + const localSpfi = this._sp.using(JSONParse()); + try { + let legacyChildrenTerms: ITerms; + if (parentTermId && parentTermId !== Guid.empty) { + legacyChildrenTerms = (localSpfi.termStore.sets.getById(termSetId.toString()).terms.getById(parentTermId.toString()).concat('/getLegacyChildren') as unknown as ITerms); + } + else { + legacyChildrenTerms = (localSpfi.termStore.sets.getById(termSetId.toString()).concat('/getLegacyChildren') as unknown as ITerms); + } + legacyChildrenTerms = legacyChildrenTerms.top(pageSize); + if (hideDeprecatedTerms) { + legacyChildrenTerms = legacyChildrenTerms.filter('isDeprecated eq false'); + } + if (skiptoken && skiptoken !== '') { + legacyChildrenTerms.query.set('$skiptoken', skiptoken); + } + + // type manipulations as we're getting plain JSON here + const termsJsonResult = await legacyChildrenTerms() as { '@odata.nextLink': string | undefined, value: ITermInfo[] }; + let newSkiptoken = ''; + if (termsJsonResult['@odata.nextLink']) { + const urlParams = new URLSearchParams(termsJsonResult['@odata.nextLink'].split('?')[1]); + if (urlParams.has('$skiptoken')) { + newSkiptoken = urlParams.get('$skiptoken'); } - const termsResult = await legacyChildrenQueryable() as {value: ITermInfo[], skiptoken: string}; - return termsResult; - } catch (error) { - return { value: [], skiptoken: '' }; } + + return { value: termsJsonResult.value as ITermInfo[], skiptoken: newSkiptoken }; + } catch (error) { + return { value: [], skiptoken: '' }; + } } public async getTermById(termSetId: Guid, termId: Guid): Promise { @@ -53,7 +53,7 @@ export class SPTaxonomyService { return undefined; } try { - const termInfo = await sp.termStore.sets.getById(termSetId.toString()).terms.getById(termId.toString()).expand("parent")(); + const termInfo = await this._sp.termStore.sets.getById(termSetId.toString()).terms.getById(termId.toString()).expand("parent")(); return termInfo; } catch (error) { return undefined; @@ -69,21 +69,20 @@ export class SPTaxonomyService { `stringMatchId='${stringMatchId}'` ]; - if(parentTermId !== Guid.empty) { + if (parentTermId !== Guid.empty) { query.push(`parentTermId='${parentTermId}'`); } - const searchTermUrl = sp.termStore.concat(`/searchTerm(${query.join(',')})`).toUrl(); - const searchTermQuery = SharePointQueryableCollection(searchTermUrl).top(pageSize); + const searchTermQuery = (this._sp.termStore.concat(`/searchTerm(${query.join(',')})`) as unknown as ITerms).top(pageSize); let filteredTerms: ITermInfo[] = await searchTermQuery(); - if(allowSelectingChildren === false) { + if (allowSelectingChildren === false) { const hasParentId = parentTermId !== Guid.empty; - const set = sp.termStore.sets.getById(termSetId.toString()); + const set = this._sp.termStore.sets.getById(termSetId.toString()); const collection = hasParentId ? set.terms.getById(parentTermId.toString()).children : set.children; - const childrenIds = await collection.select("id").get().then(children => children.map(c => c.id)); + const childrenIds = await collection.select("id")().then(children => children.map(c => c.id)); filteredTerms = filteredTerms.filter(term => childrenIds.includes(term.id)); } @@ -94,12 +93,12 @@ export class SPTaxonomyService { } public async getTermSetInfo(termSetId: Guid): Promise { - const tsInfo = await sp.termStore.sets.getById(termSetId.toString()).get(); + const tsInfo = await this._sp.termStore.sets.getById(termSetId.toString())(); return tsInfo; } public async getTermStoreInfo(): Promise { - const termStoreInfo = await sp.termStore(); + const termStoreInfo = await this._sp.termStore(); return termStoreInfo; } } From 12042c8eb769490690c5fb43ec864b70bee31d29 Mon Sep 17 00:00:00 2001 From: Alex Terentiev Date: Sun, 16 Jul 2023 13:52:58 -0400 Subject: [PATCH 05/15] wp test --- src/webparts/controlsTest/components/ControlsTest.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index 204c563e3..cb62dda19 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -576,7 +576,7 @@ export default class ControlsTest extends React.Component