@aprovan/patchwork 0.1.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/.eslintrc.json +22 -0
- package/.github/workflows/publish.yml +41 -0
- package/.prettierignore +17 -0
- package/LICENSE +373 -0
- package/README.md +15 -0
- package/apps/chat/.utcp_config.json +14 -0
- package/apps/chat/.working/widgets/27060b91-a2a5-4272-b243-6eb904bd4070/main.tsx +107 -0
- package/apps/chat/index.html +17 -0
- package/apps/chat/node_modules/.bin/autoprefixer +17 -0
- package/apps/chat/node_modules/.bin/browserslist +17 -0
- package/apps/chat/node_modules/.bin/conc +17 -0
- package/apps/chat/node_modules/.bin/concurrently +17 -0
- package/apps/chat/node_modules/.bin/copilot-proxy +17 -0
- package/apps/chat/node_modules/.bin/jiti +17 -0
- package/apps/chat/node_modules/.bin/tailwind +17 -0
- package/apps/chat/node_modules/.bin/tailwindcss +17 -0
- package/apps/chat/node_modules/.bin/tsc +17 -0
- package/apps/chat/node_modules/.bin/tsserver +17 -0
- package/apps/chat/node_modules/.bin/tsx +17 -0
- package/apps/chat/node_modules/.bin/vite +17 -0
- package/apps/chat/package.json +55 -0
- package/apps/chat/postcss.config.js +6 -0
- package/apps/chat/src/App.tsx +7 -0
- package/apps/chat/src/components/ui/avatar.tsx +48 -0
- package/apps/chat/src/components/ui/badge.tsx +36 -0
- package/apps/chat/src/components/ui/button.tsx +56 -0
- package/apps/chat/src/components/ui/card.tsx +86 -0
- package/apps/chat/src/components/ui/collapsible.tsx +9 -0
- package/apps/chat/src/components/ui/dialog.tsx +60 -0
- package/apps/chat/src/components/ui/input.tsx +25 -0
- package/apps/chat/src/components/ui/scroll-area.tsx +46 -0
- package/apps/chat/src/index.css +190 -0
- package/apps/chat/src/lib/utils.ts +6 -0
- package/apps/chat/src/main.tsx +10 -0
- package/apps/chat/src/pages/ChatPage.tsx +460 -0
- package/apps/chat/tailwind.config.js +71 -0
- package/apps/chat/tsconfig.json +25 -0
- package/apps/chat/vite.config.ts +26 -0
- package/package.json +35 -0
- package/packages/bobbin/node_modules/.bin/esbuild +14 -0
- package/packages/bobbin/node_modules/.bin/jiti +17 -0
- package/packages/bobbin/node_modules/.bin/tsc +17 -0
- package/packages/bobbin/node_modules/.bin/tsserver +17 -0
- package/packages/bobbin/node_modules/.bin/tsup +17 -0
- package/packages/bobbin/node_modules/.bin/tsup-node +17 -0
- package/packages/bobbin/node_modules/.bin/tsx +17 -0
- package/packages/bobbin/package.json +30 -0
- package/packages/bobbin/src/Bobbin.tsx +89 -0
- package/packages/bobbin/src/components/EditPanel/EditPanel.tsx +376 -0
- package/packages/bobbin/src/components/EditPanel/controls/ColorPicker.tsx +138 -0
- package/packages/bobbin/src/components/EditPanel/controls/QuickSelectDropdown.tsx +142 -0
- package/packages/bobbin/src/components/EditPanel/controls/SliderInput.tsx +94 -0
- package/packages/bobbin/src/components/EditPanel/controls/SpacingControl.tsx +285 -0
- package/packages/bobbin/src/components/EditPanel/controls/ToggleGroup.tsx +37 -0
- package/packages/bobbin/src/components/EditPanel/controls/TokenDropdown.tsx +33 -0
- package/packages/bobbin/src/components/EditPanel/sections/AnnotationSection.tsx +136 -0
- package/packages/bobbin/src/components/EditPanel/sections/BackgroundSection.tsx +79 -0
- package/packages/bobbin/src/components/EditPanel/sections/EffectsSection.tsx +85 -0
- package/packages/bobbin/src/components/EditPanel/sections/LayoutSection.tsx +224 -0
- package/packages/bobbin/src/components/EditPanel/sections/SectionWrapper.tsx +57 -0
- package/packages/bobbin/src/components/EditPanel/sections/SizeSection.tsx +166 -0
- package/packages/bobbin/src/components/EditPanel/sections/SpacingSection.tsx +69 -0
- package/packages/bobbin/src/components/EditPanel/sections/TypographySection.tsx +148 -0
- package/packages/bobbin/src/components/Inspector/Inspector.tsx +221 -0
- package/packages/bobbin/src/components/Overlay/ControlHandles.tsx +572 -0
- package/packages/bobbin/src/components/Overlay/MarginPaddingOverlay.tsx +229 -0
- package/packages/bobbin/src/components/Overlay/SelectionOverlay.tsx +73 -0
- package/packages/bobbin/src/components/Pill/Pill.tsx +155 -0
- package/packages/bobbin/src/components/ThemeToggle/ThemeToggle.tsx +72 -0
- package/packages/bobbin/src/core/changeSerializer.ts +139 -0
- package/packages/bobbin/src/core/useBobbin.ts +399 -0
- package/packages/bobbin/src/core/useChangeTracker.ts +186 -0
- package/packages/bobbin/src/core/useClipboard.ts +21 -0
- package/packages/bobbin/src/core/useElementSelection.ts +146 -0
- package/packages/bobbin/src/index.ts +46 -0
- package/packages/bobbin/src/tokens/borders.ts +19 -0
- package/packages/bobbin/src/tokens/colors.ts +150 -0
- package/packages/bobbin/src/tokens/index.ts +37 -0
- package/packages/bobbin/src/tokens/shadows.ts +10 -0
- package/packages/bobbin/src/tokens/spacing.ts +37 -0
- package/packages/bobbin/src/tokens/typography.ts +51 -0
- package/packages/bobbin/src/types.ts +157 -0
- package/packages/bobbin/src/utils/animation.ts +40 -0
- package/packages/bobbin/src/utils/dom.ts +36 -0
- package/packages/bobbin/src/utils/selectors.ts +76 -0
- package/packages/bobbin/tsconfig.json +10 -0
- package/packages/bobbin/tsup.config.ts +10 -0
- package/packages/compiler/node_modules/.bin/esbuild +17 -0
- package/packages/compiler/node_modules/.bin/jiti +17 -0
- package/packages/compiler/node_modules/.bin/tsc +17 -0
- package/packages/compiler/node_modules/.bin/tsserver +17 -0
- package/packages/compiler/node_modules/.bin/tsup +17 -0
- package/packages/compiler/node_modules/.bin/tsup-node +17 -0
- package/packages/compiler/node_modules/.bin/tsx +17 -0
- package/packages/compiler/package.json +38 -0
- package/packages/compiler/src/compiler.ts +258 -0
- package/packages/compiler/src/images/index.ts +13 -0
- package/packages/compiler/src/images/loader.ts +234 -0
- package/packages/compiler/src/images/registry.ts +112 -0
- package/packages/compiler/src/index.ts +141 -0
- package/packages/compiler/src/mount/bridge.ts +399 -0
- package/packages/compiler/src/mount/embedded.ts +306 -0
- package/packages/compiler/src/mount/iframe.ts +433 -0
- package/packages/compiler/src/mount/index.ts +18 -0
- package/packages/compiler/src/schemas.ts +169 -0
- package/packages/compiler/src/transforms/cdn.ts +411 -0
- package/packages/compiler/src/transforms/index.ts +4 -0
- package/packages/compiler/src/transforms/vfs.ts +138 -0
- package/packages/compiler/src/types.ts +233 -0
- package/packages/compiler/src/vfs/backends/indexeddb.ts +66 -0
- package/packages/compiler/src/vfs/backends/local-fs.ts +41 -0
- package/packages/compiler/src/vfs/backends/s3.ts +60 -0
- package/packages/compiler/src/vfs/index.ts +11 -0
- package/packages/compiler/src/vfs/project.ts +56 -0
- package/packages/compiler/src/vfs/store.ts +53 -0
- package/packages/compiler/src/vfs/types.ts +20 -0
- package/packages/compiler/tsconfig.json +8 -0
- package/packages/compiler/tsup.config.ts +14 -0
- package/packages/editor/node_modules/.bin/jiti +17 -0
- package/packages/editor/node_modules/.bin/tsc +17 -0
- package/packages/editor/node_modules/.bin/tsserver +17 -0
- package/packages/editor/node_modules/.bin/tsup +17 -0
- package/packages/editor/node_modules/.bin/tsup-node +17 -0
- package/packages/editor/node_modules/.bin/tsx +17 -0
- package/packages/editor/package.json +45 -0
- package/packages/editor/src/components/CodeBlockExtension.tsx +190 -0
- package/packages/editor/src/components/CodePreview.tsx +344 -0
- package/packages/editor/src/components/MarkdownEditor.tsx +270 -0
- package/packages/editor/src/components/ServicesInspector.tsx +118 -0
- package/packages/editor/src/components/edit/EditHistory.tsx +89 -0
- package/packages/editor/src/components/edit/EditModal.tsx +236 -0
- package/packages/editor/src/components/edit/FileTree.tsx +144 -0
- package/packages/editor/src/components/edit/api.ts +100 -0
- package/packages/editor/src/components/edit/index.ts +6 -0
- package/packages/editor/src/components/edit/types.ts +53 -0
- package/packages/editor/src/components/edit/useEditSession.ts +164 -0
- package/packages/editor/src/components/index.ts +5 -0
- package/packages/editor/src/index.ts +72 -0
- package/packages/editor/src/lib/code-extractor.ts +210 -0
- package/packages/editor/src/lib/diff.ts +308 -0
- package/packages/editor/src/lib/index.ts +4 -0
- package/packages/editor/src/lib/utils.ts +6 -0
- package/packages/editor/src/lib/vfs.ts +106 -0
- package/packages/editor/tsconfig.json +10 -0
- package/packages/editor/tsup.config.ts +10 -0
- package/packages/images/ink/node_modules/.bin/jiti +17 -0
- package/packages/images/ink/node_modules/.bin/tsc +17 -0
- package/packages/images/ink/node_modules/.bin/tsserver +17 -0
- package/packages/images/ink/node_modules/.bin/tsup +17 -0
- package/packages/images/ink/node_modules/.bin/tsup-node +17 -0
- package/packages/images/ink/node_modules/.bin/tsx +17 -0
- package/packages/images/ink/package.json +53 -0
- package/packages/images/ink/src/index.ts +48 -0
- package/packages/images/ink/src/runner.ts +331 -0
- package/packages/images/ink/src/setup.ts +123 -0
- package/packages/images/ink/tsconfig.json +10 -0
- package/packages/images/ink/tsup.config.ts +11 -0
- package/packages/images/shadcn/node_modules/.bin/jiti +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsc +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsserver +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsup +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsup-node +17 -0
- package/packages/images/shadcn/node_modules/.bin/tsx +17 -0
- package/packages/images/shadcn/package.json +82 -0
- package/packages/images/shadcn/src/html.ts +341 -0
- package/packages/images/shadcn/src/index.ts +37 -0
- package/packages/images/shadcn/src/setup.ts +287 -0
- package/packages/images/shadcn/tsconfig.json +9 -0
- package/packages/images/shadcn/tsup.config.ts +13 -0
- package/packages/images/vanilla/node_modules/.bin/jiti +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsc +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsserver +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsup +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsup-node +17 -0
- package/packages/images/vanilla/node_modules/.bin/tsx +17 -0
- package/packages/images/vanilla/package.json +35 -0
- package/packages/images/vanilla/src/index.ts +7 -0
- package/packages/images/vanilla/src/setup.ts +6 -0
- package/packages/images/vanilla/tsconfig.json +9 -0
- package/packages/images/vanilla/tsup.config.ts +10 -0
- package/packages/patchwork/node_modules/.bin/jiti +17 -0
- package/packages/patchwork/node_modules/.bin/tsc +17 -0
- package/packages/patchwork/node_modules/.bin/tsserver +17 -0
- package/packages/patchwork/node_modules/.bin/tsup +17 -0
- package/packages/patchwork/node_modules/.bin/tsup-node +17 -0
- package/packages/patchwork/node_modules/.bin/tsx +17 -0
- package/packages/patchwork/package.json +27 -0
- package/packages/patchwork/src/index.ts +15 -0
- package/packages/patchwork/src/services/index.ts +11 -0
- package/packages/patchwork/src/services/proxy.ts +213 -0
- package/packages/patchwork/src/services/types.ts +28 -0
- package/packages/patchwork/src/types.ts +116 -0
- package/packages/patchwork/tsconfig.json +8 -0
- package/packages/patchwork/tsup.config.ts +14 -0
- package/packages/stitchery/node_modules/.bin/jiti +17 -0
- package/packages/stitchery/node_modules/.bin/tsc +17 -0
- package/packages/stitchery/node_modules/.bin/tsserver +17 -0
- package/packages/stitchery/node_modules/.bin/tsup +17 -0
- package/packages/stitchery/node_modules/.bin/tsup-node +17 -0
- package/packages/stitchery/node_modules/.bin/tsx +17 -0
- package/packages/stitchery/package.json +40 -0
- package/packages/stitchery/src/cli.ts +116 -0
- package/packages/stitchery/src/index.ts +16 -0
- package/packages/stitchery/src/prompts.ts +326 -0
- package/packages/stitchery/src/server/index.ts +365 -0
- package/packages/stitchery/src/server/local-packages.ts +91 -0
- package/packages/stitchery/src/server/routes.ts +122 -0
- package/packages/stitchery/src/server/services.ts +382 -0
- package/packages/stitchery/src/server/vfs-routes.ts +142 -0
- package/packages/stitchery/src/types.ts +59 -0
- package/packages/stitchery/tsconfig.json +13 -0
- package/packages/stitchery/tsup.config.ts +15 -0
- package/packages/utcp/node_modules/.bin/jiti +17 -0
- package/packages/utcp/node_modules/.bin/tsc +17 -0
- package/packages/utcp/node_modules/.bin/tsserver +17 -0
- package/packages/utcp/node_modules/.bin/tsup +17 -0
- package/packages/utcp/node_modules/.bin/tsup-node +17 -0
- package/packages/utcp/node_modules/.bin/tsx +17 -0
- package/packages/utcp/package.json +38 -0
- package/packages/utcp/src/index.ts +153 -0
- package/packages/utcp/tsconfig.json +8 -0
- package/packages/utcp/tsup.config.ts +12 -0
- package/pnpm-workspace.yaml +3 -0
- package/tsconfig.json +18 -0
- package/turbo.json +23 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service bridge - handles communication between widgets and service proxy
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
BridgeMessage,
|
|
7
|
+
ServiceCallPayload,
|
|
8
|
+
ServiceResultPayload,
|
|
9
|
+
ServiceProxy,
|
|
10
|
+
} from '../types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate a unique message ID
|
|
14
|
+
*/
|
|
15
|
+
function generateMessageId(): string {
|
|
16
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a service proxy that calls the backend via HTTP
|
|
21
|
+
*/
|
|
22
|
+
export function createHttpServiceProxy(proxyUrl: string): ServiceProxy {
|
|
23
|
+
return {
|
|
24
|
+
async call(
|
|
25
|
+
namespace: string,
|
|
26
|
+
procedure: string,
|
|
27
|
+
args: unknown[],
|
|
28
|
+
): Promise<unknown> {
|
|
29
|
+
const url = `${proxyUrl}/${namespace}/${procedure}`;
|
|
30
|
+
const response = await fetch(url, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: { 'Content-Type': 'application/json' },
|
|
33
|
+
body: JSON.stringify({ args: args[0] ?? {} }),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Service call failed: ${response.status} ${response.statusText}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = await response.json();
|
|
43
|
+
return result;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates a proxy that enables fluent method chaining for dynamic field access.
|
|
50
|
+
*
|
|
51
|
+
* This allows arbitrary nested property access that resolves to a callable function,
|
|
52
|
+
* supporting patterns like `proxy.foo()`, `proxy.foo.bar()`, `proxy.bar.baz.qux()`.
|
|
53
|
+
*
|
|
54
|
+
* Used to create global namespace objects that proxy calls to a service backend.
|
|
55
|
+
*/
|
|
56
|
+
export function createFieldAccessProxy<T = unknown>(
|
|
57
|
+
namespace: string,
|
|
58
|
+
handler: (
|
|
59
|
+
namespace: string,
|
|
60
|
+
methodPath: string,
|
|
61
|
+
...args: T[]
|
|
62
|
+
) => Promise<unknown>,
|
|
63
|
+
): Record<string, (...args: T[]) => Promise<unknown>> {
|
|
64
|
+
function createNestedProxy(path: string): (...args: T[]) => Promise<unknown> {
|
|
65
|
+
const fn = (...args: T[]) => handler(namespace, path, ...args);
|
|
66
|
+
|
|
67
|
+
return new Proxy(fn, {
|
|
68
|
+
get(_, nestedName: string) {
|
|
69
|
+
if (typeof nestedName === 'symbol') return undefined;
|
|
70
|
+
const newPath = path ? `${path}.${nestedName}` : nestedName;
|
|
71
|
+
return createNestedProxy(newPath);
|
|
72
|
+
},
|
|
73
|
+
}) as (...args: T[]) => Promise<unknown>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return new Proxy(
|
|
77
|
+
{},
|
|
78
|
+
{
|
|
79
|
+
get(_, fieldName: string) {
|
|
80
|
+
if (typeof fieldName === 'symbol') return undefined;
|
|
81
|
+
return createNestedProxy(fieldName);
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create namespace globals that proxy calls to a service proxy
|
|
89
|
+
*
|
|
90
|
+
* Creates dynamic proxy objects for each namespace that support arbitrary
|
|
91
|
+
* nested method calls. This replaces the old static method registration.
|
|
92
|
+
*
|
|
93
|
+
* @param services - Array of service names (e.g., ['git', 'github'])
|
|
94
|
+
* @param proxy - The service proxy to forward calls to
|
|
95
|
+
* @returns Record of namespace names to proxy objects
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const namespaces = generateNamespaceGlobals(['git', 'github'], proxy);
|
|
100
|
+
* // namespaces.git.status() calls proxy.call('git', 'status', [])
|
|
101
|
+
* // namespaces.github.repos.list_for_user({ username: 'x' })
|
|
102
|
+
* // calls proxy.call('github', 'repos.list_for_user', [{ username: 'x' }])
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export function generateNamespaceGlobals(
|
|
106
|
+
services: string[],
|
|
107
|
+
proxy: ServiceProxy,
|
|
108
|
+
): Record<string, unknown> {
|
|
109
|
+
const namespaces: Record<string, unknown> = {};
|
|
110
|
+
const uniqueNamespaces = extractNamespaces(services);
|
|
111
|
+
|
|
112
|
+
for (const namespace of uniqueNamespaces) {
|
|
113
|
+
namespaces[namespace] = createFieldAccessProxy(
|
|
114
|
+
namespace,
|
|
115
|
+
(ns, method, ...args) => proxy.call(ns, method, args),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return namespaces;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Inject namespace globals into a window object
|
|
124
|
+
*/
|
|
125
|
+
export function injectNamespaceGlobals(
|
|
126
|
+
target: Window | typeof globalThis,
|
|
127
|
+
namespaces: Record<string, unknown>,
|
|
128
|
+
): void {
|
|
129
|
+
for (const [name, value] of Object.entries(namespaces)) {
|
|
130
|
+
(target as Record<string, unknown>)[name] = value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Remove namespace globals from a window object
|
|
136
|
+
*/
|
|
137
|
+
export function removeNamespaceGlobals(
|
|
138
|
+
target: Window | typeof globalThis,
|
|
139
|
+
namespaceNames: string[],
|
|
140
|
+
): void {
|
|
141
|
+
for (const name of namespaceNames) {
|
|
142
|
+
delete (target as Record<string, unknown>)[name];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Extract unique namespace names from services array
|
|
148
|
+
*/
|
|
149
|
+
export function extractNamespaces(services: string[]): string[] {
|
|
150
|
+
const namespaces = new Set<string>();
|
|
151
|
+
for (const service of services) {
|
|
152
|
+
const parts = service.split('.');
|
|
153
|
+
if (parts[0]) {
|
|
154
|
+
namespaces.add(parts[0]);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return Array.from(namespaces);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Parent-side bridge for iframe communication
|
|
162
|
+
*
|
|
163
|
+
* Listens for postMessage events from iframes and proxies service calls.
|
|
164
|
+
*/
|
|
165
|
+
export class ParentBridge {
|
|
166
|
+
private proxy: ServiceProxy;
|
|
167
|
+
private pendingCalls = new Map<
|
|
168
|
+
string,
|
|
169
|
+
{ resolve: (value: unknown) => void; reject: (error: Error) => void }
|
|
170
|
+
>();
|
|
171
|
+
private iframes = new Set<HTMLIFrameElement>();
|
|
172
|
+
private messageHandler: (event: MessageEvent) => void;
|
|
173
|
+
|
|
174
|
+
constructor(proxy: ServiceProxy) {
|
|
175
|
+
this.proxy = proxy;
|
|
176
|
+
this.messageHandler = this.handleMessage.bind(this);
|
|
177
|
+
if (typeof window !== 'undefined') {
|
|
178
|
+
window.addEventListener('message', this.messageHandler);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Register an iframe to receive messages from
|
|
184
|
+
*/
|
|
185
|
+
registerIframe(iframe: HTMLIFrameElement): void {
|
|
186
|
+
this.iframes.add(iframe);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Unregister an iframe
|
|
191
|
+
*/
|
|
192
|
+
unregisterIframe(iframe: HTMLIFrameElement): void {
|
|
193
|
+
this.iframes.delete(iframe);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Handle incoming messages from iframes
|
|
198
|
+
*/
|
|
199
|
+
private async handleMessage(event: MessageEvent): Promise<void> {
|
|
200
|
+
// Verify source is a registered iframe
|
|
201
|
+
const sourceIframe = Array.from(this.iframes).find(
|
|
202
|
+
(iframe) => iframe.contentWindow === event.source,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
if (!sourceIframe) {
|
|
206
|
+
return; // Ignore messages from unknown sources
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const message = event.data as BridgeMessage;
|
|
210
|
+
if (!message || typeof message !== 'object') return;
|
|
211
|
+
|
|
212
|
+
if (message.type === 'service-call') {
|
|
213
|
+
const payload = message.payload as ServiceCallPayload;
|
|
214
|
+
try {
|
|
215
|
+
const result = await this.proxy.call(
|
|
216
|
+
payload.namespace,
|
|
217
|
+
payload.procedure,
|
|
218
|
+
payload.args,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const response: BridgeMessage = {
|
|
222
|
+
type: 'service-result',
|
|
223
|
+
id: message.id,
|
|
224
|
+
payload: { result } as ServiceResultPayload,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
sourceIframe.contentWindow?.postMessage(response, '*');
|
|
228
|
+
} catch (error) {
|
|
229
|
+
const response: BridgeMessage = {
|
|
230
|
+
type: 'service-result',
|
|
231
|
+
id: message.id,
|
|
232
|
+
payload: {
|
|
233
|
+
error: error instanceof Error ? error.message : String(error),
|
|
234
|
+
} as ServiceResultPayload,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
sourceIframe.contentWindow?.postMessage(response, '*');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Dispose the bridge
|
|
244
|
+
*/
|
|
245
|
+
dispose(): void {
|
|
246
|
+
if (typeof window !== 'undefined') {
|
|
247
|
+
window.removeEventListener('message', this.messageHandler);
|
|
248
|
+
}
|
|
249
|
+
this.iframes.clear();
|
|
250
|
+
this.pendingCalls.clear();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Child-side bridge for iframe communication
|
|
256
|
+
*
|
|
257
|
+
* Creates a service proxy that sends postMessage to parent.
|
|
258
|
+
*/
|
|
259
|
+
export function createIframeServiceProxy(): ServiceProxy {
|
|
260
|
+
const pendingCalls = new Map<
|
|
261
|
+
string,
|
|
262
|
+
{ resolve: (value: unknown) => void; reject: (error: Error) => void }
|
|
263
|
+
>();
|
|
264
|
+
|
|
265
|
+
// Listen for results from parent
|
|
266
|
+
if (typeof window !== 'undefined') {
|
|
267
|
+
window.addEventListener('message', (event: MessageEvent) => {
|
|
268
|
+
const message = event.data as BridgeMessage;
|
|
269
|
+
if (!message || typeof message !== 'object') return;
|
|
270
|
+
|
|
271
|
+
if (message.type === 'service-result') {
|
|
272
|
+
const pending = pendingCalls.get(message.id);
|
|
273
|
+
if (pending) {
|
|
274
|
+
pendingCalls.delete(message.id);
|
|
275
|
+
const payload = message.payload as ServiceResultPayload;
|
|
276
|
+
if (payload.error) {
|
|
277
|
+
pending.reject(new Error(payload.error));
|
|
278
|
+
} else {
|
|
279
|
+
pending.resolve(payload.result);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
call(
|
|
288
|
+
namespace: string,
|
|
289
|
+
procedure: string,
|
|
290
|
+
args: unknown[],
|
|
291
|
+
): Promise<unknown> {
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
const id = generateMessageId();
|
|
294
|
+
pendingCalls.set(id, { resolve, reject });
|
|
295
|
+
|
|
296
|
+
const message: BridgeMessage = {
|
|
297
|
+
type: 'service-call',
|
|
298
|
+
id,
|
|
299
|
+
payload: { namespace, procedure, args } as ServiceCallPayload,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
window.parent.postMessage(message, '*');
|
|
303
|
+
|
|
304
|
+
// Timeout after 30 seconds
|
|
305
|
+
setTimeout(() => {
|
|
306
|
+
if (pendingCalls.has(id)) {
|
|
307
|
+
pendingCalls.delete(id);
|
|
308
|
+
reject(
|
|
309
|
+
new Error(`Service call timeout: ${namespace}.${procedure}`),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}, 30000);
|
|
313
|
+
});
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Generate the bridge script to inject into iframes
|
|
320
|
+
*
|
|
321
|
+
* Creates a self-contained script that sets up:
|
|
322
|
+
* 1. Message handling for service results from parent
|
|
323
|
+
* 2. Dynamic proxy objects for each namespace that support arbitrary nested calls
|
|
324
|
+
*/
|
|
325
|
+
export function generateIframeBridgeScript(services: string[]): string {
|
|
326
|
+
const uniqueNamespaces = extractNamespaces(services);
|
|
327
|
+
const namespaceAssignments = uniqueNamespaces
|
|
328
|
+
.map((ns) => `window.${ns} = createNamespaceProxy('${ns}');`)
|
|
329
|
+
.join('\n ');
|
|
330
|
+
|
|
331
|
+
return `
|
|
332
|
+
(function() {
|
|
333
|
+
const pendingCalls = new Map();
|
|
334
|
+
|
|
335
|
+
window.addEventListener('message', function(event) {
|
|
336
|
+
const message = event.data;
|
|
337
|
+
if (!message || typeof message !== 'object') return;
|
|
338
|
+
|
|
339
|
+
if (message.type === 'service-result') {
|
|
340
|
+
const pending = pendingCalls.get(message.id);
|
|
341
|
+
if (pending) {
|
|
342
|
+
pendingCalls.delete(message.id);
|
|
343
|
+
if (message.payload.error) {
|
|
344
|
+
pending.reject(new Error(message.payload.error));
|
|
345
|
+
} else {
|
|
346
|
+
pending.resolve(message.payload.result);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
function proxyCall(namespace, procedure, args) {
|
|
353
|
+
return new Promise(function(resolve, reject) {
|
|
354
|
+
const id = Date.now() + '-' + Math.random().toString(36).slice(2, 11);
|
|
355
|
+
pendingCalls.set(id, { resolve: resolve, reject: reject });
|
|
356
|
+
|
|
357
|
+
window.parent.postMessage({
|
|
358
|
+
type: 'service-call',
|
|
359
|
+
id: id,
|
|
360
|
+
payload: { namespace: namespace, procedure: procedure, args: args }
|
|
361
|
+
}, '*');
|
|
362
|
+
|
|
363
|
+
setTimeout(function() {
|
|
364
|
+
if (pendingCalls.has(id)) {
|
|
365
|
+
pendingCalls.delete(id);
|
|
366
|
+
reject(new Error('Service call timeout: ' + namespace + '.' + procedure));
|
|
367
|
+
}
|
|
368
|
+
}, 30000);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Create a dynamic proxy for a namespace that supports arbitrary nested method calls
|
|
373
|
+
function createNamespaceProxy(namespace) {
|
|
374
|
+
function createNestedProxy(path) {
|
|
375
|
+
var fn = function() {
|
|
376
|
+
return proxyCall(namespace, path, Array.prototype.slice.call(arguments));
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
return new Proxy(fn, {
|
|
380
|
+
get: function(_, nestedName) {
|
|
381
|
+
if (typeof nestedName === 'symbol') return undefined;
|
|
382
|
+
var newPath = path ? path + '.' + nestedName : nestedName;
|
|
383
|
+
return createNestedProxy(newPath);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return new Proxy({}, {
|
|
389
|
+
get: function(_, fieldName) {
|
|
390
|
+
if (typeof fieldName === 'symbol') return undefined;
|
|
391
|
+
return createNestedProxy(fieldName);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
${namespaceAssignments}
|
|
397
|
+
})();
|
|
398
|
+
`;
|
|
399
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded mount mode - mounts widgets directly in the DOM
|
|
3
|
+
*
|
|
4
|
+
* For trusted widgets that need full window access.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
CompiledWidget,
|
|
9
|
+
LoadedImage,
|
|
10
|
+
MountedWidget,
|
|
11
|
+
MountOptions,
|
|
12
|
+
ServiceProxy,
|
|
13
|
+
} from '../types.js';
|
|
14
|
+
import {
|
|
15
|
+
generateNamespaceGlobals,
|
|
16
|
+
injectNamespaceGlobals,
|
|
17
|
+
removeNamespaceGlobals,
|
|
18
|
+
extractNamespaces,
|
|
19
|
+
} from './bridge.js';
|
|
20
|
+
|
|
21
|
+
let mountCounter = 0;
|
|
22
|
+
let importMapInjected = false;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Inject an import map for bare module specifiers.
|
|
26
|
+
* Maps package names to their CDN URLs so browsers can resolve them.
|
|
27
|
+
* Must be called before any ES module imports happen.
|
|
28
|
+
*/
|
|
29
|
+
function injectImportMap(
|
|
30
|
+
globals: Record<string, string>,
|
|
31
|
+
preloadUrls: string[],
|
|
32
|
+
deps?: Record<string, string>,
|
|
33
|
+
): void {
|
|
34
|
+
// Only inject once per page (browser limitation)
|
|
35
|
+
if (importMapInjected) return;
|
|
36
|
+
|
|
37
|
+
// Check if there's already an import map
|
|
38
|
+
const existingMap = document.querySelector('script[type="importmap"]');
|
|
39
|
+
if (existingMap) {
|
|
40
|
+
// Cannot modify existing import maps in standard browsers
|
|
41
|
+
importMapInjected = true;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Build import map from globals + preload URLs
|
|
46
|
+
// Convention: globals keys are package names, preload URLs are in matching order
|
|
47
|
+
const imports: Record<string, string> = {};
|
|
48
|
+
const packageNames = Object.keys(globals);
|
|
49
|
+
|
|
50
|
+
packageNames.forEach((pkgName, index) => {
|
|
51
|
+
// Use the preload URL if available, otherwise construct CDN URL
|
|
52
|
+
if (preloadUrls[index]) {
|
|
53
|
+
imports[pkgName] = preloadUrls[index];
|
|
54
|
+
} else if (deps?.[pkgName]) {
|
|
55
|
+
imports[pkgName] = `https://esm.sh/${pkgName}@${deps[pkgName]}`;
|
|
56
|
+
} else {
|
|
57
|
+
imports[pkgName] = `https://esm.sh/${pkgName}`;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Also add common subpaths (e.g., react-dom/client)
|
|
62
|
+
if (imports['react-dom']) {
|
|
63
|
+
imports['react-dom/client'] = imports['react-dom'];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Inject new import map
|
|
67
|
+
const script = document.createElement('script');
|
|
68
|
+
script.type = 'importmap';
|
|
69
|
+
script.textContent = JSON.stringify({ imports }, null, 2);
|
|
70
|
+
document.head.insertBefore(script, document.head.firstChild);
|
|
71
|
+
|
|
72
|
+
importMapInjected = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generate a unique mount ID
|
|
77
|
+
*/
|
|
78
|
+
function generateMountId(): string {
|
|
79
|
+
return `pw-mount-${Date.now()}-${++mountCounter}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type CreateElementFn = (...args: unknown[]) => unknown;
|
|
83
|
+
type CreateRootFn = (el: HTMLElement) => {
|
|
84
|
+
render: (el: unknown) => void;
|
|
85
|
+
unmount?: () => void;
|
|
86
|
+
};
|
|
87
|
+
type RenderFn = (el: unknown, container: HTMLElement) => void;
|
|
88
|
+
|
|
89
|
+
type Renderer =
|
|
90
|
+
| { kind: 'root'; createRoot: CreateRootFn }
|
|
91
|
+
| { kind: 'render'; render: RenderFn };
|
|
92
|
+
|
|
93
|
+
function pickCreateElement(
|
|
94
|
+
globals: Array<Record<string, unknown>>,
|
|
95
|
+
): CreateElementFn | null {
|
|
96
|
+
for (const obj of globals) {
|
|
97
|
+
const ce = obj?.createElement;
|
|
98
|
+
if (typeof ce === 'function') return ce as CreateElementFn;
|
|
99
|
+
const def = obj?.default as Record<string, unknown> | undefined;
|
|
100
|
+
if (def && typeof def.createElement === 'function') {
|
|
101
|
+
return def.createElement as CreateElementFn;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function pickRenderer(
|
|
108
|
+
globals: Array<Record<string, unknown>>,
|
|
109
|
+
): Renderer | null {
|
|
110
|
+
for (const obj of globals) {
|
|
111
|
+
if (obj && typeof obj.createRoot === 'function') {
|
|
112
|
+
return { kind: 'root', createRoot: obj.createRoot as CreateRootFn };
|
|
113
|
+
}
|
|
114
|
+
if (obj && typeof obj.render === 'function') {
|
|
115
|
+
return { kind: 'render', render: obj.render as RenderFn };
|
|
116
|
+
}
|
|
117
|
+
const def = obj?.default as Record<string, unknown> | undefined;
|
|
118
|
+
if (def && typeof def.createRoot === 'function') {
|
|
119
|
+
return { kind: 'root', createRoot: def.createRoot as CreateRootFn };
|
|
120
|
+
}
|
|
121
|
+
if (def && typeof def.render === 'function') {
|
|
122
|
+
return { kind: 'render', render: def.render as RenderFn };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Mount a widget in embedded mode (direct DOM injection)
|
|
130
|
+
*/
|
|
131
|
+
export async function mountEmbedded(
|
|
132
|
+
widget: CompiledWidget,
|
|
133
|
+
options: MountOptions,
|
|
134
|
+
image: LoadedImage | null,
|
|
135
|
+
proxy: ServiceProxy,
|
|
136
|
+
): Promise<MountedWidget> {
|
|
137
|
+
const { target, inputs = {} } = options;
|
|
138
|
+
const mountId = generateMountId();
|
|
139
|
+
|
|
140
|
+
// Create container
|
|
141
|
+
const container = document.createElement('div');
|
|
142
|
+
container.id = mountId;
|
|
143
|
+
container.className = 'patchwork-widget patchwork-embedded';
|
|
144
|
+
target.appendChild(container);
|
|
145
|
+
|
|
146
|
+
// Run image setup if available
|
|
147
|
+
if (image?.setup) {
|
|
148
|
+
await image.setup(container);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Inject CSS if available
|
|
152
|
+
if (image?.css) {
|
|
153
|
+
const style = document.createElement('style');
|
|
154
|
+
style.id = `${mountId}-style`;
|
|
155
|
+
style.textContent = image.css;
|
|
156
|
+
document.head.appendChild(style);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Generate and inject service namespace globals
|
|
160
|
+
const services = widget.manifest.services || [];
|
|
161
|
+
const namespaceNames = extractNamespaces(services);
|
|
162
|
+
const namespaces = generateNamespaceGlobals(services, proxy);
|
|
163
|
+
injectNamespaceGlobals(window, namespaces);
|
|
164
|
+
|
|
165
|
+
// Get framework config from image
|
|
166
|
+
const frameworkConfig = image?.config?.framework || {};
|
|
167
|
+
const preloadUrls = frameworkConfig.preload || [];
|
|
168
|
+
const globalMapping = frameworkConfig.globals || {};
|
|
169
|
+
const deps = frameworkConfig.deps || {};
|
|
170
|
+
|
|
171
|
+
// Inject import map for bare module specifiers (must happen before ES module imports)
|
|
172
|
+
// This allows the browser to resolve imports like 'react' to CDN URLs
|
|
173
|
+
injectImportMap(globalMapping, preloadUrls, deps);
|
|
174
|
+
|
|
175
|
+
// Pre-load framework modules from image config
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
177
|
+
const preloadedModules: any[] = await Promise.all(
|
|
178
|
+
preloadUrls.map(
|
|
179
|
+
(url: string) => import(/* webpackIgnore: true */ /* @vite-ignore */ url),
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Set framework globals on window based on image config
|
|
184
|
+
const win = window as unknown as Record<string, unknown>;
|
|
185
|
+
const globalNames = Object.values(globalMapping) as string[];
|
|
186
|
+
|
|
187
|
+
// Map preloaded modules to their global names
|
|
188
|
+
// Convention: preload order matches globals order (react -> React, react-dom -> ReactDOM)
|
|
189
|
+
preloadedModules.forEach((mod, index) => {
|
|
190
|
+
if (globalNames[index]) {
|
|
191
|
+
const name = globalNames[index];
|
|
192
|
+
win[name] = mod;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Create a blob with the widget code
|
|
197
|
+
const blob = new Blob([widget.code], { type: 'application/javascript' });
|
|
198
|
+
const scriptUrl = URL.createObjectURL(blob);
|
|
199
|
+
|
|
200
|
+
// Import the module
|
|
201
|
+
let moduleCleanup: (() => void) | undefined;
|
|
202
|
+
|
|
203
|
+
const globalObjects = globalNames
|
|
204
|
+
.map((n) => win[n] as unknown)
|
|
205
|
+
.filter(Boolean) as Array<Record<string, unknown>>;
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const module = await import(/* webpackIgnore: true */ scriptUrl);
|
|
209
|
+
|
|
210
|
+
// Image-provided mount handler takes priority
|
|
211
|
+
if (image?.mount) {
|
|
212
|
+
const result = await image.mount(module, container, inputs);
|
|
213
|
+
if (typeof result === 'function') {
|
|
214
|
+
moduleCleanup = result;
|
|
215
|
+
}
|
|
216
|
+
} else if (typeof module.mount === 'function') {
|
|
217
|
+
// Widget exports its own mount function
|
|
218
|
+
const result = await module.mount(container, inputs);
|
|
219
|
+
if (typeof result === 'function') {
|
|
220
|
+
moduleCleanup = result;
|
|
221
|
+
}
|
|
222
|
+
} else if (typeof module.render === 'function') {
|
|
223
|
+
// Custom render function
|
|
224
|
+
const result = await module.render(container, inputs);
|
|
225
|
+
if (typeof result === 'function') {
|
|
226
|
+
moduleCleanup = result;
|
|
227
|
+
}
|
|
228
|
+
} else if (typeof module.default === 'function') {
|
|
229
|
+
// Default export component - render using framework
|
|
230
|
+
const Component = module.default;
|
|
231
|
+
|
|
232
|
+
const createElement = pickCreateElement(globalObjects);
|
|
233
|
+
const renderer = pickRenderer(globalObjects);
|
|
234
|
+
|
|
235
|
+
if (createElement && renderer?.kind === 'root') {
|
|
236
|
+
const root = renderer.createRoot(container);
|
|
237
|
+
root.render(createElement(Component, inputs));
|
|
238
|
+
if (typeof root.unmount === 'function') {
|
|
239
|
+
moduleCleanup = () => root.unmount!();
|
|
240
|
+
}
|
|
241
|
+
} else if (createElement && renderer?.kind === 'render') {
|
|
242
|
+
renderer.render(createElement(Component, inputs), container);
|
|
243
|
+
} else {
|
|
244
|
+
// No framework renderer - try calling as plain function
|
|
245
|
+
const result = Component(inputs);
|
|
246
|
+
if (result instanceof HTMLElement) {
|
|
247
|
+
container.appendChild(result);
|
|
248
|
+
} else if (typeof result === 'string') {
|
|
249
|
+
container.innerHTML = result;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} finally {
|
|
254
|
+
URL.revokeObjectURL(scriptUrl);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Create unmount function
|
|
258
|
+
const unmount = () => {
|
|
259
|
+
// Call module cleanup if available
|
|
260
|
+
if (moduleCleanup) {
|
|
261
|
+
moduleCleanup();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Remove namespace globals
|
|
265
|
+
removeNamespaceGlobals(window, namespaceNames);
|
|
266
|
+
|
|
267
|
+
// Remove style
|
|
268
|
+
const style = document.getElementById(`${mountId}-style`);
|
|
269
|
+
if (style) {
|
|
270
|
+
style.remove();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Remove container
|
|
274
|
+
container.remove();
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
id: mountId,
|
|
279
|
+
widget,
|
|
280
|
+
mode: 'embedded',
|
|
281
|
+
target,
|
|
282
|
+
inputs,
|
|
283
|
+
unmount,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Hot reload an embedded widget
|
|
289
|
+
*/
|
|
290
|
+
export async function reloadEmbedded(
|
|
291
|
+
mounted: MountedWidget,
|
|
292
|
+
widget: CompiledWidget,
|
|
293
|
+
image: LoadedImage | null,
|
|
294
|
+
proxy: ServiceProxy,
|
|
295
|
+
): Promise<MountedWidget> {
|
|
296
|
+
// Unmount existing
|
|
297
|
+
mounted.unmount();
|
|
298
|
+
|
|
299
|
+
// Remount with new widget
|
|
300
|
+
return mountEmbedded(
|
|
301
|
+
widget,
|
|
302
|
+
{ target: mounted.target, mode: 'embedded', inputs: mounted.inputs },
|
|
303
|
+
image,
|
|
304
|
+
proxy,
|
|
305
|
+
);
|
|
306
|
+
}
|