@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.
- package/.npmignore +16 -0
- package/.nvmrc +1 -0
- package/config/config.json +27 -0
- package/config/deploy-azure-storage.json +7 -0
- package/config/package-solution.json +51 -0
- package/config/sass.json +3 -0
- package/config/serve.json +18 -0
- package/config/write-manifests.json +4 -0
- package/dist/8d1029da-85e6-48cc-aaaf-37a5bbc0b9be.manifest.json +2 -2
- package/dist/d6ca1fc2-0591-4c6d-8a25-cae3262c017b.manifest.json +2 -2
- package/dist/debug/83e13c11-682e-4eaa-9ae0-74617ca28f96/Extension_8d1029da-85e6-48cc-aaaf-37a5bbc0b9be.xml +1 -1
- package/dist/debug/83e13c11-682e-4eaa-9ae0-74617ca28f96/WebPart_d6ca1fc2-0591-4c6d-8a25-cae3262c017b.xml +1 -1
- package/dist/debug/AppManifest.xml +1 -1
- package/dist/debug/ClientSideAssets/{spfx-extension-application-customizer_a8132780a6e1626ca4ef.js → spfx-extension-application-customizer_840b4102fa2c27e7913b.js} +1 -1
- package/dist/{spfx-extension-loader_d2e5154cdd7a4b34af83.js → debug/ClientSideAssets/spfx-extension-loader_7703538ecb97e35e6fe9.js} +1 -1
- package/dist/{spfx-extensionloader-web-part_e4f8ad9f3b2be2ee380e.js → debug/ClientSideAssets/spfx-extensionloader-web-part_ef8308f26188dc2ae907.js} +1 -1
- package/dist/debug/ClientSideAssets.xml +1 -1
- package/dist/debug/ClientSideAssets.xml.config.xml +1 -1
- package/dist/debug/_rels/ClientSideAssets.xml.rels +1 -1
- package/dist/debug/feature_83e13c11-682e-4eaa-9ae0-74617ca28f96.xml +1 -1
- package/dist/debug/feature_83e13c11-682e-4eaa-9ae0-74617ca28f96.xml.config.xml +1 -1
- package/dist/deploy/sp-fx-extensions.sppkg +0 -0
- package/dist/{spfx-extension-application-customizer_a8132780a6e1626ca4ef.js → spfx-extension-application-customizer_840b4102fa2c27e7913b.js} +1 -1
- package/dist/{debug/ClientSideAssets/spfx-extension-loader_d2e5154cdd7a4b34af83.js → spfx-extension-loader_7703538ecb97e35e6fe9.js} +1 -1
- package/dist/{debug/ClientSideAssets/spfx-extensionloader-web-part_e4f8ad9f3b2be2ee380e.js → spfx-extensionloader-web-part_ef8308f26188dc2ae907.js} +1 -1
- package/gulpfile.js +196 -0
- package/package.json +12 -2
- package/sharepoint/assets/ClientSideInstance.xml +9 -0
- package/sharepoint/assets/elements.xml +9 -0
- package/src/@types/globals.d.ts +15 -0
- package/src/extensions/spfxExtension/SpfxExtensionApplicationCustomizer.manifest.json +17 -0
- package/src/extensions/spfxExtension/SpfxExtensionApplicationCustomizer.ts +52 -0
- package/src/extensions/spfxExtension/loc/en-us.js +5 -0
- package/src/extensions/spfxExtension/loc/myStrings.d.ts +8 -0
- package/src/index.ts +1 -0
- package/src/services/initCoreService.ts +46 -0
- package/src/utilities/constants.ts +4 -0
- package/src/webparts/spfxExtensionloader/SpfxExtensionloaderWebPart.manifest.json +35 -0
- package/src/webparts/spfxExtensionloader/SpfxExtensionloaderWebPart.module.scss +185 -0
- package/src/webparts/spfxExtensionloader/SpfxExtensionloaderWebPart.module.scss.ts +24 -0
- package/src/webparts/spfxExtensionloader/SpfxExtensionloaderWebPart.ts +573 -0
- package/src/webparts/spfxExtensionloader/assets/welcome-dark.png +0 -0
- package/src/webparts/spfxExtensionloader/assets/welcome-light.png +0 -0
- package/src/webparts/spfxExtensionloader/loc/en-us.js +16 -0
- package/src/webparts/spfxExtensionloader/loc/mystrings.d.ts +19 -0
- package/teams/3be36e80-4431-4b52-99c5-0a339b4e696e_color.png +0 -0
- package/teams/3be36e80-4431-4b52-99c5-0a339b4e696e_outline.png +0 -0
- 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
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+
}
|
|
Binary file
|
|
Binary file
|
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
|
+
}
|