@spfx-extensions/package 1.4.13 → 1.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.npmignore +16 -0
  2. package/.nvmrc +1 -0
  3. package/config/config.json +27 -0
  4. package/config/deploy-azure-storage.json +7 -0
  5. package/config/package-solution.json +51 -0
  6. package/config/sass.json +3 -0
  7. package/config/serve.json +18 -0
  8. package/config/write-manifests.json +4 -0
  9. package/dist/8d1029da-85e6-48cc-aaaf-37a5bbc0b9be.manifest.json +2 -2
  10. package/dist/d6ca1fc2-0591-4c6d-8a25-cae3262c017b.manifest.json +2 -2
  11. package/dist/debug/83e13c11-682e-4eaa-9ae0-74617ca28f96/Extension_8d1029da-85e6-48cc-aaaf-37a5bbc0b9be.xml +1 -1
  12. package/dist/debug/83e13c11-682e-4eaa-9ae0-74617ca28f96/WebPart_d6ca1fc2-0591-4c6d-8a25-cae3262c017b.xml +1 -1
  13. package/dist/debug/AppManifest.xml +1 -1
  14. package/dist/debug/ClientSideAssets/{spfx-extension-application-customizer_a8132780a6e1626ca4ef.js → spfx-extension-application-customizer_840b4102fa2c27e7913b.js} +1 -1
  15. package/dist/{spfx-extension-loader_d2e5154cdd7a4b34af83.js → debug/ClientSideAssets/spfx-extension-loader_7703538ecb97e35e6fe9.js} +1 -1
  16. package/dist/{spfx-extensionloader-web-part_e4f8ad9f3b2be2ee380e.js → debug/ClientSideAssets/spfx-extensionloader-web-part_ef8308f26188dc2ae907.js} +1 -1
  17. package/dist/debug/ClientSideAssets.xml +1 -1
  18. package/dist/debug/ClientSideAssets.xml.config.xml +1 -1
  19. package/dist/debug/_rels/ClientSideAssets.xml.rels +1 -1
  20. package/dist/debug/feature_83e13c11-682e-4eaa-9ae0-74617ca28f96.xml +1 -1
  21. package/dist/debug/feature_83e13c11-682e-4eaa-9ae0-74617ca28f96.xml.config.xml +1 -1
  22. package/dist/deploy/sp-fx-extensions.sppkg +0 -0
  23. package/dist/{spfx-extension-application-customizer_a8132780a6e1626ca4ef.js → spfx-extension-application-customizer_840b4102fa2c27e7913b.js} +1 -1
  24. package/dist/{debug/ClientSideAssets/spfx-extension-loader_d2e5154cdd7a4b34af83.js → spfx-extension-loader_7703538ecb97e35e6fe9.js} +1 -1
  25. package/dist/{debug/ClientSideAssets/spfx-extensionloader-web-part_e4f8ad9f3b2be2ee380e.js → spfx-extensionloader-web-part_ef8308f26188dc2ae907.js} +1 -1
  26. package/gulpfile.js +196 -0
  27. package/package.json +12 -2
  28. package/sharepoint/assets/ClientSideInstance.xml +9 -0
  29. package/sharepoint/assets/elements.xml +9 -0
  30. package/src/@types/globals.d.ts +15 -0
  31. package/src/extensions/spfxExtension/SpfxExtensionApplicationCustomizer.manifest.json +17 -0
  32. package/src/extensions/spfxExtension/SpfxExtensionApplicationCustomizer.ts +52 -0
  33. package/src/extensions/spfxExtension/loc/en-us.js +5 -0
  34. package/src/extensions/spfxExtension/loc/myStrings.d.ts +8 -0
  35. package/src/index.ts +1 -0
  36. package/src/services/initCoreService.ts +46 -0
  37. package/src/utilities/constants.ts +4 -0
  38. package/src/webparts/spfxExtensionloader/SpfxExtensionloaderWebPart.manifest.json +35 -0
  39. package/src/webparts/spfxExtensionloader/SpfxExtensionloaderWebPart.module.scss +185 -0
  40. package/src/webparts/spfxExtensionloader/SpfxExtensionloaderWebPart.module.scss.ts +24 -0
  41. package/src/webparts/spfxExtensionloader/SpfxExtensionloaderWebPart.ts +573 -0
  42. package/src/webparts/spfxExtensionloader/assets/welcome-dark.png +0 -0
  43. package/src/webparts/spfxExtensionloader/assets/welcome-light.png +0 -0
  44. package/src/webparts/spfxExtensionloader/loc/en-us.js +16 -0
  45. package/src/webparts/spfxExtensionloader/loc/mystrings.d.ts +19 -0
  46. package/teams/3be36e80-4431-4b52-99c5-0a339b4e696e_color.png +0 -0
  47. package/teams/3be36e80-4431-4b52-99c5-0a339b4e696e_outline.png +0 -0
  48. package/tsconfig.json +25 -0
