@wire-dsl/web 0.0.7 → 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/.turbo/turbo-build.log +103 -7
- package/CHANGELOG.md +30 -3
- package/dist/assets/abap-DLDM7-KI.js +1 -0
- package/dist/assets/apex-DNDY2TF8.js +1 -0
- package/dist/assets/azcli-Y6nb8tq_.js +1 -0
- package/dist/assets/bat-BwHxbl9M.js +1 -0
- package/dist/assets/bicep-CFznDFnq.js +2 -0
- package/dist/assets/cameligo-Bf6VGUru.js +1 -0
- package/dist/assets/clojure-Dnu-v4kV.js +1 -0
- package/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/assets/coffee-Bd8akH9Z.js +1 -0
- package/dist/assets/cpp-BbWJElDN.js +1 -0
- package/dist/assets/csharp-Co3qMtFm.js +1 -0
- package/dist/assets/csp-D-4FJmMZ.js +1 -0
- package/dist/assets/css-DdJfP1eB.js +3 -0
- package/dist/assets/css.worker-DBVD8oXr.js +93 -0
- package/dist/assets/cssMode-BgkzAyoH.js +1 -0
- package/dist/assets/cypher-cTPe9QuQ.js +1 -0
- package/dist/assets/dart-BOtBlQCF.js +1 -0
- package/dist/assets/dockerfile-BG73LgW2.js +1 -0
- package/dist/assets/ecl-BEgZUVRK.js +1 -0
- package/dist/assets/elixir-BkW5O-1t.js +1 -0
- package/dist/assets/flow9-BeJ5waoc.js +1 -0
- package/dist/assets/freemarker2-D05KrEgD.js +3 -0
- package/dist/assets/fsharp-PahG7c26.js +1 -0
- package/dist/assets/go-acbASCJo.js +1 -0
- package/dist/assets/graphql-BxJiqAUM.js +1 -0
- package/dist/assets/handlebars-CmNF6dIr.js +1 -0
- package/dist/assets/hcl-DtV1sZF8.js +1 -0
- package/dist/assets/html-DdFfMtqo.js +1 -0
- package/dist/assets/html.worker-CwpTb9lJ.js +470 -0
- package/dist/assets/htmlMode-BzENSv3x.js +1 -0
- package/dist/assets/index-C6yQ9VSx.js +1595 -0
- package/dist/assets/index-CmAJnnOw.css +1 -0
- package/dist/assets/ini-Kd9XrMLS.js +1 -0
- package/dist/assets/java-CXBNlu9o.js +1 -0
- package/dist/assets/javascript-BDq34vkg.js +1 -0
- package/dist/assets/json.worker-BoL8UZqY.js +58 -0
- package/dist/assets/jsonMode-xsVJWt4Q.js +7 -0
- package/dist/assets/julia-cl7-CwDS.js +1 -0
- package/dist/assets/kotlin-s7OhZKlX.js +1 -0
- package/dist/assets/less-9HpZscsL.js +2 -0
- package/dist/assets/lexon-OrD6JF1K.js +1 -0
- package/dist/assets/liquid-BKLduW-j.js +1 -0
- package/dist/assets/lspLanguageFeatures-DENz5XIL.js +4 -0
- package/dist/assets/lua-Cyyb5UIc.js +1 -0
- package/dist/assets/m3-B8OfTtLu.js +1 -0
- package/dist/assets/markdown-BFxVWTOG.js +1 -0
- package/dist/assets/mdx-Cpg3g8iv.js +1 -0
- package/dist/assets/mips-CiqrrVzr.js +1 -0
- package/dist/assets/msdax-DmeGPVcC.js +1 -0
- package/dist/assets/mysql-C_tMU-Nz.js +1 -0
- package/dist/assets/objective-c-BDtDVThU.js +1 -0
- package/dist/assets/pascal-vHIfCaH5.js +1 -0
- package/dist/assets/pascaligo-DtZ0uQbO.js +1 -0
- package/dist/assets/perl-Ub6l9XKa.js +1 -0
- package/dist/assets/pgsql-BlNEE0v7.js +1 -0
- package/dist/assets/php-BBUBE1dy.js +1 -0
- package/dist/assets/pla-DSh2-awV.js +1 -0
- package/dist/assets/postiats-CocnycG-.js +1 -0
- package/dist/assets/powerquery-tScXyioY.js +1 -0
- package/dist/assets/powershell-COWaemsV.js +1 -0
- package/dist/assets/protobuf-Brw8urJB.js +2 -0
- package/dist/assets/pug-8SOpv6rk.js +1 -0
- package/dist/assets/python-Ca2JvAvf.js +1 -0
- package/dist/assets/qsharp-Bw9ernYp.js +1 -0
- package/dist/assets/r-j7ic8hl3.js +1 -0
- package/dist/assets/razor-B_xld0Yq.js +1 -0
- package/dist/assets/redis-Bu5POkcn.js +1 -0
- package/dist/assets/redshift-Bs9aos_-.js +1 -0
- package/dist/assets/restructuredtext-CqXO7rUv.js +1 -0
- package/dist/assets/ruby-zBfavPgS.js +1 -0
- package/dist/assets/rust-BzKRNQWT.js +1 -0
- package/dist/assets/sb-BBc9UKZt.js +1 -0
- package/dist/assets/scala-D9hQfWCl.js +1 -0
- package/dist/assets/scheme-BPhDTwHR.js +1 -0
- package/dist/assets/scss-CBJaRo0y.js +3 -0
- package/dist/assets/shell-DiJ1NA_G.js +1 -0
- package/dist/assets/solidity-Db0IVjzk.js +1 -0
- package/dist/assets/sophia-CnS9iZB_.js +1 -0
- package/dist/assets/sparql-CJmd_6j2.js +1 -0
- package/dist/assets/sql-ClhHkBeG.js +1 -0
- package/dist/assets/st-CHwy0fLd.js +1 -0
- package/dist/assets/swift-CnmFD0ga.js +1 -0
- package/dist/assets/systemverilog-Bs9z6M-B.js +1 -0
- package/dist/assets/tcl-Dm6ycUr_.js +1 -0
- package/dist/assets/ts.worker-BH9nVgjN.js +67718 -0
- package/dist/assets/tsMode-BinQkqy9.js +11 -0
- package/dist/assets/twig-Csy3S7wG.js +1 -0
- package/dist/assets/typescript-DQO38ZbJ.js +1 -0
- package/dist/assets/typespec-Btyra-wh.js +1 -0
- package/dist/assets/vb-Db0cS2oM.js +1 -0
- package/dist/assets/wgsl-BTesnYfV.js +298 -0
- package/dist/assets/xml-DF1bgZg2.js +1 -0
- package/dist/assets/yaml-BiNWh9S_.js +1 -0
- package/dist/examples/admin-dashboard.wire +95 -0
- package/dist/examples/analytics-dashboard.wire +65 -0
- package/dist/examples/form-example.wire +50 -0
- package/dist/examples/simple-dashboard.wire +40 -0
- package/dist/examples/simple-multi-screen.wire +30 -0
- package/dist/index.html +2 -2
- package/package.json +5 -3
- package/postcss.config.mjs +3 -1
- package/public/examples/admin-dashboard.wire +95 -0
- package/public/examples/analytics-dashboard.wire +65 -0
- package/public/examples/form-example.wire +50 -0
- package/public/examples/simple-dashboard.wire +40 -0
- package/public/examples/simple-multi-screen.wire +30 -0
- package/src/App.tsx +3 -13
- package/src/components/MonacoEditorComponent.tsx +112 -0
- package/src/components/WireLiveEditor.tsx +729 -0
- package/src/components/WireLiveHeader.tsx +469 -0
- package/src/components/index.ts +5 -0
- package/src/hooks/useCanvasZoom.ts +137 -0
- package/src/hooks/useFileSystemAccess.ts +123 -0
- package/src/hooks/useWireParser.ts +222 -0
- package/src/index.css +1 -3
- package/src/main.tsx +7 -5
- package/src/monaco/wireLanguage.ts +370 -0
- package/src/store/editorStore.ts +196 -0
- package/src/store/index.ts +2 -0
- package/tailwind.config.js +2 -1
- package/vite.config.ts +17 -0
- package/dist/assets/index-CHiOjnNN.js +0 -9
- package/dist/assets/index-CUIy2zPc.css +0 -1
- package/src/App.js +0 -4
- package/src/main.js +0 -6
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import * as monaco from 'monaco-editor';
|
|
2
|
+
import {
|
|
3
|
+
ALL_KEYWORDS,
|
|
4
|
+
getCompletions,
|
|
5
|
+
getScopeBasedCompletions,
|
|
6
|
+
COMPONENTS,
|
|
7
|
+
LAYOUTS,
|
|
8
|
+
PROPERTY_VALUES,
|
|
9
|
+
getAvailableComponents,
|
|
10
|
+
getComponentProperties,
|
|
11
|
+
getPropertyValueSuggestions,
|
|
12
|
+
determineScope,
|
|
13
|
+
type KeywordDefinition,
|
|
14
|
+
} from '@wire-dsl/language-support';
|
|
15
|
+
|
|
16
|
+
// Registrar el lenguaje Wire DSL en Monaco
|
|
17
|
+
export function registerWireLanguage() {
|
|
18
|
+
// Registrar el lenguaje
|
|
19
|
+
monaco.languages.register({ id: 'wire' });
|
|
20
|
+
|
|
21
|
+
// Construir patrones de keywords desde language-support
|
|
22
|
+
const keywordPattern = (ALL_KEYWORDS as KeywordDefinition[])
|
|
23
|
+
.map((kw: KeywordDefinition) => kw.name)
|
|
24
|
+
.join('|');
|
|
25
|
+
|
|
26
|
+
// Definir el tokenizer
|
|
27
|
+
monaco.languages.setMonarchTokensProvider('wire', {
|
|
28
|
+
tokenizer: {
|
|
29
|
+
root: [
|
|
30
|
+
// Keywords y componentes
|
|
31
|
+
[
|
|
32
|
+
new RegExp(`\\b(${keywordPattern})\\b`),
|
|
33
|
+
'keyword',
|
|
34
|
+
],
|
|
35
|
+
[/\b(true|false|null)\b/, 'constant'],
|
|
36
|
+
[/\b(let|const|var|if|else|for|while|return|function)\b/, 'keyword.control'],
|
|
37
|
+
|
|
38
|
+
// Strings
|
|
39
|
+
[/"(?:\\.|[^"\\])*"/, 'string'],
|
|
40
|
+
[/'(?:\\.|[^'\\])*'/, 'string'],
|
|
41
|
+
|
|
42
|
+
// Comments
|
|
43
|
+
[/\/\/.*$/, 'comment'],
|
|
44
|
+
[/\/\*/, 'comment', '@comment'],
|
|
45
|
+
|
|
46
|
+
// Numbers
|
|
47
|
+
[/\d+(\.\d+)?/, 'number'],
|
|
48
|
+
|
|
49
|
+
// Operators
|
|
50
|
+
[/[=+\-*/%&|^<>!]=?|[(){}\[\];:.,]/, 'operator'],
|
|
51
|
+
|
|
52
|
+
// Whitespace
|
|
53
|
+
[/\s+/, 'white'],
|
|
54
|
+
],
|
|
55
|
+
comment: [
|
|
56
|
+
[/[^*/]+/, 'comment'],
|
|
57
|
+
[/\*\//, 'comment', '@pop'],
|
|
58
|
+
[/[*/]/, 'comment'],
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Configurar color de sintaxis (theme)
|
|
64
|
+
monaco.editor.defineTheme('wire-light', {
|
|
65
|
+
base: 'vs',
|
|
66
|
+
inherit: true,
|
|
67
|
+
rules: [
|
|
68
|
+
{ token: 'keyword', foreground: '7928ca', fontStyle: 'bold' },
|
|
69
|
+
{ token: 'keyword.control', foreground: '7928ca', fontStyle: 'bold' },
|
|
70
|
+
{ token: 'constant', foreground: 'c41a16' },
|
|
71
|
+
{ token: 'string', foreground: '2a7e20' },
|
|
72
|
+
{ token: 'number', foreground: '098658' },
|
|
73
|
+
{ token: 'comment', foreground: '999999', fontStyle: 'italic' },
|
|
74
|
+
],
|
|
75
|
+
colors: {
|
|
76
|
+
'editor.background': '#ffffff',
|
|
77
|
+
'editor.foreground': '#333333',
|
|
78
|
+
'editor.lineNumbersColumn.background': '#f8f8f8',
|
|
79
|
+
'editorCursor.foreground': '#333333',
|
|
80
|
+
'editor.selectionBackground': '#d4d4d450',
|
|
81
|
+
'editorLineNumber.foreground': '#999999',
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Configurar autocompletar con lógica de Wire DSL
|
|
86
|
+
monaco.languages.registerCompletionItemProvider('wire', {
|
|
87
|
+
provideCompletionItems: (model, position) => {
|
|
88
|
+
const lineText = model.getLineContent(position.lineNumber).substring(0, position.column - 1);
|
|
89
|
+
const documentText = model.getValue();
|
|
90
|
+
const cursorOffset = model.getOffsetAt(position);
|
|
91
|
+
const textBeforeCursor = documentText.substring(0, cursorOffset);
|
|
92
|
+
|
|
93
|
+
// HIGHEST PRIORITY: Check if we're in a component definition (before anything else)
|
|
94
|
+
const componentContext = detectComponentPropertyContext(lineText);
|
|
95
|
+
if (componentContext) {
|
|
96
|
+
return {
|
|
97
|
+
suggestions: getComponentPropertiesCompletions(componentContext, lineText),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check immediate line context (layout types, component names, property values)
|
|
102
|
+
if (/layout\s+\w*$/.test(lineText)) {
|
|
103
|
+
return {
|
|
104
|
+
suggestions: getLayoutTypeCompletions(),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (/component\s+[A-Z]?\w*$/.test(lineText)) {
|
|
109
|
+
return {
|
|
110
|
+
suggestions: getComponentCompletions(),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (/:\s+\w*$/.test(lineText)) {
|
|
115
|
+
return {
|
|
116
|
+
suggestions: getPropertyValueCompletions(lineText),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Determine broader document scope
|
|
121
|
+
const scope = determineScope(textBeforeCursor);
|
|
122
|
+
|
|
123
|
+
if (scope === 'empty-file') {
|
|
124
|
+
return {
|
|
125
|
+
suggestions: getProjectOnlyCompletion(),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (scope === 'inside-project') {
|
|
129
|
+
return {
|
|
130
|
+
suggestions: getProjectLevelCompletions(),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (scope === 'inside-screen') {
|
|
134
|
+
return {
|
|
135
|
+
suggestions: getScreenLevelCompletions(),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (scope === 'inside-layout') {
|
|
139
|
+
return {
|
|
140
|
+
suggestions: getLayoutBodyCompletions(),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
suggestions: [],
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
triggerCharacters: [' ', '{', ':', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'],
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Helper functions for completion logic
|
|
152
|
+
function detectComponentPropertyContext(lineText: string): string | null {
|
|
153
|
+
const match = lineText.match(/component\s+([A-Z]\w*)/);
|
|
154
|
+
if (!match) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const componentName = match[1];
|
|
159
|
+
|
|
160
|
+
// Check if this is a valid component
|
|
161
|
+
if (!COMPONENTS[componentName as keyof typeof COMPONENTS]) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Get text after the component name
|
|
166
|
+
const afterComponent = lineText.substring(match.index! + match[0].length);
|
|
167
|
+
|
|
168
|
+
// If there's nothing after, or only spaces, we're at the end - show properties
|
|
169
|
+
// Or if we have properties already (word followed by colon), show properties
|
|
170
|
+
if (afterComponent.match(/^\s*$/) || afterComponent.match(/^\s+[\w-]+:/)) {
|
|
171
|
+
return componentName;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getProjectOnlyCompletion(): any[] {
|
|
178
|
+
return [
|
|
179
|
+
{
|
|
180
|
+
label: 'project',
|
|
181
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
182
|
+
detail: 'Define a new Wire DSL project (root element)',
|
|
183
|
+
insertText: 'project "${1:ProjectName}" {\n\t${2:}\n}',
|
|
184
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
185
|
+
}
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function getProjectLevelCompletions(): any[] {
|
|
190
|
+
return [
|
|
191
|
+
{
|
|
192
|
+
label: 'tokens',
|
|
193
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
194
|
+
detail: 'Define design tokens',
|
|
195
|
+
insertText: 'tokens ',
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
label: 'colors',
|
|
199
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
200
|
+
detail: 'Define custom color palette',
|
|
201
|
+
insertText: 'colors {\n\t${1:}\n}',
|
|
202
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
label: 'mocks',
|
|
206
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
207
|
+
detail: 'Define mock data',
|
|
208
|
+
insertText: 'mocks {\n\t${1:}\n}',
|
|
209
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
label: 'screen',
|
|
213
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
214
|
+
detail: 'Define a new screen/view',
|
|
215
|
+
insertText: 'screen ${1:ScreenName} {\n\t${2:}\n}',
|
|
216
|
+
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getScreenLevelCompletions(): any[] {
|
|
222
|
+
return [
|
|
223
|
+
{
|
|
224
|
+
label: 'layout',
|
|
225
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
226
|
+
detail: 'Define a layout for this screen',
|
|
227
|
+
insertText: 'layout ',
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getLayoutBodyCompletions(): any[] {
|
|
233
|
+
return [
|
|
234
|
+
{
|
|
235
|
+
label: 'component',
|
|
236
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
237
|
+
detail: 'Add a component',
|
|
238
|
+
insertText: 'component ',
|
|
239
|
+
sortText: '1-component',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
label: 'layout',
|
|
243
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
244
|
+
detail: 'Add a nested layout',
|
|
245
|
+
insertText: 'layout ',
|
|
246
|
+
sortText: '2-layout',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
label: 'cell',
|
|
250
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
251
|
+
detail: 'Define a cell in a grid',
|
|
252
|
+
insertText: 'cell span: ',
|
|
253
|
+
sortText: '3-cell',
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function getLayoutTypeCompletions(): any[] {
|
|
259
|
+
const items: any[] = [];
|
|
260
|
+
|
|
261
|
+
for (const [key, layout] of Object.entries(LAYOUTS)) {
|
|
262
|
+
const item: any = {
|
|
263
|
+
label: key,
|
|
264
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
|
265
|
+
detail: (layout as any).description,
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// Monaco no soporta choice lists como VS Code, así que usamos insertText simple
|
|
269
|
+
if (key === 'stack') {
|
|
270
|
+
item.insertText = 'stack(direction: vertical, gap: md, padding: md) {\n\t${1:}\n}';
|
|
271
|
+
} else if (key === 'grid') {
|
|
272
|
+
item.insertText = 'grid(columns: 12, gap: md) {\n\t${1:}\n}';
|
|
273
|
+
} else if (key === 'split') {
|
|
274
|
+
item.insertText = 'split(sidebar: 260, gap: md) {\n\t${1:}\n}';
|
|
275
|
+
} else if (key === 'panel') {
|
|
276
|
+
item.insertText = 'panel(padding: md) {\n\t${1:}\n}';
|
|
277
|
+
} else if (key === 'card') {
|
|
278
|
+
item.insertText = 'card(padding: md, gap: md) {\n\t${1:}\n}';
|
|
279
|
+
}
|
|
280
|
+
item.insertTextRules = monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet;
|
|
281
|
+
|
|
282
|
+
items.push(item);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return items;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function getComponentCompletions(): any[] {
|
|
289
|
+
const items: any[] = [];
|
|
290
|
+
|
|
291
|
+
for (const [name, metadata] of Object.entries(COMPONENTS)) {
|
|
292
|
+
// Only uppercase names are components
|
|
293
|
+
if (!/^[A-Z]/.test(name)) continue;
|
|
294
|
+
|
|
295
|
+
const item = {
|
|
296
|
+
label: name,
|
|
297
|
+
kind: monaco.languages.CompletionItemKind.Class,
|
|
298
|
+
detail: (metadata as any).description,
|
|
299
|
+
insertText: name + ' ',
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
items.push(item);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return items;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function getComponentPropertiesCompletions(componentName: string, lineText: string): any[] {
|
|
309
|
+
const items: any[] = [];
|
|
310
|
+
const component = COMPONENTS[componentName as keyof typeof COMPONENTS];
|
|
311
|
+
|
|
312
|
+
if (!component) {
|
|
313
|
+
return items;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Get component-specific properties
|
|
317
|
+
const properties = component.properties || [];
|
|
318
|
+
|
|
319
|
+
// Extract already-declared properties from the line
|
|
320
|
+
const declaredPropsMatch = lineText.matchAll(/(\w+):\s*/g);
|
|
321
|
+
const declaredProps = new Set<string>();
|
|
322
|
+
for (const match of declaredPropsMatch) {
|
|
323
|
+
declaredProps.add(match[1]);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
for (const propName of properties) {
|
|
327
|
+
// Skip if this property is already declared on this line
|
|
328
|
+
if (declaredProps.has(propName)) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const item = {
|
|
333
|
+
label: propName,
|
|
334
|
+
kind: monaco.languages.CompletionItemKind.Property,
|
|
335
|
+
detail: `Property of ${componentName}`,
|
|
336
|
+
insertText: propName + ': ',
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
items.push(item);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return items;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function getPropertyValueCompletions(lineText: string): any[] {
|
|
346
|
+
const items: any[] = [];
|
|
347
|
+
|
|
348
|
+
// Extract property name
|
|
349
|
+
const propertyMatch = lineText.match(/(\w+):\s*\w*$/);
|
|
350
|
+
if (!propertyMatch) {
|
|
351
|
+
return items;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const propertyName = propertyMatch[1];
|
|
355
|
+
const values = PROPERTY_VALUES[propertyName];
|
|
356
|
+
|
|
357
|
+
if (values && Array.isArray(values)) {
|
|
358
|
+
for (const value of values) {
|
|
359
|
+
const item = {
|
|
360
|
+
label: String(value),
|
|
361
|
+
kind: monaco.languages.CompletionItemKind.Value,
|
|
362
|
+
insertText: String(value),
|
|
363
|
+
};
|
|
364
|
+
items.push(item);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return items;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { persist } from 'zustand/middleware';
|
|
3
|
+
|
|
4
|
+
export interface EditorFile {
|
|
5
|
+
name: string;
|
|
6
|
+
content: string;
|
|
7
|
+
isDirty: boolean;
|
|
8
|
+
lastModified: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface EditorState {
|
|
12
|
+
files: Map<string, EditorFile>;
|
|
13
|
+
currentFileId: string;
|
|
14
|
+
zoomLevel: number;
|
|
15
|
+
previewMode: 'all-screens' | 'single-screen';
|
|
16
|
+
selectedScreen: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface EditorStore extends EditorState {
|
|
20
|
+
// File operations
|
|
21
|
+
createFile: (name: string, content?: string) => void;
|
|
22
|
+
openFile: (fileId: string) => void;
|
|
23
|
+
updateFileContent: (fileId: string, content: string) => void;
|
|
24
|
+
renameFile: (fileId: string, newName: string) => void;
|
|
25
|
+
markFileSaved: (fileId: string) => void;
|
|
26
|
+
deleteFile: (fileId: string) => void;
|
|
27
|
+
closeFile: (fileId: string) => void;
|
|
28
|
+
|
|
29
|
+
// Preview operations
|
|
30
|
+
setZoomLevel: (level: number) => void;
|
|
31
|
+
setPreviewMode: (mode: 'all-screens' | 'single-screen') => void;
|
|
32
|
+
setSelectedScreen: (screenName: string | null) => void;
|
|
33
|
+
|
|
34
|
+
// Current file shortcuts
|
|
35
|
+
getCurrentFile: () => EditorFile | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const createInitialFile = (): EditorFile => ({
|
|
39
|
+
name: 'untitled.wire',
|
|
40
|
+
content: '',
|
|
41
|
+
isDirty: false,
|
|
42
|
+
lastModified: Date.now(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const useEditorStore = create<EditorStore>()(
|
|
46
|
+
persist(
|
|
47
|
+
(set, get) => ({
|
|
48
|
+
files: new Map([['untitled-1', createInitialFile()]]),
|
|
49
|
+
currentFileId: 'untitled-1',
|
|
50
|
+
zoomLevel: 100,
|
|
51
|
+
previewMode: 'all-screens',
|
|
52
|
+
selectedScreen: null,
|
|
53
|
+
|
|
54
|
+
createFile: (name: string, content?: string) => {
|
|
55
|
+
const fileId = `file-${Date.now()}`;
|
|
56
|
+
const file: EditorFile = {
|
|
57
|
+
name,
|
|
58
|
+
content: content || '',
|
|
59
|
+
isDirty: !!content,
|
|
60
|
+
lastModified: Date.now(),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
set((state) => ({
|
|
64
|
+
files: new Map(state.files).set(fileId, file),
|
|
65
|
+
currentFileId: fileId,
|
|
66
|
+
}));
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
openFile: (fileId: string) => {
|
|
70
|
+
set({ currentFileId: fileId });
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
updateFileContent: (fileId: string, content: string) => {
|
|
74
|
+
set((state) => {
|
|
75
|
+
const files = new Map(state.files);
|
|
76
|
+
const file = files.get(fileId);
|
|
77
|
+
if (file) {
|
|
78
|
+
files.set(fileId, {
|
|
79
|
+
...file,
|
|
80
|
+
content,
|
|
81
|
+
isDirty: true,
|
|
82
|
+
lastModified: Date.now(),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return { files };
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
renameFile: (fileId: string, newName: string) => {
|
|
90
|
+
set((state) => {
|
|
91
|
+
const files = new Map(state.files);
|
|
92
|
+
const file = files.get(fileId);
|
|
93
|
+
if (file) {
|
|
94
|
+
files.set(fileId, { ...file, name: newName });
|
|
95
|
+
}
|
|
96
|
+
return { files };
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
markFileSaved: (fileId: string) => {
|
|
101
|
+
set((state) => {
|
|
102
|
+
const files = new Map(state.files);
|
|
103
|
+
const file = files.get(fileId);
|
|
104
|
+
if (file) {
|
|
105
|
+
files.set(fileId, { ...file, isDirty: false });
|
|
106
|
+
}
|
|
107
|
+
return { files };
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
deleteFile: (fileId: string) => {
|
|
112
|
+
set((state) => {
|
|
113
|
+
const files = new Map(state.files);
|
|
114
|
+
files.delete(fileId);
|
|
115
|
+
|
|
116
|
+
// Si eliminamos el archivo actual, cambiar a otro
|
|
117
|
+
let newCurrentId = state.currentFileId;
|
|
118
|
+
if (newCurrentId === fileId) {
|
|
119
|
+
const remaining = Array.from(files.keys());
|
|
120
|
+
newCurrentId = remaining.length > 0 ? remaining[0] : '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
files,
|
|
125
|
+
currentFileId: newCurrentId,
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
closeFile: (fileId: string) => {
|
|
131
|
+
set((state) => {
|
|
132
|
+
if (state.currentFileId === fileId) {
|
|
133
|
+
const remaining = Array.from(state.files.keys()).filter(
|
|
134
|
+
(id) => id !== fileId
|
|
135
|
+
);
|
|
136
|
+
return {
|
|
137
|
+
currentFileId: remaining.length > 0 ? remaining[0] : '',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {};
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
setZoomLevel: (level: number) => {
|
|
145
|
+
set({ zoomLevel: Math.max(25, Math.min(200, level)) });
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
setPreviewMode: (mode: 'all-screens' | 'single-screen') => {
|
|
149
|
+
set({ previewMode: mode });
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
setSelectedScreen: (screenName: string | null) => {
|
|
153
|
+
set({ selectedScreen: screenName });
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
getCurrentFile: () => {
|
|
157
|
+
const state = get();
|
|
158
|
+
return state.files.get(state.currentFileId) || null;
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
{
|
|
162
|
+
name: 'wire-editor-store',
|
|
163
|
+
storage: {
|
|
164
|
+
getItem: (name: string) => {
|
|
165
|
+
const item = localStorage.getItem(name);
|
|
166
|
+
if (!item) return null;
|
|
167
|
+
try {
|
|
168
|
+
const parsed = JSON.parse(item);
|
|
169
|
+
return {
|
|
170
|
+
...parsed,
|
|
171
|
+
state: {
|
|
172
|
+
...parsed.state,
|
|
173
|
+
files: new Map(parsed.state.files || []),
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
} catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
setItem: (name: string, value: any) => {
|
|
181
|
+
localStorage.setItem(
|
|
182
|
+
name,
|
|
183
|
+
JSON.stringify({
|
|
184
|
+
...value,
|
|
185
|
+
state: {
|
|
186
|
+
...value.state,
|
|
187
|
+
files: Array.from(value.state.files || new Map()),
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
);
|
|
191
|
+
},
|
|
192
|
+
removeItem: (name: string) => localStorage.removeItem(name),
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
);
|
package/tailwind.config.js
CHANGED
package/vite.config.ts
CHANGED
|
@@ -3,8 +3,25 @@ import react from '@vitejs/plugin-react';
|
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
5
|
plugins: [react()],
|
|
6
|
+
css: {
|
|
7
|
+
postcss: './postcss.config.mjs',
|
|
8
|
+
},
|
|
6
9
|
server: {
|
|
7
10
|
port: 3000,
|
|
8
11
|
strictPort: false,
|
|
9
12
|
},
|
|
13
|
+
optimizeDeps: {
|
|
14
|
+
exclude: ['sharp'],
|
|
15
|
+
include: ['monaco-editor', 'zustand', 'zustand/middleware', 'lucide-react'],
|
|
16
|
+
},
|
|
17
|
+
build: {
|
|
18
|
+
rollupOptions: {
|
|
19
|
+
external: ['sharp'],
|
|
20
|
+
output: {
|
|
21
|
+
globals: {
|
|
22
|
+
sharp: 'sharp',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
10
27
|
});
|