@layoutdesign/context 0.1.7
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 +21 -0
- package/README.md +424 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +57 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/src/cli/import-zip.d.ts +2 -0
- package/dist/src/cli/import-zip.d.ts.map +1 -0
- package/dist/src/cli/import-zip.js +156 -0
- package/dist/src/cli/import-zip.js.map +1 -0
- package/dist/src/cli/init.d.ts +4 -0
- package/dist/src/cli/init.d.ts.map +1 -0
- package/dist/src/cli/init.js +104 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/install.d.ts +5 -0
- package/dist/src/cli/install.d.ts.map +1 -0
- package/dist/src/cli/install.js +192 -0
- package/dist/src/cli/install.js.map +1 -0
- package/dist/src/cli/list.d.ts +2 -0
- package/dist/src/cli/list.d.ts.map +1 -0
- package/dist/src/cli/list.js +36 -0
- package/dist/src/cli/list.js.map +1 -0
- package/dist/src/cli/serve.d.ts +2 -0
- package/dist/src/cli/serve.d.ts.map +1 -0
- package/dist/src/cli/serve.js +9 -0
- package/dist/src/cli/serve.js.map +1 -0
- package/dist/src/cli/use.d.ts +2 -0
- package/dist/src/cli/use.d.ts.map +1 -0
- package/dist/src/cli/use.js +54 -0
- package/dist/src/cli/use.js.map +1 -0
- package/dist/src/compliance/checker.d.ts +23 -0
- package/dist/src/compliance/checker.d.ts.map +1 -0
- package/dist/src/compliance/checker.js +31 -0
- package/dist/src/compliance/checker.js.map +1 -0
- package/dist/src/compliance/rules.d.ts +11 -0
- package/dist/src/compliance/rules.d.ts.map +1 -0
- package/dist/src/compliance/rules.js +147 -0
- package/dist/src/compliance/rules.js.map +1 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/kit/loader.d.ts +16 -0
- package/dist/src/kit/loader.d.ts.map +1 -0
- package/dist/src/kit/loader.js +98 -0
- package/dist/src/kit/loader.js.map +1 -0
- package/dist/src/kit/parser.d.ts +21 -0
- package/dist/src/kit/parser.d.ts.map +1 -0
- package/dist/src/kit/parser.js +98 -0
- package/dist/src/kit/parser.js.map +1 -0
- package/dist/src/kit/registry.d.ts +4 -0
- package/dist/src/kit/registry.d.ts.map +1 -0
- package/dist/src/kit/registry.js +91 -0
- package/dist/src/kit/registry.js.map +1 -0
- package/dist/src/kit/types.d.ts +51 -0
- package/dist/src/kit/types.d.ts.map +1 -0
- package/dist/src/kit/types.js +11 -0
- package/dist/src/kit/types.js.map +1 -0
- package/dist/src/mcp/server.d.ts +6 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +56 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools/check-compliance.d.ts +16 -0
- package/dist/src/mcp/tools/check-compliance.d.ts.map +1 -0
- package/dist/src/mcp/tools/check-compliance.js +44 -0
- package/dist/src/mcp/tools/check-compliance.js.map +1 -0
- package/dist/src/mcp/tools/design-in-figma.d.ts +24 -0
- package/dist/src/mcp/tools/design-in-figma.d.ts.map +1 -0
- package/dist/src/mcp/tools/design-in-figma.js +202 -0
- package/dist/src/mcp/tools/design-in-figma.js.map +1 -0
- package/dist/src/mcp/tools/get-component.d.ts +16 -0
- package/dist/src/mcp/tools/get-component.d.ts.map +1 -0
- package/dist/src/mcp/tools/get-component.js +52 -0
- package/dist/src/mcp/tools/get-component.js.map +1 -0
- package/dist/src/mcp/tools/get-design-system.d.ts +16 -0
- package/dist/src/mcp/tools/get-design-system.d.ts.map +1 -0
- package/dist/src/mcp/tools/get-design-system.js +51 -0
- package/dist/src/mcp/tools/get-design-system.js.map +1 -0
- package/dist/src/mcp/tools/get-screenshots.d.ts +23 -0
- package/dist/src/mcp/tools/get-screenshots.d.ts.map +1 -0
- package/dist/src/mcp/tools/get-screenshots.js +78 -0
- package/dist/src/mcp/tools/get-screenshots.js.map +1 -0
- package/dist/src/mcp/tools/get-tokens.d.ts +20 -0
- package/dist/src/mcp/tools/get-tokens.d.ts.map +1 -0
- package/dist/src/mcp/tools/get-tokens.js +50 -0
- package/dist/src/mcp/tools/get-tokens.js.map +1 -0
- package/dist/src/mcp/tools/list-components.d.ts +11 -0
- package/dist/src/mcp/tools/list-components.d.ts.map +1 -0
- package/dist/src/mcp/tools/list-components.js +38 -0
- package/dist/src/mcp/tools/list-components.js.map +1 -0
- package/dist/src/mcp/tools/preview.d.ts +21 -0
- package/dist/src/mcp/tools/preview.d.ts.map +1 -0
- package/dist/src/mcp/tools/preview.js +63 -0
- package/dist/src/mcp/tools/preview.js.map +1 -0
- package/dist/src/mcp/tools/push-to-figma.d.ts +24 -0
- package/dist/src/mcp/tools/push-to-figma.d.ts.map +1 -0
- package/dist/src/mcp/tools/push-to-figma.js +101 -0
- package/dist/src/mcp/tools/push-to-figma.js.map +1 -0
- package/dist/src/mcp/tools/update-tokens.d.ts +21 -0
- package/dist/src/mcp/tools/update-tokens.d.ts.map +1 -0
- package/dist/src/mcp/tools/update-tokens.js +187 -0
- package/dist/src/mcp/tools/update-tokens.js.map +1 -0
- package/dist/src/mcp/tools/url-to-figma.d.ts +29 -0
- package/dist/src/mcp/tools/url-to-figma.d.ts.map +1 -0
- package/dist/src/mcp/tools/url-to-figma.js +103 -0
- package/dist/src/mcp/tools/url-to-figma.js.map +1 -0
- package/dist/src/preview/server.d.ts +15 -0
- package/dist/src/preview/server.d.ts.map +1 -0
- package/dist/src/preview/server.js +146 -0
- package/dist/src/preview/server.js.map +1 -0
- package/dist/src/preview/static/index.html +493 -0
- package/dist/src/preview/transpile.d.ts +10 -0
- package/dist/src/preview/transpile.d.ts.map +1 -0
- package/dist/src/preview/transpile.js +40 -0
- package/dist/src/preview/transpile.js.map +1 -0
- package/dist/src/preview/ws.d.ts +17 -0
- package/dist/src/preview/ws.d.ts.map +1 -0
- package/dist/src/preview/ws.js +66 -0
- package/dist/src/preview/ws.js.map +1 -0
- package/kits/linear-lite/DESIGN.md +421 -0
- package/kits/linear-lite/kit.json +12 -0
- package/kits/linear-lite/tokens.css +46 -0
- package/kits/linear-lite/tokens.json +47 -0
- package/kits/notion-lite/DESIGN.md +528 -0
- package/kits/notion-lite/kit.json +12 -0
- package/kits/notion-lite/tokens.css +50 -0
- package/kits/notion-lite/tokens.json +51 -0
- package/kits/stripe-lite/DESIGN.md +539 -0
- package/kits/stripe-lite/kit.json +12 -0
- package/kits/stripe-lite/tokens.css +57 -0
- package/kits/stripe-lite/tokens.json +58 -0
- package/package.json +63 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Layout Preview</title>
|
|
7
|
+
<script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script>
|
|
8
|
+
<style>
|
|
9
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
10
|
+
|
|
11
|
+
:root {
|
|
12
|
+
--bg-app: #0C0C0E;
|
|
13
|
+
--bg-panel: #111115;
|
|
14
|
+
--bg-surface: #17171C;
|
|
15
|
+
--bg-elevated: #1E1E24;
|
|
16
|
+
--bg-hover: #24242B;
|
|
17
|
+
--border: rgba(255,255,255,0.07);
|
|
18
|
+
--border-strong: rgba(255,255,255,0.14);
|
|
19
|
+
--accent: #6366F1;
|
|
20
|
+
--accent-hover: #7577F3;
|
|
21
|
+
--text-primary: #E8E8F0;
|
|
22
|
+
--text-secondary: rgba(232,232,240,0.55);
|
|
23
|
+
--text-muted: rgba(232,232,240,0.35);
|
|
24
|
+
--ease: cubic-bezier(0.0, 0.0, 0.2, 1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
body {
|
|
28
|
+
background: var(--bg-app);
|
|
29
|
+
color: var(--text-primary);
|
|
30
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
31
|
+
height: 100vh;
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Toolbar */
|
|
38
|
+
.toolbar {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: 8px;
|
|
42
|
+
padding: 8px 16px;
|
|
43
|
+
background: var(--bg-panel);
|
|
44
|
+
border-bottom: 1px solid var(--border);
|
|
45
|
+
flex-shrink: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.toolbar-group {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: 2px;
|
|
52
|
+
background: var(--bg-surface);
|
|
53
|
+
border-radius: 6px;
|
|
54
|
+
padding: 2px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.toolbar-btn {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
gap: 6px;
|
|
62
|
+
padding: 6px 12px;
|
|
63
|
+
border: none;
|
|
64
|
+
border-radius: 4px;
|
|
65
|
+
background: transparent;
|
|
66
|
+
color: var(--text-secondary);
|
|
67
|
+
font-size: 12px;
|
|
68
|
+
font-weight: 500;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
transition: all 150ms var(--ease);
|
|
71
|
+
white-space: nowrap;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.toolbar-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
75
|
+
.toolbar-btn.active { background: var(--accent); color: white; }
|
|
76
|
+
|
|
77
|
+
.toolbar-spacer { flex: 1; }
|
|
78
|
+
|
|
79
|
+
.status {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: 6px;
|
|
83
|
+
font-size: 11px;
|
|
84
|
+
color: var(--text-muted);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.status-dot {
|
|
88
|
+
width: 6px;
|
|
89
|
+
height: 6px;
|
|
90
|
+
border-radius: 50%;
|
|
91
|
+
background: #ef4444;
|
|
92
|
+
transition: background 300ms var(--ease);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.status-dot.connected { background: #22c55e; }
|
|
96
|
+
|
|
97
|
+
/* Main content */
|
|
98
|
+
.main {
|
|
99
|
+
display: flex;
|
|
100
|
+
flex: 1;
|
|
101
|
+
overflow: hidden;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Preview area */
|
|
105
|
+
.preview-area {
|
|
106
|
+
flex: 1;
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: center;
|
|
110
|
+
padding: 24px;
|
|
111
|
+
overflow: auto;
|
|
112
|
+
background: var(--bg-app);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.preview-container {
|
|
116
|
+
background: white;
|
|
117
|
+
border-radius: 8px;
|
|
118
|
+
overflow: hidden;
|
|
119
|
+
box-sizing: border-box;
|
|
120
|
+
border: 1px solid var(--border-strong);
|
|
121
|
+
transition: width 300ms var(--ease);
|
|
122
|
+
height: 100%;
|
|
123
|
+
display: flex;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.preview-container.dark-content {
|
|
127
|
+
background: #1a1a2e;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.preview-container[data-viewport="mobile"] { width: 375px; }
|
|
131
|
+
.preview-container[data-viewport="tablet"] { width: 768px; }
|
|
132
|
+
.preview-container[data-viewport="desktop"] { width: 100%; max-width: 1280px; }
|
|
133
|
+
|
|
134
|
+
.preview-iframe {
|
|
135
|
+
width: 100%;
|
|
136
|
+
height: 100%;
|
|
137
|
+
border: none;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Source panel */
|
|
141
|
+
.source-panel {
|
|
142
|
+
width: 400px;
|
|
143
|
+
background: var(--bg-panel);
|
|
144
|
+
border-left: 1px solid var(--border);
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
flex-shrink: 0;
|
|
148
|
+
transition: width 200ms var(--ease);
|
|
149
|
+
overflow: hidden;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.source-panel.collapsed { width: 0; }
|
|
153
|
+
|
|
154
|
+
.source-header {
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
justify-content: space-between;
|
|
158
|
+
padding: 10px 16px;
|
|
159
|
+
border-bottom: 1px solid var(--border);
|
|
160
|
+
font-size: 12px;
|
|
161
|
+
font-weight: 600;
|
|
162
|
+
color: var(--text-secondary);
|
|
163
|
+
text-transform: uppercase;
|
|
164
|
+
letter-spacing: 0.05em;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.source-close {
|
|
168
|
+
background: none;
|
|
169
|
+
border: none;
|
|
170
|
+
color: var(--text-muted);
|
|
171
|
+
cursor: pointer;
|
|
172
|
+
font-size: 16px;
|
|
173
|
+
padding: 2px 6px;
|
|
174
|
+
border-radius: 4px;
|
|
175
|
+
transition: all 150ms var(--ease);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.source-close:hover { color: var(--text-primary); background: var(--bg-hover); }
|
|
179
|
+
|
|
180
|
+
.source-code {
|
|
181
|
+
flex: 1;
|
|
182
|
+
overflow: auto;
|
|
183
|
+
padding: 16px;
|
|
184
|
+
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', 'Cascadia Code', monospace;
|
|
185
|
+
font-size: 12px;
|
|
186
|
+
line-height: 1.6;
|
|
187
|
+
color: var(--text-primary);
|
|
188
|
+
white-space: pre;
|
|
189
|
+
tab-size: 2;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Empty state */
|
|
193
|
+
.empty-state {
|
|
194
|
+
display: flex;
|
|
195
|
+
flex-direction: column;
|
|
196
|
+
align-items: center;
|
|
197
|
+
justify-content: center;
|
|
198
|
+
height: 100%;
|
|
199
|
+
gap: 12px;
|
|
200
|
+
color: var(--text-muted);
|
|
201
|
+
font-size: 14px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.empty-state svg {
|
|
205
|
+
width: 48px;
|
|
206
|
+
height: 48px;
|
|
207
|
+
opacity: 0.3;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Branding */
|
|
211
|
+
.branding {
|
|
212
|
+
position: fixed;
|
|
213
|
+
bottom: 12px;
|
|
214
|
+
left: 16px;
|
|
215
|
+
font-size: 11px;
|
|
216
|
+
color: var(--text-muted);
|
|
217
|
+
letter-spacing: 0.04em;
|
|
218
|
+
user-select: none;
|
|
219
|
+
z-index: 10;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* Scrollbar */
|
|
223
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
224
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
225
|
+
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 3px; }
|
|
226
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.2); }
|
|
227
|
+
|
|
228
|
+
/* Copy toast */
|
|
229
|
+
.toast {
|
|
230
|
+
position: fixed;
|
|
231
|
+
bottom: 24px;
|
|
232
|
+
right: 24px;
|
|
233
|
+
background: var(--bg-elevated);
|
|
234
|
+
border: 1px solid var(--border-strong);
|
|
235
|
+
color: var(--text-primary);
|
|
236
|
+
padding: 8px 16px;
|
|
237
|
+
border-radius: 6px;
|
|
238
|
+
font-size: 12px;
|
|
239
|
+
opacity: 0;
|
|
240
|
+
transform: translateY(8px);
|
|
241
|
+
transition: all 200ms var(--ease);
|
|
242
|
+
pointer-events: none;
|
|
243
|
+
z-index: 100;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.toast.show {
|
|
247
|
+
opacity: 1;
|
|
248
|
+
transform: translateY(0);
|
|
249
|
+
}
|
|
250
|
+
</style>
|
|
251
|
+
</head>
|
|
252
|
+
<body>
|
|
253
|
+
<!-- Toolbar -->
|
|
254
|
+
<div class="toolbar">
|
|
255
|
+
<div class="toolbar-group">
|
|
256
|
+
<button class="toolbar-btn active" data-viewport="desktop" onclick="setViewport('desktop')">
|
|
257
|
+
Desktop
|
|
258
|
+
</button>
|
|
259
|
+
<button class="toolbar-btn" data-viewport="tablet" onclick="setViewport('tablet')">
|
|
260
|
+
Tablet
|
|
261
|
+
</button>
|
|
262
|
+
<button class="toolbar-btn" data-viewport="mobile" onclick="setViewport('mobile')">
|
|
263
|
+
Mobile
|
|
264
|
+
</button>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<div class="toolbar-group">
|
|
268
|
+
<button class="toolbar-btn active" id="lightModeBtn" onclick="setContentMode('light')">
|
|
269
|
+
Light
|
|
270
|
+
</button>
|
|
271
|
+
<button class="toolbar-btn" id="darkModeBtn" onclick="setContentMode('dark')">
|
|
272
|
+
Dark
|
|
273
|
+
</button>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<div class="toolbar-spacer"></div>
|
|
277
|
+
|
|
278
|
+
<div class="status">
|
|
279
|
+
<span class="status-dot" id="statusDot"></span>
|
|
280
|
+
<span id="statusText">Connecting...</span>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<button class="toolbar-btn" id="sourceToggleBtn" onclick="toggleSource()">
|
|
284
|
+
Source
|
|
285
|
+
</button>
|
|
286
|
+
|
|
287
|
+
<button class="toolbar-btn" onclick="copyCode()">
|
|
288
|
+
Copy Code
|
|
289
|
+
</button>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
<!-- Main -->
|
|
293
|
+
<div class="main">
|
|
294
|
+
<div class="preview-area">
|
|
295
|
+
<div class="preview-container" id="previewContainer" data-viewport="desktop">
|
|
296
|
+
<div class="empty-state" id="emptyState">
|
|
297
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
298
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
299
|
+
</svg>
|
|
300
|
+
<span>Waiting for component...</span>
|
|
301
|
+
</div>
|
|
302
|
+
<iframe class="preview-iframe" id="previewIframe" style="display:none;"></iframe>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<div class="source-panel" id="sourcePanel">
|
|
307
|
+
<div class="source-header">
|
|
308
|
+
<span>Source</span>
|
|
309
|
+
<button class="source-close" onclick="toggleSource()">×</button>
|
|
310
|
+
</div>
|
|
311
|
+
<pre class="source-code" id="sourceCode">No code received yet.</pre>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<div class="branding">Layout</div>
|
|
316
|
+
<div class="toast" id="toast">Copied to clipboard</div>
|
|
317
|
+
|
|
318
|
+
<script>
|
|
319
|
+
// State
|
|
320
|
+
let currentCode = '';
|
|
321
|
+
let currentCompiledJs = '';
|
|
322
|
+
let currentViewport = 'desktop';
|
|
323
|
+
let contentDarkMode = false;
|
|
324
|
+
let sourceVisible = true;
|
|
325
|
+
|
|
326
|
+
// Elements
|
|
327
|
+
const previewContainer = document.getElementById('previewContainer');
|
|
328
|
+
const previewIframe = document.getElementById('previewIframe');
|
|
329
|
+
const emptyState = document.getElementById('emptyState');
|
|
330
|
+
const sourcePanel = document.getElementById('sourcePanel');
|
|
331
|
+
const sourceCode = document.getElementById('sourceCode');
|
|
332
|
+
const statusDot = document.getElementById('statusDot');
|
|
333
|
+
const statusText = document.getElementById('statusText');
|
|
334
|
+
const toast = document.getElementById('toast');
|
|
335
|
+
|
|
336
|
+
// Viewport
|
|
337
|
+
function setViewport(viewport) {
|
|
338
|
+
currentViewport = viewport;
|
|
339
|
+
previewContainer.setAttribute('data-viewport', viewport);
|
|
340
|
+
document.querySelectorAll('[data-viewport]').forEach(function(btn) {
|
|
341
|
+
if (btn.classList.contains('toolbar-btn')) {
|
|
342
|
+
btn.classList.toggle('active', btn.getAttribute('data-viewport') === viewport);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Content dark/light mode
|
|
348
|
+
function setContentMode(mode) {
|
|
349
|
+
contentDarkMode = mode === 'dark';
|
|
350
|
+
document.getElementById('lightModeBtn').classList.toggle('active', !contentDarkMode);
|
|
351
|
+
document.getElementById('darkModeBtn').classList.toggle('active', contentDarkMode);
|
|
352
|
+
previewContainer.classList.toggle('dark-content', contentDarkMode);
|
|
353
|
+
if (currentCode) {
|
|
354
|
+
renderPreview(currentCode, currentCompiledJs);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Source panel
|
|
359
|
+
function toggleSource() {
|
|
360
|
+
sourceVisible = !sourceVisible;
|
|
361
|
+
sourcePanel.classList.toggle('collapsed', !sourceVisible);
|
|
362
|
+
document.getElementById('sourceToggleBtn').classList.toggle('active', sourceVisible);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Copy code
|
|
366
|
+
function copyCode() {
|
|
367
|
+
if (!currentCode) return;
|
|
368
|
+
navigator.clipboard.writeText(currentCode).then(function() {
|
|
369
|
+
toast.classList.add('show');
|
|
370
|
+
setTimeout(function() { toast.classList.remove('show'); }, 1500);
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Check if code is plain HTML
|
|
375
|
+
function isPlainHtml(code) {
|
|
376
|
+
var t = code.trim();
|
|
377
|
+
return t.startsWith('<!') || (
|
|
378
|
+
t.startsWith('<') &&
|
|
379
|
+
t.indexOf('import ') === -1 &&
|
|
380
|
+
t.indexOf('export ') === -1 &&
|
|
381
|
+
t.indexOf('useState') === -1 &&
|
|
382
|
+
t.indexOf('useEffect') === -1 &&
|
|
383
|
+
t.indexOf('const ') === -1 &&
|
|
384
|
+
t.indexOf('function ') === -1
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Build error display element safely (no innerHTML with untrusted content)
|
|
389
|
+
function buildErrorHtml(message) {
|
|
390
|
+
return '<pre style="padding:16px;color:#ef4444;font-family:monospace;font-size:13px;white-space:pre-wrap;">' +
|
|
391
|
+
message.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') +
|
|
392
|
+
'</pre>';
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Render preview
|
|
396
|
+
function renderPreview(code, compiledJs) {
|
|
397
|
+
emptyState.style.display = 'none';
|
|
398
|
+
previewIframe.style.display = 'block';
|
|
399
|
+
|
|
400
|
+
var bgColour = contentDarkMode ? '#1a1a2e' : '#ffffff';
|
|
401
|
+
var textColour = contentDarkMode ? '#e2e8f0' : '#1a202c';
|
|
402
|
+
|
|
403
|
+
if (isPlainHtml(code)) {
|
|
404
|
+
// Plain HTML - inject directly
|
|
405
|
+
previewIframe.srcdoc = '<!DOCTYPE html>' +
|
|
406
|
+
'<html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">' +
|
|
407
|
+
'<script src="https://cdn.tailwindcss.com"><\/script>' +
|
|
408
|
+
'<style>body { background: ' + bgColour + '; color: ' + textColour + '; }</style>' +
|
|
409
|
+
'</head><body>' + code + '</body></html>';
|
|
410
|
+
} else {
|
|
411
|
+
// TSX - use compiled JS with require/exports/module shims
|
|
412
|
+
var escapedJs = JSON.stringify(compiledJs)
|
|
413
|
+
.replace(/</g, '\\u003c')
|
|
414
|
+
.replace(/>/g, '\\u003e');
|
|
415
|
+
|
|
416
|
+
previewIframe.srcdoc = '<!DOCTYPE html>' +
|
|
417
|
+
'<html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">' +
|
|
418
|
+
'<script src="https://cdn.tailwindcss.com"><\/script>' +
|
|
419
|
+
'<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"><\/script>' +
|
|
420
|
+
'<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"><\/script>' +
|
|
421
|
+
'<style>body { background: ' + bgColour + '; color: ' + textColour + '; margin: 0; }</style>' +
|
|
422
|
+
'</head><body><div id="root"></div>' +
|
|
423
|
+
'<script>' +
|
|
424
|
+
'try {' +
|
|
425
|
+
' var exports = {};' +
|
|
426
|
+
' var module = { exports: exports };' +
|
|
427
|
+
' function require(name) {' +
|
|
428
|
+
' if (name === "react" || name === "React") return React;' +
|
|
429
|
+
' if (name === "react-dom" || name === "react-dom/client" || name === "ReactDOM") return ReactDOM;' +
|
|
430
|
+
' throw new Error("Cannot require: " + name);' +
|
|
431
|
+
' }' +
|
|
432
|
+
' var __code = ' + escapedJs + ';' +
|
|
433
|
+
' var s = document.createElement("script");' +
|
|
434
|
+
' s.textContent = __code;' +
|
|
435
|
+
' document.body.appendChild(s);' +
|
|
436
|
+
' var Component = module.exports.default || module.exports;' +
|
|
437
|
+
' if (typeof Component === "function") {' +
|
|
438
|
+
' var root = ReactDOM.createRoot(document.getElementById("root"));' +
|
|
439
|
+
' root.render(React.createElement(Component));' +
|
|
440
|
+
' } else {' +
|
|
441
|
+
' document.getElementById("root").textContent = "No default export found. Export a React component as default.";' +
|
|
442
|
+
' }' +
|
|
443
|
+
'} catch(e) {' +
|
|
444
|
+
' document.getElementById("root").textContent = e.message + "\\n" + (e.stack || "");' +
|
|
445
|
+
'}' +
|
|
446
|
+
'<\/script></body></html>';
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Update source panel
|
|
451
|
+
function updateSourcePanel(code) {
|
|
452
|
+
sourceCode.textContent = code;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// WebSocket connection with auto-reconnect
|
|
456
|
+
function connect() {
|
|
457
|
+
var protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
458
|
+
var ws = new WebSocket(protocol + '//' + location.host);
|
|
459
|
+
|
|
460
|
+
ws.onopen = function() {
|
|
461
|
+
statusDot.classList.add('connected');
|
|
462
|
+
statusText.textContent = 'Connected';
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
ws.onclose = function() {
|
|
466
|
+
statusDot.classList.remove('connected');
|
|
467
|
+
statusText.textContent = 'Disconnected';
|
|
468
|
+
setTimeout(connect, 2000);
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
ws.onerror = function() {
|
|
472
|
+
ws.close();
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
ws.onmessage = function(event) {
|
|
476
|
+
try {
|
|
477
|
+
var data = JSON.parse(event.data);
|
|
478
|
+
if (data.type === 'update') {
|
|
479
|
+
currentCode = data.code;
|
|
480
|
+
currentCompiledJs = data.compiledJs;
|
|
481
|
+
renderPreview(currentCode, currentCompiledJs);
|
|
482
|
+
updateSourcePanel(currentCode);
|
|
483
|
+
}
|
|
484
|
+
} catch (e) {
|
|
485
|
+
console.error('Failed to parse WebSocket message:', e);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
connect();
|
|
491
|
+
</script>
|
|
492
|
+
</body>
|
|
493
|
+
</html>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface TranspileResult {
|
|
2
|
+
js: string;
|
|
3
|
+
error?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Transpile TSX/JSX source to CommonJS JavaScript using TypeScript's transpileModule.
|
|
7
|
+
* If the input looks like plain HTML (no JSX indicators), returns it unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export declare function transpileTsx(code: string): TranspileResult;
|
|
10
|
+
//# sourceMappingURL=transpile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transpile.d.ts","sourceRoot":"","sources":["../../../src/preview/transpile.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAqC1D"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
/**
|
|
3
|
+
* Transpile TSX/JSX source to CommonJS JavaScript using TypeScript's transpileModule.
|
|
4
|
+
* If the input looks like plain HTML (no JSX indicators), returns it unchanged.
|
|
5
|
+
*/
|
|
6
|
+
export function transpileTsx(code) {
|
|
7
|
+
const trimmed = code.trim();
|
|
8
|
+
// If it looks like plain HTML (starts with < but has no JSX indicators), return as-is
|
|
9
|
+
const isPlainHtml = trimmed.startsWith("<!") ||
|
|
10
|
+
(trimmed.startsWith("<") &&
|
|
11
|
+
!trimmed.includes("import ") &&
|
|
12
|
+
!trimmed.includes("export ") &&
|
|
13
|
+
!trimmed.includes("const ") &&
|
|
14
|
+
!trimmed.includes("function ") &&
|
|
15
|
+
!trimmed.includes("=> ") &&
|
|
16
|
+
!trimmed.includes("useState") &&
|
|
17
|
+
!trimmed.includes("useEffect"));
|
|
18
|
+
if (isPlainHtml) {
|
|
19
|
+
return { js: code };
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const result = ts.transpileModule(code, {
|
|
23
|
+
compilerOptions: {
|
|
24
|
+
module: ts.ModuleKind.CommonJS,
|
|
25
|
+
target: ts.ScriptTarget.ES2020,
|
|
26
|
+
jsx: ts.JsxEmit.React,
|
|
27
|
+
esModuleInterop: true,
|
|
28
|
+
allowJs: true,
|
|
29
|
+
strict: false,
|
|
30
|
+
},
|
|
31
|
+
fileName: "component.tsx",
|
|
32
|
+
});
|
|
33
|
+
return { js: result.outputText };
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
37
|
+
return { js: "", error: message };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=transpile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transpile.js","sourceRoot":"","sources":["../../../src/preview/transpile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAO5B;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAE5B,sFAAsF;IACtF,MAAM,WAAW,GACf,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;QACxB,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YACtB,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;YAC5B,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;YAC5B,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAC3B,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;YAC9B,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;YACxB,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;YAC7B,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;IAEpC,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE;YACtC,eAAe,EAAE;gBACf,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ;gBAC9B,MAAM,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM;gBAC9B,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK;gBACrB,eAAe,EAAE,IAAI;gBACrB,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,KAAK;aACd;YACD,QAAQ,EAAE,eAAe;SAC1B,CAAC,CAAC;QAEH,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IACpC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type http from "node:http";
|
|
2
|
+
export interface WsBroadcaster {
|
|
3
|
+
broadcast: (code: string, compiledJs: string) => void;
|
|
4
|
+
getLastPreview: () => {
|
|
5
|
+
code: string;
|
|
6
|
+
compiledJs: string;
|
|
7
|
+
} | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create a WebSocket server attached to the given HTTP server.
|
|
11
|
+
* Returns a broadcast function that pushes updates to all connected clients.
|
|
12
|
+
*
|
|
13
|
+
* Also handles incoming { type: "preview", code, language } messages from MCP tools:
|
|
14
|
+
* transpiles TSX server-side and broadcasts the compiled result to all preview clients.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createWsServer(server: http.Server): WsBroadcaster;
|
|
17
|
+
//# sourceMappingURL=ws.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../../src/preview/ws.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAIlC,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,cAAc,EAAE,MAAM;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACnE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,aAAa,CAuEjE"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { WebSocketServer } from "ws";
|
|
2
|
+
import { transpileTsx } from "./transpile.js";
|
|
3
|
+
/**
|
|
4
|
+
* Create a WebSocket server attached to the given HTTP server.
|
|
5
|
+
* Returns a broadcast function that pushes updates to all connected clients.
|
|
6
|
+
*
|
|
7
|
+
* Also handles incoming { type: "preview", code, language } messages from MCP tools:
|
|
8
|
+
* transpiles TSX server-side and broadcasts the compiled result to all preview clients.
|
|
9
|
+
*/
|
|
10
|
+
export function createWsServer(server) {
|
|
11
|
+
const wss = new WebSocketServer({ server });
|
|
12
|
+
wss.on("error", () => {
|
|
13
|
+
// Handled by the HTTP server's error listener — suppress unhandled crash
|
|
14
|
+
});
|
|
15
|
+
const clients = new Set();
|
|
16
|
+
let lastPreview = null;
|
|
17
|
+
wss.on("connection", (ws) => {
|
|
18
|
+
clients.add(ws);
|
|
19
|
+
ws.on("message", (data) => {
|
|
20
|
+
try {
|
|
21
|
+
const msg = JSON.parse(String(data));
|
|
22
|
+
if (msg.type === "preview" && msg.code) {
|
|
23
|
+
const language = msg.language ?? "tsx";
|
|
24
|
+
let compiledJs = msg.code;
|
|
25
|
+
if (language === "tsx" || language === "jsx" || language === "ts") {
|
|
26
|
+
const result = transpileTsx(msg.code);
|
|
27
|
+
if (result.error) {
|
|
28
|
+
// Send error back to the sender
|
|
29
|
+
ws.send(JSON.stringify({
|
|
30
|
+
type: "error",
|
|
31
|
+
message: `Transpilation failed: ${result.error}`,
|
|
32
|
+
}));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
compiledJs = result.js;
|
|
36
|
+
}
|
|
37
|
+
// Broadcast to all OTHER clients (the preview page)
|
|
38
|
+
broadcast(msg.code, compiledJs);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Ignore malformed messages
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
ws.on("close", () => {
|
|
46
|
+
clients.delete(ws);
|
|
47
|
+
});
|
|
48
|
+
ws.on("error", () => {
|
|
49
|
+
clients.delete(ws);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
function broadcast(code, compiledJs) {
|
|
53
|
+
lastPreview = { code, compiledJs };
|
|
54
|
+
const payload = JSON.stringify({ type: "update", code, compiledJs });
|
|
55
|
+
for (const client of clients) {
|
|
56
|
+
if (client.readyState === client.OPEN) {
|
|
57
|
+
client.send(payload);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function getLastPreview() {
|
|
62
|
+
return lastPreview;
|
|
63
|
+
}
|
|
64
|
+
return { broadcast, getLastPreview };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=ws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.js","sourceRoot":"","sources":["../../../src/preview/ws.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAkB,MAAM,IAAI,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAO9C;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,MAAmB;IAChD,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5C,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACnB,yEAAyE;IAC3E,CAAC,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,IAAI,GAAG,EAAa,CAAC;IACrC,IAAI,WAAW,GAAgD,IAAI,CAAC;IAEpE,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,EAAE;QAC1B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAIlC,CAAC;gBAEF,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;oBACvC,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,KAAK,CAAC;oBACvC,IAAI,UAAU,GAAG,GAAG,CAAC,IAAI,CAAC;oBAE1B,IAAI,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;wBAClE,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;wBACtC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;4BACjB,gCAAgC;4BAChC,EAAE,CAAC,IAAI,CACL,IAAI,CAAC,SAAS,CAAC;gCACb,IAAI,EAAE,OAAO;gCACb,OAAO,EAAE,yBAAyB,MAAM,CAAC,KAAK,EAAE;6BACjD,CAAC,CACH,CAAC;4BACF,OAAO;wBACT,CAAC;wBACD,UAAU,GAAG,MAAM,CAAC,EAAE,CAAC;oBACzB,CAAC;oBAED,oDAAoD;oBACpD,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;gBAClC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,4BAA4B;YAC9B,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,SAAS,SAAS,CAAC,IAAY,EAAE,UAAkB;QACjD,WAAW,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAErE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,UAAU,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;gBACtC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,SAAS,cAAc;QACrB,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;AACvC,CAAC"}
|