piral-blazor 1.0.0-pre.2217 → 1.0.0
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/LICENSE +1 -1
- package/README.md +55 -12
- package/convert.d.ts +13 -10
- package/convert.js +17 -12
- package/esm/converter.d.ts +10 -6
- package/esm/converter.js +164 -48
- package/esm/converter.js.map +1 -1
- package/esm/create.d.ts +21 -1
- package/esm/create.js +29 -15
- package/esm/create.js.map +1 -1
- package/esm/dependencies.d.ts +6 -3
- package/esm/dependencies.js +104 -14
- package/esm/dependencies.js.map +1 -1
- package/esm/events.d.ts +6 -0
- package/esm/events.js +145 -0
- package/esm/events.js.map +1 -0
- package/esm/interop.d.ts +29 -0
- package/esm/interop.js +205 -0
- package/esm/interop.js.map +1 -0
- package/esm/navigation.d.ts +2 -0
- package/esm/navigation.js +30 -0
- package/esm/navigation.js.map +1 -0
- package/esm/types.d.ts +97 -4
- package/infra.codegen +53 -68
- package/lib/converter.d.ts +10 -6
- package/lib/converter.js +164 -48
- package/lib/converter.js.map +1 -1
- package/lib/create.d.ts +21 -1
- package/lib/create.js +31 -17
- package/lib/create.js.map +1 -1
- package/lib/dependencies.d.ts +6 -3
- package/lib/dependencies.js +104 -14
- package/lib/dependencies.js.map +1 -1
- package/lib/events.d.ts +6 -0
- package/lib/events.js +154 -0
- package/lib/events.js.map +1 -0
- package/lib/index.js +1 -1
- package/lib/interop.d.ts +29 -0
- package/lib/interop.js +226 -0
- package/lib/interop.js.map +1 -0
- package/lib/navigation.d.ts +2 -0
- package/lib/navigation.js +35 -0
- package/lib/navigation.js.map +1 -0
- package/lib/types.d.ts +97 -4
- package/package.json +26 -13
- package/src/converter.ts +237 -57
- package/src/create.ts +53 -9
- package/src/dependencies.ts +122 -14
- package/src/events.ts +174 -0
- package/src/interop.ts +273 -0
- package/src/navigation.ts +36 -0
- package/src/types.ts +115 -4
- package/convert.ts +0 -17
- package/esm/internal/Environment.d.ts +0 -3
- package/esm/internal/Environment.js +0 -6
- package/esm/internal/Environment.js.map +0 -1
- package/esm/internal/Platform/BootConfig.d.ts +0 -20
- package/esm/internal/Platform/BootConfig.js +0 -10
- package/esm/internal/Platform/BootConfig.js.map +0 -1
- package/esm/internal/Platform/Mono/MonoDebugger.d.ts +0 -3
- package/esm/internal/Platform/Mono/MonoDebugger.js +0 -43
- package/esm/internal/Platform/Mono/MonoDebugger.js.map +0 -1
- package/esm/internal/Platform/Mono/MonoPlatform.d.ts +0 -2
- package/esm/internal/Platform/Mono/MonoPlatform.js +0 -403
- package/esm/internal/Platform/Mono/MonoPlatform.js.map +0 -1
- package/esm/internal/Platform/Mono/TimezoneDataFile.d.ts +0 -1
- package/esm/internal/Platform/Mono/TimezoneDataFile.js +0 -51
- package/esm/internal/Platform/Mono/TimezoneDataFile.js.map +0 -1
- package/esm/internal/Platform/Platform.d.ts +0 -31
- package/esm/internal/Platform/Platform.js +0 -2
- package/esm/internal/Platform/Platform.js.map +0 -1
- package/esm/internal/Platform/Url.d.ts +0 -2
- package/esm/internal/Platform/Url.js +0 -11
- package/esm/internal/Platform/Url.js.map +0 -1
- package/esm/internal/Platform/WebAssemblyConfigLoader.d.ts +0 -4
- package/esm/internal/Platform/WebAssemblyConfigLoader.js +0 -64
- package/esm/internal/Platform/WebAssemblyConfigLoader.js.map +0 -1
- package/esm/internal/Platform/WebAssemblyResourceLoader.d.ts +0 -24
- package/esm/internal/Platform/WebAssemblyResourceLoader.js +0 -223
- package/esm/internal/Platform/WebAssemblyResourceLoader.js.map +0 -1
- package/esm/internal/Platform/WebAssemblyStartOptions.d.ts +0 -13
- package/esm/internal/Platform/WebAssemblyStartOptions.js +0 -2
- package/esm/internal/Platform/WebAssemblyStartOptions.js.map +0 -1
- package/esm/internal/Rendering/BrowserRenderer.d.ts +0 -38
- package/esm/internal/Rendering/BrowserRenderer.js +0 -458
- package/esm/internal/Rendering/BrowserRenderer.js.map +0 -1
- package/esm/internal/Rendering/ElementReferenceCapture.d.ts +0 -1
- package/esm/internal/Rendering/ElementReferenceCapture.js +0 -24
- package/esm/internal/Rendering/ElementReferenceCapture.js.map +0 -1
- package/esm/internal/Rendering/EventDelegator.d.ts +0 -20
- package/esm/internal/Rendering/EventDelegator.js +0 -236
- package/esm/internal/Rendering/EventDelegator.js.map +0 -1
- package/esm/internal/Rendering/EventFieldInfo.d.ts +0 -6
- package/esm/internal/Rendering/EventFieldInfo.js +0 -32
- package/esm/internal/Rendering/EventFieldInfo.js.map +0 -1
- package/esm/internal/Rendering/EventForDotNet.d.ts +0 -10
- package/esm/internal/Rendering/EventForDotNet.js +0 -194
- package/esm/internal/Rendering/EventForDotNet.js.map +0 -1
- package/esm/internal/Rendering/LogicalElements.d.ts +0 -19
- package/esm/internal/Rendering/LogicalElements.js +0 -250
- package/esm/internal/Rendering/LogicalElements.js.map +0 -1
- package/esm/internal/Rendering/RenderBatch/BinaryDecoder.d.ts +0 -5
- package/esm/internal/Rendering/RenderBatch/BinaryDecoder.js +0 -34
- package/esm/internal/Rendering/RenderBatch/BinaryDecoder.js.map +0 -1
- package/esm/internal/Rendering/RenderBatch/OutOfProcessRenderBatch.d.ts +0 -18
- package/esm/internal/Rendering/RenderBatch/OutOfProcessRenderBatch.js +0 -190
- package/esm/internal/Rendering/RenderBatch/OutOfProcessRenderBatch.js.map +0 -1
- package/esm/internal/Rendering/RenderBatch/RenderBatch.d.ts +0 -87
- package/esm/internal/Rendering/RenderBatch/RenderBatch.js +0 -26
- package/esm/internal/Rendering/RenderBatch/RenderBatch.js.map +0 -1
- package/esm/internal/Rendering/RenderBatch/SharedMemoryRenderBatch.d.ts +0 -52
- package/esm/internal/Rendering/RenderBatch/SharedMemoryRenderBatch.js +0 -103
- package/esm/internal/Rendering/RenderBatch/SharedMemoryRenderBatch.js.map +0 -1
- package/esm/internal/Rendering/RenderBatch/Utf8Decoder.d.ts +0 -1
- package/esm/internal/Rendering/RenderBatch/Utf8Decoder.js +0 -63
- package/esm/internal/Rendering/RenderBatch/Utf8Decoder.js.map +0 -1
- package/esm/internal/Rendering/Renderer.d.ts +0 -8
- package/esm/internal/Rendering/Renderer.js +0 -69
- package/esm/internal/Rendering/Renderer.js.map +0 -1
- package/esm/internal/Rendering/RendererEventDispatcher.d.ts +0 -4
- package/esm/internal/Rendering/RendererEventDispatcher.js +0 -11
- package/esm/internal/Rendering/RendererEventDispatcher.js.map +0 -1
- package/esm/internal/Services/NavigationManager.d.ts +0 -16
- package/esm/internal/Services/NavigationManager.js +0 -138
- package/esm/internal/Services/NavigationManager.js.map +0 -1
- package/esm/internal/globals.d.ts +0 -1
- package/esm/internal/globals.js +0 -5
- package/esm/internal/globals.js.map +0 -1
- package/esm/internal/index.d.ts +0 -15
- package/esm/internal/index.js +0 -152
- package/esm/internal/index.js.map +0 -1
- package/lib/internal/Environment.d.ts +0 -3
- package/lib/internal/Environment.js +0 -9
- package/lib/internal/Environment.js.map +0 -1
- package/lib/internal/Platform/BootConfig.d.ts +0 -20
- package/lib/internal/Platform/BootConfig.js +0 -13
- package/lib/internal/Platform/BootConfig.js.map +0 -1
- package/lib/internal/Platform/Mono/MonoDebugger.d.ts +0 -3
- package/lib/internal/Platform/Mono/MonoDebugger.js +0 -48
- package/lib/internal/Platform/Mono/MonoDebugger.js.map +0 -1
- package/lib/internal/Platform/Mono/MonoPlatform.d.ts +0 -2
- package/lib/internal/Platform/Mono/MonoPlatform.js +0 -406
- package/lib/internal/Platform/Mono/MonoPlatform.js.map +0 -1
- package/lib/internal/Platform/Mono/TimezoneDataFile.d.ts +0 -1
- package/lib/internal/Platform/Mono/TimezoneDataFile.js +0 -55
- package/lib/internal/Platform/Mono/TimezoneDataFile.js.map +0 -1
- package/lib/internal/Platform/Platform.d.ts +0 -31
- package/lib/internal/Platform/Platform.js +0 -3
- package/lib/internal/Platform/Platform.js.map +0 -1
- package/lib/internal/Platform/Url.d.ts +0 -2
- package/lib/internal/Platform/Url.js +0 -16
- package/lib/internal/Platform/Url.js.map +0 -1
- package/lib/internal/Platform/WebAssemblyConfigLoader.d.ts +0 -4
- package/lib/internal/Platform/WebAssemblyConfigLoader.js +0 -67
- package/lib/internal/Platform/WebAssemblyConfigLoader.js.map +0 -1
- package/lib/internal/Platform/WebAssemblyResourceLoader.d.ts +0 -24
- package/lib/internal/Platform/WebAssemblyResourceLoader.js +0 -226
- package/lib/internal/Platform/WebAssemblyResourceLoader.js.map +0 -1
- package/lib/internal/Platform/WebAssemblyStartOptions.d.ts +0 -13
- package/lib/internal/Platform/WebAssemblyStartOptions.js +0 -3
- package/lib/internal/Platform/WebAssemblyStartOptions.js.map +0 -1
- package/lib/internal/Rendering/BrowserRenderer.d.ts +0 -38
- package/lib/internal/Rendering/BrowserRenderer.js +0 -461
- package/lib/internal/Rendering/BrowserRenderer.js.map +0 -1
- package/lib/internal/Rendering/ElementReferenceCapture.d.ts +0 -1
- package/lib/internal/Rendering/ElementReferenceCapture.js +0 -28
- package/lib/internal/Rendering/ElementReferenceCapture.js.map +0 -1
- package/lib/internal/Rendering/EventDelegator.d.ts +0 -20
- package/lib/internal/Rendering/EventDelegator.js +0 -239
- package/lib/internal/Rendering/EventDelegator.js.map +0 -1
- package/lib/internal/Rendering/EventFieldInfo.d.ts +0 -6
- package/lib/internal/Rendering/EventFieldInfo.js +0 -35
- package/lib/internal/Rendering/EventFieldInfo.js.map +0 -1
- package/lib/internal/Rendering/EventForDotNet.d.ts +0 -10
- package/lib/internal/Rendering/EventForDotNet.js +0 -197
- package/lib/internal/Rendering/EventForDotNet.js.map +0 -1
- package/lib/internal/Rendering/LogicalElements.d.ts +0 -19
- package/lib/internal/Rendering/LogicalElements.js +0 -265
- package/lib/internal/Rendering/LogicalElements.js.map +0 -1
- package/lib/internal/Rendering/RenderBatch/BinaryDecoder.d.ts +0 -5
- package/lib/internal/Rendering/RenderBatch/BinaryDecoder.js +0 -42
- package/lib/internal/Rendering/RenderBatch/BinaryDecoder.js.map +0 -1
- package/lib/internal/Rendering/RenderBatch/OutOfProcessRenderBatch.d.ts +0 -18
- package/lib/internal/Rendering/RenderBatch/OutOfProcessRenderBatch.js +0 -193
- package/lib/internal/Rendering/RenderBatch/OutOfProcessRenderBatch.js.map +0 -1
- package/lib/internal/Rendering/RenderBatch/RenderBatch.d.ts +0 -87
- package/lib/internal/Rendering/RenderBatch/RenderBatch.js +0 -29
- package/lib/internal/Rendering/RenderBatch/RenderBatch.js.map +0 -1
- package/lib/internal/Rendering/RenderBatch/SharedMemoryRenderBatch.d.ts +0 -52
- package/lib/internal/Rendering/RenderBatch/SharedMemoryRenderBatch.js +0 -106
- package/lib/internal/Rendering/RenderBatch/SharedMemoryRenderBatch.js.map +0 -1
- package/lib/internal/Rendering/RenderBatch/Utf8Decoder.d.ts +0 -1
- package/lib/internal/Rendering/RenderBatch/Utf8Decoder.js +0 -66
- package/lib/internal/Rendering/RenderBatch/Utf8Decoder.js.map +0 -1
- package/lib/internal/Rendering/Renderer.d.ts +0 -8
- package/lib/internal/Rendering/Renderer.js +0 -76
- package/lib/internal/Rendering/Renderer.js.map +0 -1
- package/lib/internal/Rendering/RendererEventDispatcher.d.ts +0 -4
- package/lib/internal/Rendering/RendererEventDispatcher.js +0 -16
- package/lib/internal/Rendering/RendererEventDispatcher.js.map +0 -1
- package/lib/internal/Services/NavigationManager.d.ts +0 -16
- package/lib/internal/Services/NavigationManager.js +0 -144
- package/lib/internal/Services/NavigationManager.js.map +0 -1
- package/lib/internal/globals.d.ts +0 -1
- package/lib/internal/globals.js +0 -7
- package/lib/internal/globals.js.map +0 -1
- package/lib/internal/index.d.ts +0 -15
- package/lib/internal/index.js +0 -161
- package/lib/internal/index.js.map +0 -1
- package/src/internal/Environment.ts +0 -11
- package/src/internal/Platform/BootConfig.ts +0 -21
- package/src/internal/Platform/Mono/MonoDebugger.ts +0 -48
- package/src/internal/Platform/Mono/MonoPlatform.ts +0 -494
- package/src/internal/Platform/Mono/MonoTypes.d.ts +0 -27
- package/src/internal/Platform/Mono/TimezoneDataFile.ts +0 -46
- package/src/internal/Platform/Platform.ts +0 -40
- package/src/internal/Platform/Url.ts +0 -11
- package/src/internal/Platform/WebAssemblyConfigLoader.ts +0 -34
- package/src/internal/Platform/WebAssemblyResourceLoader.ts +0 -234
- package/src/internal/Platform/WebAssemblyStartOptions.ts +0 -22
- package/src/internal/Rendering/BrowserRenderer.ts +0 -616
- package/src/internal/Rendering/ElementReferenceCapture.ts +0 -27
- package/src/internal/Rendering/EventDelegator.ts +0 -293
- package/src/internal/Rendering/EventFieldInfo.ts +0 -31
- package/src/internal/Rendering/EventForDotNet.ts +0 -370
- package/src/internal/Rendering/LogicalElements.ts +0 -289
- package/src/internal/Rendering/RenderBatch/BinaryDecoder.ts +0 -43
- package/src/internal/Rendering/RenderBatch/OutOfProcessRenderBatch.ts +0 -244
- package/src/internal/Rendering/RenderBatch/RenderBatch.ts +0 -100
- package/src/internal/Rendering/RenderBatch/SharedMemoryRenderBatch.ts +0 -137
- package/src/internal/Rendering/RenderBatch/Utf8Decoder.ts +0 -66
- package/src/internal/Rendering/Renderer.ts +0 -98
- package/src/internal/Rendering/RendererEventDispatcher.ts +0 -20
- package/src/internal/Services/NavigationManager.ts +0 -157
- package/src/internal/globals.ts +0 -5
- package/src/internal/index.ts +0 -170
package/src/dependencies.ts
CHANGED
|
@@ -1,24 +1,132 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PiletMetadata } from 'piral-core';
|
|
2
|
+
import { loadBlazorPilet, loadResource, loadResourceWithSymbol, unloadBlazorPilet, unloadResource } from './interop';
|
|
2
3
|
import type { createConverter } from './converter';
|
|
4
|
+
import type { BlazorDependencyLoader, BlazorRootConfig } from './types';
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
const loadedDependencies = (window.$blazorDependencies ??= []);
|
|
7
|
+
const depsWithPrios = (window.$blazorDependencyPrios ??= []);
|
|
8
|
+
|
|
9
|
+
export function createDependencyLoader(convert: ReturnType<typeof createConverter>) {
|
|
10
|
+
const definedBlazorReferences: Array<string> = [];
|
|
11
|
+
const loadedBlazorPilets: Array<string> = [];
|
|
12
|
+
let dependency: BlazorDependencyLoader;
|
|
6
13
|
|
|
7
14
|
return {
|
|
8
15
|
getDependency() {
|
|
9
16
|
return dependency;
|
|
10
17
|
},
|
|
11
|
-
defineBlazorReferences(references) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
defineBlazorReferences(references: Array<string>, meta: Partial<PiletMetadata> = {}, satellites = {}, prio = 0) {
|
|
19
|
+
prio = Math.max(prio, 0);
|
|
20
|
+
|
|
21
|
+
const depWithPrio = {
|
|
22
|
+
prio,
|
|
23
|
+
load() {
|
|
24
|
+
return Promise.resolve();
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let result: false | Promise<void> = false;
|
|
29
|
+
const load = async ([_, capabilities]: BlazorRootConfig) => {
|
|
30
|
+
// let others finish first
|
|
31
|
+
await Promise.all(depsWithPrios.filter((m) => m.prio > prio).map((m) => m.load()));
|
|
32
|
+
|
|
33
|
+
window.dispatchEvent(new CustomEvent('loading-blazor-pilet', { detail: meta }));
|
|
34
|
+
|
|
35
|
+
if (capabilities.includes('load')) {
|
|
36
|
+
// new loading mechanism
|
|
37
|
+
|
|
38
|
+
if (!capabilities.includes('language')) {
|
|
39
|
+
satellites = undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const dependencies = references.filter((m) => m.endsWith('.dll'));
|
|
43
|
+
const dllUrl = dependencies.pop();
|
|
44
|
+
const piletName = dllUrl.substring(0, dllUrl.length - 4);
|
|
45
|
+
const piletPdb = `${piletName}.pdb`;
|
|
46
|
+
const pdbUrl = references.find((m) => m === piletPdb);
|
|
47
|
+
const id = Math.random().toString(26).substring(2);
|
|
48
|
+
|
|
49
|
+
await loadBlazorPilet(id, {
|
|
50
|
+
name: meta.name || '(unknown)',
|
|
51
|
+
version: meta.version || '0.0.0',
|
|
52
|
+
config: JSON.stringify(meta.config || {}),
|
|
53
|
+
baseUrl: meta.basePath || dllUrl.substring(0, dllUrl.lastIndexOf('/')).replace('/_framework/', '/'),
|
|
54
|
+
dependencies,
|
|
55
|
+
satellites,
|
|
56
|
+
dllUrl,
|
|
57
|
+
pdbUrl,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
loadedBlazorPilets.push(id);
|
|
61
|
+
} else {
|
|
62
|
+
// old loading mechanism
|
|
63
|
+
|
|
64
|
+
for (const dllUrl of references) {
|
|
65
|
+
const dllName = dllUrl.substring(dllUrl.lastIndexOf('/') + 1);
|
|
66
|
+
|
|
67
|
+
if (dllUrl.endsWith('.dll')) {
|
|
68
|
+
const entry = loadedDependencies.find((m) => m.name === dllName);
|
|
69
|
+
|
|
70
|
+
if (entry) {
|
|
71
|
+
entry.count++;
|
|
72
|
+
await entry.promise;
|
|
73
|
+
} else {
|
|
74
|
+
const urlWithoutExtension = dllUrl.substring(0, dllUrl.length - 4);
|
|
75
|
+
const pdbName = `${urlWithoutExtension}.pdb`;
|
|
76
|
+
const pdbUrl = references.find((m) => m === pdbName);
|
|
77
|
+
const promise = pdbUrl ? loadResourceWithSymbol(dllUrl, pdbUrl) : loadResource(dllUrl);
|
|
78
|
+
|
|
79
|
+
loadedDependencies.push({
|
|
80
|
+
name: dllName,
|
|
81
|
+
url: dllUrl,
|
|
82
|
+
count: 1,
|
|
83
|
+
promise,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await promise;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
definedBlazorReferences.push(dllName);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// inform remaining that this one finished
|
|
95
|
+
window.dispatchEvent(new CustomEvent('loaded-blazor-pilet', { detail: meta }));
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
depWithPrio.load = () => {
|
|
99
|
+
if (!result) {
|
|
100
|
+
result = convert.loader.then(load);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result;
|
|
104
|
+
};
|
|
105
|
+
result = !convert.lazy && convert.loader.then(load);
|
|
106
|
+
dependency = (config) => result || (result = load(config));
|
|
107
|
+
|
|
108
|
+
if (prio) {
|
|
109
|
+
depsWithPrios.push(depWithPrio);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
async releaseBlazorReferences() {
|
|
113
|
+
const references = definedBlazorReferences.splice(0, definedBlazorReferences.length);
|
|
114
|
+
const ids = loadedBlazorPilets.splice(0, loadedBlazorPilets.length);
|
|
115
|
+
|
|
116
|
+
// old way of loading
|
|
117
|
+
for (const reference of references) {
|
|
118
|
+
const entry = loadedDependencies.find((m) => m.name === reference);
|
|
119
|
+
|
|
120
|
+
if (--entry.count === 0) {
|
|
121
|
+
loadedDependencies.splice(loadedDependencies.indexOf(entry), 1);
|
|
122
|
+
await unloadResource(entry.url);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// new way of loading
|
|
127
|
+
for (const id of ids) {
|
|
128
|
+
await unloadBlazorPilet(id);
|
|
129
|
+
}
|
|
22
130
|
},
|
|
23
131
|
};
|
|
24
132
|
}
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { ExtensionRegistration } from 'piral-core';
|
|
2
|
+
import { isInternalNavigation, performInternalNavigation } from './navigation';
|
|
3
|
+
|
|
4
|
+
const blazorRootId = 'blazor-root';
|
|
5
|
+
const eventParents: Array<HTMLElement> = [];
|
|
6
|
+
|
|
7
|
+
const globalEventNames = [
|
|
8
|
+
'abort',
|
|
9
|
+
'blur',
|
|
10
|
+
'change',
|
|
11
|
+
'error',
|
|
12
|
+
'focus',
|
|
13
|
+
'load',
|
|
14
|
+
'loadend',
|
|
15
|
+
'loadstart',
|
|
16
|
+
'mouseenter',
|
|
17
|
+
'mouseleave',
|
|
18
|
+
'progress',
|
|
19
|
+
'reset',
|
|
20
|
+
'scroll',
|
|
21
|
+
'submit',
|
|
22
|
+
'unload',
|
|
23
|
+
'DOMNodeInsertedIntoDocument',
|
|
24
|
+
'DOMNodeRemovedFromDocument',
|
|
25
|
+
'click',
|
|
26
|
+
'dblclick',
|
|
27
|
+
'mousedown',
|
|
28
|
+
'mousemove',
|
|
29
|
+
'mouseup',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const eventNames = {
|
|
33
|
+
render: 'render-blazor-extension',
|
|
34
|
+
navigate: 'navigate-blazor',
|
|
35
|
+
piral: 'piral-blazor',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function isRooted(target: HTMLElement) {
|
|
39
|
+
let parent = target.parentElement;
|
|
40
|
+
|
|
41
|
+
while (parent) {
|
|
42
|
+
if (parent.id === blazorRootId) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
parent = parent.parentElement;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function findTarget(target: HTMLElement = document.body) {
|
|
53
|
+
if (eventParents.length === 0) {
|
|
54
|
+
return target;
|
|
55
|
+
} else if (target === document.body) {
|
|
56
|
+
return eventParents[0];
|
|
57
|
+
} else {
|
|
58
|
+
return target;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function dispatchToRoot(event: any) {
|
|
63
|
+
isInternalNavigation(event) && performInternalNavigation(event);
|
|
64
|
+
|
|
65
|
+
// the mutation event cannot be cloned (at least in Webkit-based browsers)
|
|
66
|
+
if (!(event instanceof MutationEvent) && !event.processed) {
|
|
67
|
+
const eventClone = new event.constructor(event.type, event);
|
|
68
|
+
document.getElementById(blazorRootId)?.dispatchEvent(eventClone);
|
|
69
|
+
// make sure to only process every event once; even though multiple boundaries might be active
|
|
70
|
+
event.processed = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function emitRenderEvent(
|
|
75
|
+
source: HTMLElement,
|
|
76
|
+
name: string,
|
|
77
|
+
params: any,
|
|
78
|
+
sourceRef: any,
|
|
79
|
+
fallbackComponent: string | null,
|
|
80
|
+
) {
|
|
81
|
+
const target = findTarget(source);
|
|
82
|
+
const empty = typeof fallbackComponent === 'string' ? () => {} : undefined;
|
|
83
|
+
const order =
|
|
84
|
+
typeof sourceRef !== 'undefined'
|
|
85
|
+
? (elements: Array<ExtensionRegistration>) => {
|
|
86
|
+
const oldItems = elements.map((el, id) => ({
|
|
87
|
+
id,
|
|
88
|
+
pilet: el.pilet,
|
|
89
|
+
defaults: el.defaults ?? {},
|
|
90
|
+
}));
|
|
91
|
+
const newItems: Array<{ id: number }> = sourceRef.invokeMethod('Order', oldItems);
|
|
92
|
+
return newItems.map(({ id }) => elements[id]).filter(Boolean);
|
|
93
|
+
}
|
|
94
|
+
: undefined;
|
|
95
|
+
const eventInit = {
|
|
96
|
+
bubbles: true,
|
|
97
|
+
detail: {
|
|
98
|
+
target,
|
|
99
|
+
props: {
|
|
100
|
+
name,
|
|
101
|
+
params,
|
|
102
|
+
empty,
|
|
103
|
+
order,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
const delayEmit = () =>
|
|
108
|
+
requestAnimationFrame(() => {
|
|
109
|
+
if (!isRooted(target)) {
|
|
110
|
+
target.dispatchEvent(new CustomEvent(eventNames.render, eventInit));
|
|
111
|
+
} else {
|
|
112
|
+
delayEmit();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
delayEmit();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function emitPiralEvent(type: string, args: any) {
|
|
119
|
+
document.body.dispatchEvent(
|
|
120
|
+
new CustomEvent(eventNames.piral, {
|
|
121
|
+
bubbles: false,
|
|
122
|
+
detail: {
|
|
123
|
+
type,
|
|
124
|
+
args,
|
|
125
|
+
},
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function emitNavigateEvent(source: HTMLElement, to: string, replace = false, state?: any) {
|
|
131
|
+
findTarget(source).dispatchEvent(
|
|
132
|
+
new CustomEvent(eventNames.navigate, {
|
|
133
|
+
bubbles: true,
|
|
134
|
+
detail: {
|
|
135
|
+
to,
|
|
136
|
+
replace,
|
|
137
|
+
state,
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function attachEvents(
|
|
144
|
+
host: HTMLElement,
|
|
145
|
+
render: (ev: CustomEvent) => void,
|
|
146
|
+
navigate: (ev: CustomEvent) => void,
|
|
147
|
+
forward: (ev: CustomEvent) => void,
|
|
148
|
+
) {
|
|
149
|
+
eventParents.push(host);
|
|
150
|
+
host.addEventListener(eventNames.render, render, false);
|
|
151
|
+
host.addEventListener(eventNames.navigate, navigate, false);
|
|
152
|
+
|
|
153
|
+
if (eventParents.length === 1) {
|
|
154
|
+
document.body.addEventListener(eventNames.piral, forward, false);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return () => {
|
|
158
|
+
eventParents.splice(eventParents.indexOf(host), 1);
|
|
159
|
+
host.removeEventListener(eventNames.render, render, false);
|
|
160
|
+
host.removeEventListener(eventNames.navigate, navigate, false);
|
|
161
|
+
|
|
162
|
+
if (eventParents.length === 0) {
|
|
163
|
+
document.body.removeEventListener(eventNames.piral, forward, false);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function addGlobalEventListeners(el: HTMLElement) {
|
|
169
|
+
globalEventNames.forEach((eventName) => el.addEventListener(eventName, dispatchToRoot));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function removeGlobalEventListeners(el: HTMLElement) {
|
|
173
|
+
globalEventNames.forEach((eventName) => el.removeEventListener(eventName, dispatchToRoot));
|
|
174
|
+
}
|
package/src/interop.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { PiletApi } from 'piral-core';
|
|
2
|
+
import { emitRenderEvent, emitNavigateEvent, emitPiralEvent } from './events';
|
|
3
|
+
import type { BlazorLogLevel, BlazorRootConfig, WebAssemblyStartOptions } from './types';
|
|
4
|
+
|
|
5
|
+
const wasmLib = 'Microsoft.AspNetCore.Components.WebAssembly';
|
|
6
|
+
const coreLib = 'Piral.Blazor.Core';
|
|
7
|
+
|
|
8
|
+
function isDotNet6OrBelow() {
|
|
9
|
+
return typeof window.Blazor._internal.NavigationLock === 'undefined';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function createBase() {
|
|
13
|
+
// Nothing found, we need to guess
|
|
14
|
+
const el = document.createElement('base');
|
|
15
|
+
let baseUrl = el.href;
|
|
16
|
+
|
|
17
|
+
// The main app is served by a script - but we don't know which one
|
|
18
|
+
// hence we just iterate over all the local ones and use the script
|
|
19
|
+
// that is served from the "shortest" route - should work almost
|
|
20
|
+
// always and if not - one can always explicitely set a <base> node
|
|
21
|
+
for (let i = document.scripts.length; i--; ) {
|
|
22
|
+
const s = document.scripts[i];
|
|
23
|
+
const src = s.getAttribute('src');
|
|
24
|
+
|
|
25
|
+
if (src && src.startsWith('/')) {
|
|
26
|
+
const segEnd = src.lastIndexOf('/');
|
|
27
|
+
const newUrl = src.substring(0, segEnd + 1);
|
|
28
|
+
|
|
29
|
+
if (baseUrl.split('/').length > newUrl.split('/').length) {
|
|
30
|
+
baseUrl = newUrl;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
el.href = baseUrl;
|
|
36
|
+
return document.head.appendChild(el);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function prepareForStartup() {
|
|
40
|
+
const originalApplyHotReload = window.Blazor._internal.applyHotReload;
|
|
41
|
+
const queue = [];
|
|
42
|
+
|
|
43
|
+
const applyChanges = (api: PiletApi) => {
|
|
44
|
+
const pilet = api.meta;
|
|
45
|
+
|
|
46
|
+
if (pilet.config && pilet.config.blazorHotReload) {
|
|
47
|
+
for (const item of queue.splice(0, queue.length)) {
|
|
48
|
+
item();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
window.Blazor._internal.applyHotReload = originalApplyHotReload;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
window.Blazor._internal.applyHotReload = function (...args) {
|
|
56
|
+
queue.push(() => originalApplyHotReload.apply(this, args));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return getCapabilities().then((capabilities) => ({
|
|
60
|
+
capabilities,
|
|
61
|
+
applyChanges,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function createBlazorStarter(publicPath: string): (opts: WebAssemblyStartOptions) => Promise<BlazorRootConfig> {
|
|
66
|
+
const root = document.body.appendChild(document.createElement('div'));
|
|
67
|
+
|
|
68
|
+
root.style.display = 'none';
|
|
69
|
+
root.id = 'blazor-root';
|
|
70
|
+
|
|
71
|
+
if (publicPath) {
|
|
72
|
+
const baseElement = document.head.querySelector('base') || createBase();
|
|
73
|
+
const originalBase = baseElement.href;
|
|
74
|
+
baseElement.href = publicPath;
|
|
75
|
+
|
|
76
|
+
return (opts) => {
|
|
77
|
+
const navManager = window.Blazor._internal.navigationManager;
|
|
78
|
+
|
|
79
|
+
//Overwrite to get NavigationManager in Blazor working, see https://github.com/smapiot/Piral.Blazor/issues/89
|
|
80
|
+
navManager.navigateTo = (
|
|
81
|
+
route: string,
|
|
82
|
+
opts: { forceLoad: boolean; replaceHistoryEntry: boolean; historyEntryState: any },
|
|
83
|
+
) => {
|
|
84
|
+
if (opts.forceLoad) {
|
|
85
|
+
location.href = route;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (route.startsWith(location.origin) && '') {
|
|
90
|
+
// normalize "local" absolute URLs
|
|
91
|
+
route = route.substring(location.origin.length);
|
|
92
|
+
} else if (/^https?:\/\//.test(route)) {
|
|
93
|
+
// prevent absolute URLs to be a standard navigation
|
|
94
|
+
location.href = route;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
window.Blazor.emitNavigateEvent(undefined, route, opts.replaceHistoryEntry, opts.historyEntryState);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
navManager.getBaseURI = () => originalBase;
|
|
102
|
+
|
|
103
|
+
return window.Blazor.start(opts)
|
|
104
|
+
.then(prepareForStartup)
|
|
105
|
+
.then(({ capabilities, applyChanges }) => {
|
|
106
|
+
baseElement.href = originalBase;
|
|
107
|
+
return [root, capabilities, applyChanges];
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return (opts) =>
|
|
113
|
+
window.Blazor.start(opts)
|
|
114
|
+
.then(prepareForStartup)
|
|
115
|
+
.then(({ capabilities, applyChanges }) => [root, capabilities, applyChanges]);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function computePath() {
|
|
119
|
+
try {
|
|
120
|
+
throw new Error();
|
|
121
|
+
} catch (t) {
|
|
122
|
+
const e = ('' + t.stack).match(/(https?|file|ftp|chrome-extension|moz-extension):\/\/[^)\n]+/g);
|
|
123
|
+
if (e) {
|
|
124
|
+
return e[0].replace(/^((?:https?|file|ftp|chrome-extension|moz-extension):\/\/.+)\/[^\/]+$/, '$1') + '/';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return '/';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function addScript(url: string) {
|
|
132
|
+
return new Promise<void>((resolve, reject) => {
|
|
133
|
+
const script = document.createElement('script');
|
|
134
|
+
script.src = url;
|
|
135
|
+
script.onerror = () => reject();
|
|
136
|
+
script.onload = () => resolve();
|
|
137
|
+
document.body.appendChild(script);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function processEvent(type: string, args: any) {
|
|
142
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'ProcessEvent', type, args);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function setLogLevel(logLevel: BlazorLogLevel) {
|
|
146
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'SetLogLevel', logLevel);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function createElement(moduleName: string, props: any): Promise<string> {
|
|
150
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'CreateElement', moduleName, props);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function updateElement(referenceId: string, props: any): Promise<string> {
|
|
154
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'UpdateElement', referenceId, props);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function destroyElement(referenceId: string): Promise<string> {
|
|
158
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'DestroyElement', referenceId);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function activate(moduleName: string, props: any): Promise<string> {
|
|
162
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'Activate', moduleName, props);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function reactivate(moduleName: string, referenceId: string, props: any): Promise<void> {
|
|
166
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'Reactivate', moduleName, referenceId, props).catch(() => {
|
|
167
|
+
// Apparently an older version of Piral.Blazor, which does not support this
|
|
168
|
+
// discard this error silently (in the future we may print warnings here)
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function deactivate(moduleName: string, referenceId: string): Promise<void> {
|
|
173
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'Deactivate', moduleName, referenceId);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function callNotifyLocationChanged(url: string, replace: boolean, state: any): Promise<void> {
|
|
177
|
+
if (isDotNet6OrBelow()) {
|
|
178
|
+
return window.DotNet.invokeMethodAsync(wasmLib, 'NotifyLocationChanged', url, replace);
|
|
179
|
+
} else {
|
|
180
|
+
if (state !== undefined && typeof state !== 'string') {
|
|
181
|
+
state = JSON.stringify(state);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return window.DotNet.invokeMethodAsync(wasmLib, 'NotifyLocationChanged', url, state, replace);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function setLanguage(language: string): Promise<void> {
|
|
189
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'SetLanguage', language);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function getCapabilities(): Promise<Array<string>> {
|
|
193
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'GetCapabilities').catch(() => {
|
|
194
|
+
// Apparently an older version of Piral.Blazor, which does not support this
|
|
195
|
+
// discard this error silently (in the future we may print warnings here)
|
|
196
|
+
return [];
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function loadResource(url: string): Promise<void> {
|
|
201
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'LoadComponentsFromLibrary', url);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function loadResourceWithSymbol(dllUrl: string, pdbUrl: string): Promise<void> {
|
|
205
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'LoadComponentsWithSymbolsFromLibrary', dllUrl, pdbUrl);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function unloadResource(url: string): Promise<void> {
|
|
209
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'UnloadComponentsFromLibrary', url);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface PiletData {
|
|
213
|
+
dllUrl: string;
|
|
214
|
+
pdbUrl?: string;
|
|
215
|
+
name: string;
|
|
216
|
+
version: string;
|
|
217
|
+
config: string;
|
|
218
|
+
baseUrl: string;
|
|
219
|
+
satellites?: Record<string, Array<string>>;
|
|
220
|
+
dependencies: Array<string>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function loadBlazorPilet(id: string, data: PiletData) {
|
|
224
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'LoadPilet', id, data);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function unloadBlazorPilet(id: string) {
|
|
228
|
+
return window.DotNet.invokeMethodAsync(coreLib, 'UnloadPilet', id);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function initialize(scriptUrl: string, publicPath: string, opts: WebAssemblyStartOptions = {}) {
|
|
232
|
+
if (typeof opts.loadBootResource !== 'function') {
|
|
233
|
+
opts.loadBootResource = (type, name, url) =>
|
|
234
|
+
type === 'dotnetjs' ? url : fetch(url, { method: 'GET', cache: 'no-cache' });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return new Promise<BlazorRootConfig>((resolve, reject) => {
|
|
238
|
+
const startBlazor = createBlazorStarter(publicPath);
|
|
239
|
+
const script = document.createElement('script');
|
|
240
|
+
script.src = scriptUrl;
|
|
241
|
+
script.setAttribute('autostart', 'false');
|
|
242
|
+
|
|
243
|
+
script.onerror = () => reject();
|
|
244
|
+
script.onload = () => {
|
|
245
|
+
Object.assign(window.Blazor, {
|
|
246
|
+
emitRenderEvent,
|
|
247
|
+
emitNavigateEvent,
|
|
248
|
+
emitPiralEvent,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
startBlazor(opts).then(resolve);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
document.body.appendChild(script);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function createBootLoader(scriptUrl: string, extraScriptUrls: Array<string>) {
|
|
259
|
+
const publicPath = computePath();
|
|
260
|
+
|
|
261
|
+
return (opts?: WebAssemblyStartOptions) => {
|
|
262
|
+
if (typeof window.$blazorLoader === 'undefined') {
|
|
263
|
+
window.dispatchEvent(new CustomEvent('loading-blazor-core'));
|
|
264
|
+
|
|
265
|
+
// we load all satellite scripts before we initialize blazor
|
|
266
|
+
window.$blazorLoader = Promise.all(extraScriptUrls.map(addScript)).then(() =>
|
|
267
|
+
initialize(scriptUrl, publicPath, opts),
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return window.$blazorLoader;
|
|
272
|
+
};
|
|
273
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function findClosestAncestor(element: Element | null, tagName: string) {
|
|
2
|
+
// tslint:disable-next-line:no-null-keyword
|
|
3
|
+
return !element ? null : element.tagName === tagName ? element : findClosestAncestor(element.parentElement, tagName);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function getAnchorTarget(event: MouseEvent) {
|
|
7
|
+
return findClosestAncestor(event.target as Element | null, 'A') as HTMLAnchorElement | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isWithinBaseUriSpace(href: string) {
|
|
11
|
+
const baseURI = document.baseURI;
|
|
12
|
+
const baseUriUntilLastSlash = baseURI.substr(0, baseURI.lastIndexOf('/') + 1);
|
|
13
|
+
return href.startsWith(baseUriUntilLastSlash);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function eventHasSpecialKey(event: MouseEvent) {
|
|
17
|
+
return event.ctrlKey || event.shiftKey || event.altKey || event.metaKey;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isInternalNavigation(event: MouseEvent) {
|
|
21
|
+
const anchorTarget = getAnchorTarget(event);
|
|
22
|
+
return (
|
|
23
|
+
event.type === 'click' &&
|
|
24
|
+
event.button === 0 &&
|
|
25
|
+
!eventHasSpecialKey(event) &&
|
|
26
|
+
anchorTarget?.hasAttribute('href') &&
|
|
27
|
+
isWithinBaseUriSpace(anchorTarget.href)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function performInternalNavigation(event: MouseEvent) {
|
|
32
|
+
const anchorTarget = getAnchorTarget(event);
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
const to = anchorTarget.getAttribute('href');
|
|
35
|
+
window.Blazor.emitNavigateEvent(anchorTarget, to);
|
|
36
|
+
}
|