@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.
Files changed (127) hide show
  1. package/.turbo/turbo-build.log +103 -7
  2. package/CHANGELOG.md +30 -3
  3. package/dist/assets/abap-DLDM7-KI.js +1 -0
  4. package/dist/assets/apex-DNDY2TF8.js +1 -0
  5. package/dist/assets/azcli-Y6nb8tq_.js +1 -0
  6. package/dist/assets/bat-BwHxbl9M.js +1 -0
  7. package/dist/assets/bicep-CFznDFnq.js +2 -0
  8. package/dist/assets/cameligo-Bf6VGUru.js +1 -0
  9. package/dist/assets/clojure-Dnu-v4kV.js +1 -0
  10. package/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  11. package/dist/assets/coffee-Bd8akH9Z.js +1 -0
  12. package/dist/assets/cpp-BbWJElDN.js +1 -0
  13. package/dist/assets/csharp-Co3qMtFm.js +1 -0
  14. package/dist/assets/csp-D-4FJmMZ.js +1 -0
  15. package/dist/assets/css-DdJfP1eB.js +3 -0
  16. package/dist/assets/css.worker-DBVD8oXr.js +93 -0
  17. package/dist/assets/cssMode-BgkzAyoH.js +1 -0
  18. package/dist/assets/cypher-cTPe9QuQ.js +1 -0
  19. package/dist/assets/dart-BOtBlQCF.js +1 -0
  20. package/dist/assets/dockerfile-BG73LgW2.js +1 -0
  21. package/dist/assets/ecl-BEgZUVRK.js +1 -0
  22. package/dist/assets/elixir-BkW5O-1t.js +1 -0
  23. package/dist/assets/flow9-BeJ5waoc.js +1 -0
  24. package/dist/assets/freemarker2-D05KrEgD.js +3 -0
  25. package/dist/assets/fsharp-PahG7c26.js +1 -0
  26. package/dist/assets/go-acbASCJo.js +1 -0
  27. package/dist/assets/graphql-BxJiqAUM.js +1 -0
  28. package/dist/assets/handlebars-CmNF6dIr.js +1 -0
  29. package/dist/assets/hcl-DtV1sZF8.js +1 -0
  30. package/dist/assets/html-DdFfMtqo.js +1 -0
  31. package/dist/assets/html.worker-CwpTb9lJ.js +470 -0
  32. package/dist/assets/htmlMode-BzENSv3x.js +1 -0
  33. package/dist/assets/index-C6yQ9VSx.js +1595 -0
  34. package/dist/assets/index-CmAJnnOw.css +1 -0
  35. package/dist/assets/ini-Kd9XrMLS.js +1 -0
  36. package/dist/assets/java-CXBNlu9o.js +1 -0
  37. package/dist/assets/javascript-BDq34vkg.js +1 -0
  38. package/dist/assets/json.worker-BoL8UZqY.js +58 -0
  39. package/dist/assets/jsonMode-xsVJWt4Q.js +7 -0
  40. package/dist/assets/julia-cl7-CwDS.js +1 -0
  41. package/dist/assets/kotlin-s7OhZKlX.js +1 -0
  42. package/dist/assets/less-9HpZscsL.js +2 -0
  43. package/dist/assets/lexon-OrD6JF1K.js +1 -0
  44. package/dist/assets/liquid-BKLduW-j.js +1 -0
  45. package/dist/assets/lspLanguageFeatures-DENz5XIL.js +4 -0
  46. package/dist/assets/lua-Cyyb5UIc.js +1 -0
  47. package/dist/assets/m3-B8OfTtLu.js +1 -0
  48. package/dist/assets/markdown-BFxVWTOG.js +1 -0
  49. package/dist/assets/mdx-Cpg3g8iv.js +1 -0
  50. package/dist/assets/mips-CiqrrVzr.js +1 -0
  51. package/dist/assets/msdax-DmeGPVcC.js +1 -0
  52. package/dist/assets/mysql-C_tMU-Nz.js +1 -0
  53. package/dist/assets/objective-c-BDtDVThU.js +1 -0
  54. package/dist/assets/pascal-vHIfCaH5.js +1 -0
  55. package/dist/assets/pascaligo-DtZ0uQbO.js +1 -0
  56. package/dist/assets/perl-Ub6l9XKa.js +1 -0
  57. package/dist/assets/pgsql-BlNEE0v7.js +1 -0
  58. package/dist/assets/php-BBUBE1dy.js +1 -0
  59. package/dist/assets/pla-DSh2-awV.js +1 -0
  60. package/dist/assets/postiats-CocnycG-.js +1 -0
  61. package/dist/assets/powerquery-tScXyioY.js +1 -0
  62. package/dist/assets/powershell-COWaemsV.js +1 -0
  63. package/dist/assets/protobuf-Brw8urJB.js +2 -0
  64. package/dist/assets/pug-8SOpv6rk.js +1 -0
  65. package/dist/assets/python-Ca2JvAvf.js +1 -0
  66. package/dist/assets/qsharp-Bw9ernYp.js +1 -0
  67. package/dist/assets/r-j7ic8hl3.js +1 -0
  68. package/dist/assets/razor-B_xld0Yq.js +1 -0
  69. package/dist/assets/redis-Bu5POkcn.js +1 -0
  70. package/dist/assets/redshift-Bs9aos_-.js +1 -0
  71. package/dist/assets/restructuredtext-CqXO7rUv.js +1 -0
  72. package/dist/assets/ruby-zBfavPgS.js +1 -0
  73. package/dist/assets/rust-BzKRNQWT.js +1 -0
  74. package/dist/assets/sb-BBc9UKZt.js +1 -0
  75. package/dist/assets/scala-D9hQfWCl.js +1 -0
  76. package/dist/assets/scheme-BPhDTwHR.js +1 -0
  77. package/dist/assets/scss-CBJaRo0y.js +3 -0
  78. package/dist/assets/shell-DiJ1NA_G.js +1 -0
  79. package/dist/assets/solidity-Db0IVjzk.js +1 -0
  80. package/dist/assets/sophia-CnS9iZB_.js +1 -0
  81. package/dist/assets/sparql-CJmd_6j2.js +1 -0
  82. package/dist/assets/sql-ClhHkBeG.js +1 -0
  83. package/dist/assets/st-CHwy0fLd.js +1 -0
  84. package/dist/assets/swift-CnmFD0ga.js +1 -0
  85. package/dist/assets/systemverilog-Bs9z6M-B.js +1 -0
  86. package/dist/assets/tcl-Dm6ycUr_.js +1 -0
  87. package/dist/assets/ts.worker-BH9nVgjN.js +67718 -0
  88. package/dist/assets/tsMode-BinQkqy9.js +11 -0
  89. package/dist/assets/twig-Csy3S7wG.js +1 -0
  90. package/dist/assets/typescript-DQO38ZbJ.js +1 -0
  91. package/dist/assets/typespec-Btyra-wh.js +1 -0
  92. package/dist/assets/vb-Db0cS2oM.js +1 -0
  93. package/dist/assets/wgsl-BTesnYfV.js +298 -0
  94. package/dist/assets/xml-DF1bgZg2.js +1 -0
  95. package/dist/assets/yaml-BiNWh9S_.js +1 -0
  96. package/dist/examples/admin-dashboard.wire +95 -0
  97. package/dist/examples/analytics-dashboard.wire +65 -0
  98. package/dist/examples/form-example.wire +50 -0
  99. package/dist/examples/simple-dashboard.wire +40 -0
  100. package/dist/examples/simple-multi-screen.wire +30 -0
  101. package/dist/index.html +2 -2
  102. package/package.json +5 -3
  103. package/postcss.config.mjs +3 -1
  104. package/public/examples/admin-dashboard.wire +95 -0
  105. package/public/examples/analytics-dashboard.wire +65 -0
  106. package/public/examples/form-example.wire +50 -0
  107. package/public/examples/simple-dashboard.wire +40 -0
  108. package/public/examples/simple-multi-screen.wire +30 -0
  109. package/src/App.tsx +3 -13
  110. package/src/components/MonacoEditorComponent.tsx +112 -0
  111. package/src/components/WireLiveEditor.tsx +729 -0
  112. package/src/components/WireLiveHeader.tsx +469 -0
  113. package/src/components/index.ts +5 -0
  114. package/src/hooks/useCanvasZoom.ts +137 -0
  115. package/src/hooks/useFileSystemAccess.ts +123 -0
  116. package/src/hooks/useWireParser.ts +222 -0
  117. package/src/index.css +1 -3
  118. package/src/main.tsx +7 -5
  119. package/src/monaco/wireLanguage.ts +370 -0
  120. package/src/store/editorStore.ts +196 -0
  121. package/src/store/index.ts +2 -0
  122. package/tailwind.config.js +2 -1
  123. package/vite.config.ts +17 -0
  124. package/dist/assets/index-CHiOjnNN.js +0 -9
  125. package/dist/assets/index-CUIy2zPc.css +0 -1
  126. package/src/App.js +0 -4
  127. 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
+ );
@@ -0,0 +1,2 @@
1
+ export { useEditorStore } from './editorStore';
2
+ export type { EditorFile, EditorState } from './editorStore';
@@ -1,4 +1,5 @@
1
- module.exports = {
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
2
3
  content: [
3
4
  './index.html',
4
5
  './src/**/*.{js,ts,jsx,tsx}',
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
  });