@@ -0,0 +1,573 @@
1
+ import { DisplayMode, Environment, EnvironmentType, Version } from "@microsoft/sp-core-library";
2
+ import {
3
+ IPropertyPaneConditionalGroup,
4
+ type IPropertyPaneConfiguration,
5
+ IPropertyPaneCustomFieldProps,
6
+ IPropertyPaneDropdownOption,
7
+ IPropertyPaneDropdownProps,
8
+ IPropertyPaneField,
9
+ IPropertyPaneGroup,
10
+ PropertyPaneButton,
11
+ PropertyPaneDropdown,
12
+ PropertyPaneFieldType,
13
+ PropertyPaneLabel
14
+ } from "@microsoft/sp-property-pane";
15
+ import { BaseClientSideWebPart, IWebPartPropertiesMetadata } from "@microsoft/sp-webpart-base";
16
+ import type { IReadonlyTheme } from "@microsoft/sp-component-base";
17
+
18
+ import styles from "./SpfxExtensionloaderWebPart.module.scss";
19
+ //import * as strings from "SpfxExtensionloaderWebPartStrings";
20
+ import { SPFxExtensionAppConfig, SPFxExtensionAppDefinition, SPFxExtensionAppIcon, SPFxExtensionAppInstance, SPFxExtensionAppRuntimeConfig, SPFxExtensionAppSearchableData } from "@spfx-extensions/core";
21
+ import { APP_BUTTON_LABEL, EDIT_PAGE_AND_SELECT_WEBPART, SELECT_WEBPART, SPFXPREFIX } from "../../utilities/constants";
22
+
23
+ export interface ISpfxExtensionloaderWebPartProps extends SPFxExtensionAppSearchableData {
24
+ selectedApp: string;
25
+ SPFxExtensionAppConfiguration: SPFxExtensionAppConfig | undefined;
26
+ }
27
+
28
+ type propertyPath = keyof ISpfxExtensionloaderWebPartProps;
29
+
30
+
31
+
32
+ export default class SpfxExtensionloaderWebPart extends BaseClientSideWebPart<ISpfxExtensionloaderWebPartProps> {
33
+
34
+ SPFxExtensionInstance: SPFxExtensionAppInstance | undefined;
35
+ allApps: IPropertyPaneDropdownOption[] = [];
36
+ appCatalogUrl = "/sites/appcatalog";
37
+ dropDownProps: Partial<IPropertyPaneDropdownProps> = {
38
+ options: [],
39
+ selectedKey: "",
40
+ disabled: true,
41
+ };
42
+ appDescription = "";
43
+ hideAppSelectorWhenAppLoaded = false;
44
+ hideConfiguratorButton = false;
45
+ configDomElement: HTMLElement | undefined;
46
+
47
+ emptyRendered = false;
48
+ _isDarkTheme: boolean = false;
49
+
50
+ public async onInit() {
51
+ const envType =
52
+ Environment.type === EnvironmentType.SharePoint
53
+ ? "SharePoint"
54
+ : "ClassicSharePoint";
55
+ const { initCore } = await import(/* webpackChunkName: "spfx-extension-loader" */"../../services/initCoreService");
56
+ //init core then do stuff;
57
+ await initCore(envType);
58
+ this.appCatalogUrl = window.__SPFxExtensions.Utils.ConfiguratorPageUrl;
59
+ if (this.properties.selectedApp) {
60
+ this.mountApp(this.properties.selectedApp).catch((err) => {
61
+ console.error(SPFXPREFIX, "Error while mounting appid", this.properties.selectedApp, err);
62
+ });
63
+ }
64
+ }
65
+
66
+ protected onPropertyPaneConfigurationComplete(): void {
67
+ const isPaneOpen = this.context.propertyPane.isPropertyPaneOpen();
68
+
69
+ // notify close only if the pane is not open
70
+ // complete event fires also when config is saved
71
+ // This event method is invoked in the following cases:
72
+
73
+ // When the CONFIGURATION_COMPLETE_TIMEOUT((currently the value is 5 secs) elapses after the last change.
74
+
75
+ // When user clicks the "X" (close) button before the CONFIGURATION_COMPLETE_TIMEOUT elapses.
76
+
77
+ // When user clicks the 'Apply' button before the CONFIGURATION_COMPLETE_TIMEOUT elapses.
78
+
79
+ // When the user switches web parts then the current web part gets this event.
80
+ if (!isPaneOpen && this.SPFxExtensionInstance) {
81
+
82
+ this.SPFxExtensionInstance.executeListeners(
83
+ "onConfigurationClose",
84
+ undefined
85
+ );
86
+ }
87
+ }
88
+
89
+ protected onPropertyPaneFieldChanged(
90
+ propertyPath: propertyPath,
91
+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ oldValue: any,
93
+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
94
+ newValue: any
95
+ ): void {
96
+ // if selected app changed unmount the old app
97
+ if (propertyPath === "selectedApp") {
98
+ if (oldValue && oldValue !== newValue && this.SPFxExtensionInstance) {
99
+ const shouldUnmount = confirm(
100
+ "You are about to switch app, this will erase all previous app configuration. Are you sure?"
101
+ );
102
+ if (!shouldUnmount) {
103
+ this.properties[propertyPath] = oldValue;
104
+ return;
105
+ }
106
+ this.unmountApp();
107
+ }
108
+ // if new app was selected, mount it
109
+ if (newValue) {
110
+ this.webpartSectionElement.remove();
111
+ this.mountApp(newValue).catch((err) => {
112
+ console.error(SPFXPREFIX, "Error while mounting appid", newValue, err);
113
+ });
114
+ }
115
+ }
116
+ }
117
+
118
+ protected onDisplayModeChanged(oldDisplayMode: DisplayMode): void {
119
+ const mode = oldDisplayMode === DisplayMode.Edit ? "Read" : "Edit";
120
+ if (this.SPFxExtensionInstance) {
121
+ this.SPFxExtensionInstance.executeListeners("onDisplayModeChange", mode);
122
+ return;
123
+ }
124
+ }
125
+
126
+ openPropertyPane() {
127
+ // if (this.context.propertyPane.isPropertyPaneOpen()) {
128
+ // this.context.propertyPane.close();
129
+ // }
130
+ this.context.propertyPane.open();
131
+ }
132
+
133
+ closePropertyPane() {
134
+ this.context.propertyPane.close();
135
+ }
136
+
137
+ isPropertyPaneOpen() {
138
+ return this.context.propertyPane.isPropertyPaneOpen();
139
+ }
140
+
141
+ saveConfigValue(config: SPFxExtensionAppConfig, raiseEvent = true) {
142
+ // const a = config.searchableText;
143
+ // delete config.searchableText;
144
+ // this.properties.searchableText = a;
145
+ this.properties.SPFxExtensionAppConfiguration = config;
146
+ if (raiseEvent) {
147
+ this.SPFxExtensionInstance?.executeListeners("onConfigurationChange", config);
148
+ }
149
+ }
150
+
151
+ getConfigValue(key?: string) {
152
+ if (key) {
153
+ return (this.properties[key as keyof ISpfxExtensionloaderWebPartProps] as SPFxExtensionAppConfig | undefined);
154
+ }
155
+ return this.properties.SPFxExtensionAppConfiguration;
156
+ }
157
+
158
+ getSearchData() {
159
+ return {
160
+ searchableText: this.properties.searchableText,
161
+ searchableHtml: this.properties.searchableHtml,
162
+ };
163
+ }
164
+
165
+ setSearchData(data: SPFxExtensionAppSearchableData) {
166
+ this.properties.searchableText = data.searchableText;
167
+ this.properties.searchableHtml = data.searchableHtml;
168
+ }
169
+
170
+ private async mountApp(appId: string) {
171
+ if (ISDEBUG) {
172
+ console.debug(SPFXPREFIX, "Mounting app", appId, "at", this.domElement);
173
+ }
174
+ const runTimeConfig: SPFxExtensionAppRuntimeConfig = {
175
+ domElement: this.domElement,
176
+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
177
+ webpartContext: this.context as any,
178
+ openPropertyPane: () => {
179
+ this.openPropertyPane();
180
+ },
181
+ closePropertyPane: () => {
182
+ this.closePropertyPane();
183
+ },
184
+ isPropertyPaneOpen: () => {
185
+ return this.isPropertyPaneOpen();
186
+ },
187
+ saveConfigValue: (config: SPFxExtensionAppConfig, raise = true) => {
188
+ this.saveConfigValue(config, raise);
189
+ },
190
+ getConfigValue: (key?: string) => {
191
+ return this.getConfigValue(key);
192
+ },
193
+ getSearchableData: () => {
194
+ return this.getSearchData();
195
+ },
196
+ setSearchableData: (data: SPFxExtensionAppSearchableData) => {
197
+ this.setSearchData(data);
198
+ },
199
+ };
200
+ this.SPFxExtensionInstance = await window.__SPFxExtensions.InstantiateApp(appId, runTimeConfig);
201
+ if (!this.SPFxExtensionInstance) {
202
+ return;
203
+ }
204
+ const newApp = window.__SPFxExtensions.Apps.find((app) => app.id === appId);
205
+ if (newApp) {
206
+ this.appDescription = newApp.description;
207
+ //spfx specific, for some reason refresh does not work properly (custom field is not rerendered)
208
+ // this.context.propertyPane.refresh();
209
+ // if (this.context.propertyPane.isPropertyPaneOpen()) {
210
+ // this.context.propertyPane.close();
211
+ // this.context.propertyPane.open();
212
+ // }
213
+ }
214
+ }
215
+
216
+ private unmountApp() {
217
+ if (this.SPFxExtensionInstance && this.SPFxExtensionInstance.unmount) {
218
+ if (ISDEBUG) {
219
+ console.debug(
220
+ SPFXPREFIX,
221
+ "Unmounting app",
222
+ this.SPFxExtensionInstance.key,
223
+ "at",
224
+ this.SPFxExtensionInstance.domElement
225
+ );
226
+ }
227
+ this.SPFxExtensionInstance.unmount();
228
+ }
229
+
230
+ this.properties.SPFxExtensionAppConfiguration = undefined;
231
+ }
232
+
233
+ protected onDispose(): void {
234
+ this.unmountApp();
235
+ }
236
+
237
+ appButtonElements: HTMLElement[] = [];
238
+
239
+ webpartSectionElement = document.createElement("section");
240
+ webpartSectionTitle = document.createElement("header");
241
+ appButtonsWrapper = document.createElement("div");
242
+ appButtonsContainer = document.createElement("div");
243
+
244
+ generateIconElement(icon?: SPFxExtensionAppIcon) {
245
+ const iconElement = document.createElement("i");
246
+ iconElement.classList.add(styles.icon);
247
+
248
+ if (!icon) {
249
+ iconElement.innerHTML = `<svg fill="currentColor" class="___12fm75w f1w7gpdv fez10in fg4l7m0" aria-hidden="true" width="28" height="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg"><path d="M20.84 2.66a2.25 2.25 0 0 0-3.18 0L13.5 6.8v-.56c0-1.24-1-2.25-2.25-2.25h-7C3.01 4 2 5.01 2 6.25v18c0 .97.78 1.75 1.75 1.75h18c1.24 0 2.25-1 2.25-2.25v-7c0-1.24-1-2.25-2.25-2.25h-.56l4.16-4.15c.88-.88.88-2.3 0-3.19l-4.5-4.5ZM17.31 14.5H13.5v-3.8l3.8 3.8Zm1.41-10.78c.3-.3.77-.3 1.06 0l4.5 4.5c.3.3.3.77 0 1.06l-4.5 4.51c-.3.3-.77.3-1.06 0l-4.5-4.5a.75.75 0 0 1 0-1.07l4.5-4.5ZM12 6.25v8.25H3.5V6.25c0-.41.34-.75.75-.75h7c.41 0 .75.34.75.75Zm-8.5 17.5V16H12v8.5H4.25a.75.75 0 0 1-.75-.75Zm10-7.75h8.25c.41 0 .75.34.75.75v7c0 .42-.34.75-.75.75H13.5V16Z" fill="currentColor"></path></svg>`;
250
+ return iconElement;
251
+ }
252
+
253
+ if (icon.iconType === "font" && icon.fontFamily) {
254
+ iconElement.style.fontFamily = icon.fontFamily;
255
+ iconElement.classList.add(styles.iconFont);
256
+ }
257
+
258
+ if (icon.iconType === "url") {
259
+ const imageElement = document.createElement("img");
260
+ imageElement.src = icon.iconData;
261
+ iconElement.appendChild(imageElement);
262
+ }
263
+
264
+ if (icon.iconType === "svg") {
265
+ iconElement.innerHTML = icon.iconData;
266
+ }
267
+
268
+ return iconElement;
269
+ }
270
+
271
+ createAndAppendAppButtons(app: SPFxExtensionAppDefinition) {
272
+ const appButtonElement = document.createElement("button");
273
+ appButtonElement.title = APP_BUTTON_LABEL;
274
+ appButtonElement.ariaLabel = APP_BUTTON_LABEL;
275
+ appButtonElement.className = styles.appButton;
276
+
277
+ const icon = this.generateIconElement(app.icon);
278
+ appButtonElement.append(icon, app.name);
279
+ appButtonElement.title = app.name;
280
+
281
+ appButtonElement.addEventListener("click", () => {
282
+ this.properties.selectedApp = app.id;
283
+ this.webpartSectionElement.remove();
284
+ this.mountApp(app.id).catch((err) => {
285
+ console.error(SPFXPREFIX, "Error while mounting app", app, err);
286
+ });
287
+ });
288
+
289
+ this.appButtonsContainer.appendChild(appButtonElement);
290
+ }
291
+
292
+ createWebpartSection(button?: boolean) {
293
+ this.webpartSectionElement.className = styles.applicationListSection;
294
+ this.webpartSectionTitle.className = styles.header;
295
+ this.webpartSectionElement.appendChild(this.webpartSectionTitle);
296
+ if (button) {
297
+ this.webpartSectionElement.appendChild(this.appButtonsWrapper);
298
+ }
299
+ }
300
+
301
+ renderDisplayMode() {
302
+ this.webpartSectionElement.ariaLabel = EDIT_PAGE_AND_SELECT_WEBPART;
303
+ this.webpartSectionTitle.textContent = EDIT_PAGE_AND_SELECT_WEBPART;
304
+ this.createWebpartSection();
305
+ this.domElement.appendChild(this.webpartSectionElement);
306
+ }
307
+
308
+ renderEditMode() {
309
+ this.webpartSectionElement.ariaLabel = SELECT_WEBPART;
310
+ this.webpartSectionTitle.textContent = SELECT_WEBPART;
311
+ this.createWebpartSection(true);
312
+
313
+ this.appButtonsContainer.className = styles.appButtonsContainer;
314
+ this.appButtonsWrapper.appendChild(this.appButtonsContainer);
315
+ this.appButtonsWrapper.className = styles.appButtonsWrapper;
316
+ this.domElement.appendChild(this.webpartSectionElement);
317
+
318
+ window.__SPFxExtensions.AddAppEventListener("appAdded", (app) => {
319
+ if (app.isWebPartApp) {
320
+ this.createAndAppendAppButtons(app);
321
+ }
322
+ });
323
+
324
+ window.__SPFxExtensions.Apps.filter((app) => app.registrationCompleted).forEach(
325
+ (app) => {
326
+ if (app.isWebPartApp) {
327
+ this.createAndAppendAppButtons(app);
328
+ }
329
+ }
330
+ );
331
+
332
+ window.__SPFxExtensions.Utils.spAppInitializationPromise.then(() => {
333
+ this.domElement.appendChild(this.webpartSectionElement);
334
+
335
+ window.__SPFxExtensions.Utils.appManifestPromises.forEach((promise) => {
336
+ const buttonLoader = document.createElement("div");
337
+ const loaderSpinner = document.createElement("span");
338
+ buttonLoader.className = styles.buttonLoader;
339
+ loaderSpinner.className = styles.loader;
340
+ buttonLoader.appendChild(loaderSpinner);
341
+
342
+ this.appButtonsContainer.append(buttonLoader);
343
+ promise
344
+ .catch(() => {
345
+ //do nothing
346
+ }).finally(() => {
347
+ buttonLoader.remove();
348
+ });
349
+ });
350
+ }).catch(() => {
351
+ // do nothing
352
+ });
353
+ }
354
+
355
+ public render(): void {
356
+ // do not uncomment this or this will "erase the webpart when exiting edit mode";
357
+ // required when adding same Webpart while another instance is already open and configuration pane is open as well.
358
+ if (this.context.propertyPane.isPropertyPaneOpen()) {
359
+ this.onPropertyPaneConfigurationStart();
360
+ }
361
+ if (this.properties.selectedApp) {
362
+ return;
363
+ }
364
+
365
+ this.domElement.className = styles.SPFxExtensionApp;
366
+
367
+ if (this.displayMode === DisplayMode.Read) {
368
+ this.renderDisplayMode();
369
+ }
370
+
371
+ if (this.displayMode === DisplayMode.Edit) {
372
+ this.renderEditMode();
373
+ }
374
+ }
375
+
376
+ protected get propertiesMetadata(): IWebPartPropertiesMetadata {
377
+ return {
378
+ // selectedApp: {
379
+ // isSearchablePlainText: true,
380
+ // },
381
+ // SPFxExtensionAppConfiguration: {
382
+ // dynamicPropertyType: "object",
383
+ // },
384
+ searchableText: {
385
+ isSearchablePlainText: true,
386
+ },
387
+ searchableHtml: {
388
+ isHtmlString: true,
389
+ },
390
+ };
391
+ }
392
+
393
+ protected onPropertyPaneConfigurationStart(): void {
394
+ // wait for all the manifests to load
395
+ window.__SPFxExtensions.AllAppAssetsLoadedPromise.then(() => {
396
+ // register description if an app is matching this webpart
397
+ const selectedApp = window.__SPFxExtensions.Apps.find(
398
+ (app) => app.id === this.properties.selectedApp
399
+ );
400
+ if (selectedApp) {
401
+ this.appDescription = selectedApp.description;
402
+ this.hideAppSelectorWhenAppLoaded =
403
+ selectedApp.hideAppSelectorWhenAppLoaded ?? false;
404
+ this.hideConfiguratorButton = selectedApp.hideConfiguratorButton ?? false;
405
+ }
406
+
407
+ // Clear dropdown options in propertypane
408
+ this.dropDownProps.options?.splice(0, this.dropDownProps.options?.length);
409
+
410
+ const appOptionsInDropdown: IPropertyPaneDropdownOption[] = window.__SPFxExtensions.Apps.filter(
411
+ (app) => app.isWebPartApp
412
+ ).map((app) => {
413
+ return {
414
+ key: app.id,
415
+ text: app.name,
416
+ };
417
+ });
418
+
419
+ this.dropDownProps.options?.push(...appOptionsInDropdown);
420
+
421
+ // select key
422
+ this.dropDownProps.selectedKey = this.properties.selectedApp;
423
+ // enable dropdown
424
+ this.dropDownProps.disabled = false;
425
+ // refresh to rerender the dropdown and description
426
+ this.context.propertyPane.refresh();
427
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
428
+ }).catch((err: any) => {
429
+ console.error(SPFXPREFIX, "Error while awaiting all app assets to load", err);
430
+ });
431
+ }
432
+
433
+ CustomWebpartConfigurationField(
434
+ name: string
435
+ ): IPropertyPaneField<IPropertyPaneCustomFieldProps> {
436
+ return {
437
+ type: PropertyPaneFieldType.Custom,
438
+ targetProperty: name,
439
+ properties: {
440
+ key: "SPFxExtensionAppConfiguration",
441
+ onRender: (domElement, _context, _callBack) => {
442
+ this.configDomElement = domElement;
443
+ if (this.SPFxExtensionInstance) {
444
+ // when app instance is loaded forward the render event
445
+ this.SPFxExtensionInstance.instanceLoadPromise.then(() => {
446
+ this.SPFxExtensionInstance?.executeListeners(
447
+ "onConfigurationRender",
448
+ {
449
+ domElement,
450
+ }
451
+ );
452
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
453
+ }).catch((err: any) => {
454
+ console.error(SPFXPREFIX, "Error while awaiting app to load", err);
455
+ });
456
+ }
457
+ },
458
+ onDispose: (_domeElement, _context) => {
459
+ if (this.SPFxExtensionInstance) {
460
+ this.SPFxExtensionInstance.executeListeners(
461
+ "onConfigurationClose",
462
+ undefined
463
+ );
464
+ }
465
+ },
466
+ },
467
+ };
468
+ }
469
+
470
+ protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
471
+ const configuratorButton: IPropertyPaneGroup | IPropertyPaneConditionalGroup = {
472
+ groupFields: [
473
+ PropertyPaneLabel("spfxExtensionLoaderLabel", {
474
+ text: `App not working? Try refreshing the page. Or go to the configuration page.`,
475
+ }),
476
+ PropertyPaneButton("configuratorButton", {
477
+ text: "Open Configurator",
478
+ buttonType: 1,
479
+ onClick: () => {
480
+ window.open(`${this.appCatalogUrl}?web=${this.context.pageContext.web.absoluteUrl}`, "_blank");
481
+ }
482
+ })
483
+ ]
484
+ };
485
+ const cfgButtonGroup = this.hideConfiguratorButton ? [] : [configuratorButton];
486
+
487
+ const appSelector: IPropertyPaneGroup | IPropertyPaneConditionalGroup = {
488
+ groupFields: [
489
+ PropertyPaneDropdown("selectedApp", {
490
+ label: "App",
491
+ disabled: this.dropDownProps.disabled,
492
+ options: this.dropDownProps.options,
493
+ selectedKey: this.dropDownProps.selectedKey,
494
+ }),
495
+ PropertyPaneLabel("selectedAppDecription", {
496
+ text: this.appDescription,
497
+ }),
498
+ ],
499
+ }
500
+ const cfgAppSelector = this.hideAppSelectorWhenAppLoaded ? [] : [appSelector];
501
+
502
+ return {
503
+ pages: [
504
+ {
505
+ groups: [
506
+ ...cfgButtonGroup,
507
+ ...cfgAppSelector,
508
+ {
509
+ groupFields: [
510
+ this.CustomWebpartConfigurationField(
511
+ "SPFxExtensionAppConfiguration"
512
+ ),
513
+ ],
514
+ },
515
+ ],
516
+ },
517
+ ],
518
+ };
519
+ }
520
+
521
+
522
+
523
+ // private _getEnvironmentMessage(): Promise<string> {
524
+ // if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook
525
+ // return this.context.sdks.microsoftTeams.teamsJs.app.getContext()
526
+ // .then(context => {
527
+ // let environmentMessage: string = "";
528
+ // switch (context.app.host.name) {
529
+ // case "Office": // running in Office
530
+ // environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment;
531
+ // break;
532
+ // case "Outlook": // running in Outlook
533
+ // environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment;
534
+ // break;
535
+ // case "Teams": // running in Teams
536
+ // case "TeamsModern":
537
+ // environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
538
+ // break;
539
+ // default:
540
+ // environmentMessage = strings.UnknownEnvironment;
541
+ // }
542
+
543
+ // return environmentMessage;
544
+ // });
545
+ // }
546
+
547
+ // return Promise.resolve(this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment);
548
+ // }
549
+
550
+ protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
551
+ if (!currentTheme) {
552
+ return;
553
+ }
554
+
555
+ this._isDarkTheme = !!currentTheme.isInverted;
556
+ const {
557
+ semanticColors
558
+ } = currentTheme;
559
+
560
+ if (semanticColors) {
561
+ this.domElement.style.setProperty("--bodyText", semanticColors.bodyText || null);
562
+ this.domElement.style.setProperty("--link", semanticColors.link || null);
563
+ this.domElement.style.setProperty("--linkHovered", semanticColors.linkHovered || null);
564
+ }
565
+
566
+ }
567
+
568
+ protected get dataVersion(): Version {
569
+ return Version.parse("1.0");
570
+ }
571
+
572
+
573
+ }
@@ -0,0 +1,16 @@
1
+ define([], function() {
2
+ return {
3
+ "PropertyPaneDescription": "Description",
4
+ "BasicGroupName": "Group Name",
5
+ "DescriptionFieldLabel": "Description Field",
6
+ "AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part",
7
+ "AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app",
8
+ "AppLocalEnvironmentOffice": "The app is running on your local environment in office.com",
9
+ "AppLocalEnvironmentOutlook": "The app is running on your local environment in Outlook",
10
+ "AppSharePointEnvironment": "The app is running on SharePoint page",
11
+ "AppTeamsTabEnvironment": "The app is running in Microsoft Teams",
12
+ "AppOfficeEnvironment": "The app is running in office.com",
13
+ "AppOutlookEnvironment": "The app is running in Outlook",
14
+ "UnknownEnvironment": "The app is running in an unknown environment"
15
+ }
16
+ });
@@ -0,0 +1,19 @@
1
+ declare interface ISpfxExtensionloaderWebPartStrings {
2
+ PropertyPaneDescription: string;
3
+ BasicGroupName: string;
4
+ DescriptionFieldLabel: string;
5
+ AppLocalEnvironmentSharePoint: string;
6
+ AppLocalEnvironmentTeams: string;
7
+ AppLocalEnvironmentOffice: string;
8
+ AppLocalEnvironmentOutlook: string;
9
+ AppSharePointEnvironment: string;
10
+ AppTeamsTabEnvironment: string;
11
+ AppOfficeEnvironment: string;
12
+ AppOutlookEnvironment: string;
13
+ UnknownEnvironment: string;
14
+ }
15
+
16
+ declare module 'SpfxExtensionloaderWebPartStrings' {
17
+ const strings: ISpfxExtensionloaderWebPartStrings;
18
+ export = strings;
19
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "extends": "./node_modules/@microsoft/rush-stack-compiler-5.3/includes/tsconfig-web.json",
3
+ "compilerOptions": {
4
+ "target": "ESNext",
5
+ "forceConsistentCasingInFileNames": true,
6
+ "module": "esnext",
7
+ "moduleResolution": "Bundler",
8
+ "jsx": "react",
9
+ "declaration": true,
10
+ "sourceMap": true,
11
+ "strict": true,
12
+ "experimentalDecorators": true,
13
+ "skipLibCheck": true,
14
+ "outDir": "lib",
15
+ "inlineSources": false,
16
+ "noImplicitAny": true,
17
+
18
+ "allowSyntheticDefaultImports": true,
19
+ "esModuleInterop": true,
20
+ "typeRoots": ["./node_modules/@types", "./node_modules/@microsoft"],
21
+ "types": ["webpack-env"],
22
+ "lib": ["ESNext", "DOM"]
23
+ },
24
+ "include": ["src/**/*.ts", "src/**/*.tsx"]
25
+ }