create-nativecore 0.1.1 → 0.2.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/README.md +6 -14
- package/bin/index.mjs +402 -431
- package/package.json +3 -2
- package/template/.env.example +28 -0
- package/template/.htmlhintrc +14 -0
- package/template/api/data/dashboard.json +11 -0
- package/template/api/data/users.json +18 -0
- package/template/api/mockApi.js +161 -0
- package/template/assets/icon.svg +13 -0
- package/template/assets/logo.svg +25 -0
- package/template/eslint.config.js +94 -0
- package/template/index.html +137 -0
- package/template/manifest.json +19 -0
- package/template/public/.well-known/security.txt +9 -0
- package/template/public/_headers +24 -0
- package/template/public/_redirects +14 -0
- package/template/public/assets/icon.svg +13 -0
- package/template/public/assets/logo.svg +25 -0
- package/template/public/manifest.json +19 -0
- package/template/public/robots.txt +13 -0
- package/template/public/sitemap.xml +27 -0
- package/template/scripts/build-for-bots.mjs +121 -0
- package/template/scripts/convert-to-ts.mjs +106 -0
- package/template/scripts/fix-encoding.mjs +38 -0
- package/template/scripts/fix-svg-paths.mjs +32 -0
- package/template/scripts/generate-cf-router.mjs +52 -0
- package/template/scripts/inject-dev-tools.mjs +41 -0
- package/template/scripts/inject-version.mjs +65 -0
- package/template/scripts/make-component.mjs +445 -0
- package/template/scripts/make-component.mjs.backup +432 -0
- package/template/scripts/make-controller.mjs +119 -0
- package/template/scripts/make-core-component.mjs +303 -0
- package/template/scripts/make-view.mjs +346 -0
- package/template/scripts/minify.mjs +71 -0
- package/template/scripts/prepare-static-assets.mjs +141 -0
- package/template/scripts/prompt-bot-build.mjs +223 -0
- package/template/scripts/remove-component.mjs +170 -0
- package/template/scripts/remove-core-component.mjs +156 -0
- package/template/scripts/remove-dev.mjs +13 -0
- package/template/scripts/remove-view.mjs +200 -0
- package/template/scripts/strip-dev-blocks.mjs +30 -0
- package/template/scripts/watch-compile.mjs +69 -0
- package/template/server.js +1066 -0
- package/template/src/app.ts +115 -0
- package/template/src/components/appRegistry.ts +8 -0
- package/template/src/components/core/app-footer.ts +27 -0
- package/template/src/components/core/app-header.ts +175 -0
- package/template/src/components/core/app-sidebar.ts +238 -0
- package/template/src/components/core/loading-spinner.ts +25 -0
- package/template/src/components/core/nc-a.ts +313 -0
- package/template/src/components/core/nc-accordion.ts +186 -0
- package/template/src/components/core/nc-alert.ts +153 -0
- package/template/src/components/core/nc-animation.ts +1150 -0
- package/template/src/components/core/nc-autocomplete.ts +271 -0
- package/template/src/components/core/nc-avatar-group.ts +113 -0
- package/template/src/components/core/nc-avatar.ts +148 -0
- package/template/src/components/core/nc-badge.ts +86 -0
- package/template/src/components/core/nc-bottom-nav.ts +214 -0
- package/template/src/components/core/nc-breadcrumb.ts +96 -0
- package/template/src/components/core/nc-button.ts +307 -0
- package/template/src/components/core/nc-card.ts +160 -0
- package/template/src/components/core/nc-checkbox.ts +282 -0
- package/template/src/components/core/nc-chip.ts +115 -0
- package/template/src/components/core/nc-code.ts +314 -0
- package/template/src/components/core/nc-collapsible.ts +154 -0
- package/template/src/components/core/nc-color-picker.ts +268 -0
- package/template/src/components/core/nc-copy-button.ts +119 -0
- package/template/src/components/core/nc-date-picker.ts +443 -0
- package/template/src/components/core/nc-div.ts +280 -0
- package/template/src/components/core/nc-divider.ts +81 -0
- package/template/src/components/core/nc-drawer.ts +230 -0
- package/template/src/components/core/nc-dropdown.ts +178 -0
- package/template/src/components/core/nc-empty-state.ts +134 -0
- package/template/src/components/core/nc-file-upload.ts +354 -0
- package/template/src/components/core/nc-form.ts +312 -0
- package/template/src/components/core/nc-image.ts +184 -0
- package/template/src/components/core/nc-input.ts +383 -0
- package/template/src/components/core/nc-kbd.ts +48 -0
- package/template/src/components/core/nc-menu-item.ts +193 -0
- package/template/src/components/core/nc-menu.ts +376 -0
- package/template/src/components/core/nc-modal.ts +238 -0
- package/template/src/components/core/nc-nav-item.ts +151 -0
- package/template/src/components/core/nc-number-input.ts +350 -0
- package/template/src/components/core/nc-otp-input.ts +235 -0
- package/template/src/components/core/nc-pagination.ts +178 -0
- package/template/src/components/core/nc-popover.ts +260 -0
- package/template/src/components/core/nc-progress-circular.ts +119 -0
- package/template/src/components/core/nc-progress.ts +134 -0
- package/template/src/components/core/nc-radio.ts +235 -0
- package/template/src/components/core/nc-rating.ts +266 -0
- package/template/src/components/core/nc-rich-text.ts +283 -0
- package/template/src/components/core/nc-scroll-top.ts +116 -0
- package/template/src/components/core/nc-select.ts +452 -0
- package/template/src/components/core/nc-skeleton.ts +107 -0
- package/template/src/components/core/nc-slider.ts +285 -0
- package/template/src/components/core/nc-snackbar.ts +230 -0
- package/template/src/components/core/nc-splash.ts +343 -0
- package/template/src/components/core/nc-stepper.ts +247 -0
- package/template/src/components/core/nc-switch.ts +281 -0
- package/template/src/components/core/nc-tab-item.ts +138 -0
- package/template/src/components/core/nc-table.ts +279 -0
- package/template/src/components/core/nc-tabs.ts +554 -0
- package/template/src/components/core/nc-tag-input.ts +279 -0
- package/template/src/components/core/nc-textarea.ts +216 -0
- package/template/src/components/core/nc-time-picker.ts +438 -0
- package/template/src/components/core/nc-timeline.ts +186 -0
- package/template/src/components/core/nc-tooltip.ts +143 -0
- package/template/src/components/frameworkRegistry.ts +68 -0
- package/template/src/components/preloadRegistry.ts +28 -0
- package/template/src/components/registry.ts +8 -0
- package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
- package/template/src/constants/apiEndpoints.ts +27 -0
- package/template/src/constants/errorMessages.ts +23 -0
- package/template/src/constants/index.ts +8 -0
- package/template/src/constants/routePaths.ts +15 -0
- package/template/src/constants/storageKeys.ts +18 -0
- package/template/src/controllers/dashboard.controller.ts +200 -0
- package/template/src/controllers/home.controller.ts +21 -0
- package/template/src/controllers/index.ts +11 -0
- package/template/src/controllers/login.controller.ts +131 -0
- package/template/src/core/component.ts +354 -0
- package/template/src/core/errorHandler.ts +85 -0
- package/template/src/core/gpu-animation.ts +604 -0
- package/template/src/core/http.ts +173 -0
- package/template/src/core/lazyComponents.ts +90 -0
- package/template/src/core/router.ts +642 -0
- package/template/src/core/signals.ts +146 -0
- package/template/src/core/state.ts +248 -0
- package/template/src/dev/component-editor.ts +1363 -0
- package/template/src/dev/component-overlay.ts +278 -0
- package/template/src/dev/context-menu.ts +223 -0
- package/template/src/dev/denc-tools.ts +250 -0
- package/template/src/dev/hmr.ts +189 -0
- package/template/src/dev/nfbs.code-workspace +27 -0
- package/template/src/dev/outline-panel.ts +1247 -0
- package/template/src/middleware/auth.middleware.ts +23 -0
- package/template/src/routes/routes.ts +38 -0
- package/template/src/services/api.service.ts +394 -0
- package/template/src/services/auth.service.ts +176 -0
- package/template/src/services/index.ts +8 -0
- package/template/src/services/logger.service.ts +74 -0
- package/template/src/services/storage.service.ts +88 -0
- package/template/src/stores/appStore.ts +57 -0
- package/template/src/stores/uiStore.ts +36 -0
- package/template/src/styles/core-variables.css +219 -0
- package/template/src/styles/core.css +710 -0
- package/template/src/styles/main.css +3164 -0
- package/template/src/styles/variables.css +152 -0
- package/template/src/types/global.d.ts +47 -0
- package/template/src/utils/cacheBuster.ts +20 -0
- package/template/src/utils/dom.ts +149 -0
- package/template/src/utils/events.ts +203 -0
- package/template/src/utils/form.ts +176 -0
- package/template/src/utils/formatters.ts +169 -0
- package/template/src/utils/helpers.ts +195 -0
- package/template/src/utils/markdown.ts +307 -0
- package/template/src/utils/sidebar.ts +96 -0
- package/template/src/utils/smoothScroll.ts +85 -0
- package/template/src/utils/templates.ts +23 -0
- package/template/src/utils/validation.ts +73 -0
- package/template/src/views/protected/dashboard.html +293 -0
- package/template/src/views/public/home.html +150 -0
- package/template/src/views/public/login.html +102 -0
- package/template/tests/unit/component.test.ts +87 -0
- package/template/tests/unit/computed.test.ts +79 -0
- package/template/tests/unit/form.test.ts +68 -0
- package/template/tests/unit/formatters.test.ts +49 -0
- package/template/tests/unit/lazy-components.test.ts +59 -0
- package/template/tests/unit/markdown.test.ts +62 -0
- package/template/tests/unit/router.test.ts +112 -0
- package/template/tests/unit/signals.test.ts +54 -0
- package/template/tests/unit/validation.test.ts +50 -0
- package/template/tsconfig.build.json +21 -0
- package/template/tsconfig.json +51 -0
- package/template/vitest.config.ts +36 -0
|
@@ -0,0 +1,1363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Editor Drawer
|
|
3
|
+
*
|
|
4
|
+
* Right-side sliding drawer that dynamically displays
|
|
5
|
+
* only the properties that exist in the component's source.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ComponentMetadata } from './denc-tools.js';
|
|
9
|
+
import dom from '../utils/dom.js';
|
|
10
|
+
|
|
11
|
+
interface ExtendedMetadata extends ComponentMetadata {
|
|
12
|
+
hostStyles?: { property: string; value: string }[];
|
|
13
|
+
inlineStyles?: { property: string; value: string }[];
|
|
14
|
+
usesShadowDOM?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ComponentEditor {
|
|
18
|
+
private drawer: HTMLElement | null = null;
|
|
19
|
+
private currentElement: HTMLElement | null = null;
|
|
20
|
+
private currentMetadata: ExtendedMetadata | null = null;
|
|
21
|
+
private originalStyles: Map<string, string> = new Map();
|
|
22
|
+
private originalAttributes: Map<string, string> = new Map();
|
|
23
|
+
private componentSnapshot: any = null;
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
this.injectStyles();
|
|
27
|
+
this.createDrawer();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private injectStyles(): void {
|
|
31
|
+
const styleId = 'nativecore-editor-styles';
|
|
32
|
+
if ((window.dom?.query?.(`#${styleId}`) || document.getElementById(styleId))) return;
|
|
33
|
+
|
|
34
|
+
const style = document.createElement('style');
|
|
35
|
+
style.id = styleId;
|
|
36
|
+
style.textContent = `
|
|
37
|
+
.nc-editor-overlay {
|
|
38
|
+
position: fixed;
|
|
39
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
40
|
+
z-index: 1000000;
|
|
41
|
+
background: rgba(0, 0, 0, 0.15);
|
|
42
|
+
opacity: 0;
|
|
43
|
+
pointer-events: none;
|
|
44
|
+
transition: opacity 0.3s ease;
|
|
45
|
+
}
|
|
46
|
+
.nc-editor-overlay.active {
|
|
47
|
+
opacity: 1;
|
|
48
|
+
visibility: visible;
|
|
49
|
+
}
|
|
50
|
+
.nc-editor-drawer {
|
|
51
|
+
position: fixed;
|
|
52
|
+
top: 0;
|
|
53
|
+
right: 0;
|
|
54
|
+
width: 400px;
|
|
55
|
+
height: 100vh;
|
|
56
|
+
background: #1e1e2e;
|
|
57
|
+
box-shadow: -4px 0 24px rgba(0,0,0,0.3);
|
|
58
|
+
display: flex;
|
|
59
|
+
flex-direction: column;
|
|
60
|
+
color: #cdd6f4;
|
|
61
|
+
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
62
|
+
font-size: 11px;
|
|
63
|
+
transform: translateX(100%);
|
|
64
|
+
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
65
|
+
pointer-events: auto;
|
|
66
|
+
}
|
|
67
|
+
.nc-editor-overlay.active .nc-editor-drawer {
|
|
68
|
+
transform: translateX(0);
|
|
69
|
+
}
|
|
70
|
+
.nc-editor-header {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: space-between;
|
|
74
|
+
padding: 16px 20px;
|
|
75
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
76
|
+
color: white;
|
|
77
|
+
flex-shrink: 0;
|
|
78
|
+
gap: 8px;
|
|
79
|
+
}
|
|
80
|
+
.nc-editor-title {
|
|
81
|
+
font-size: 14px;
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
flex: 1;
|
|
84
|
+
}
|
|
85
|
+
.nc-editor-file-path {
|
|
86
|
+
font-size: 10px;
|
|
87
|
+
font-weight: 400;
|
|
88
|
+
color: rgba(255, 255, 255, 0.7);
|
|
89
|
+
font-family: 'Fira Code', monospace;
|
|
90
|
+
margin-top: 4px;
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 6px;
|
|
94
|
+
}
|
|
95
|
+
.nc-editor-file-path a {
|
|
96
|
+
color: rgba(255, 255, 255, 0.9);
|
|
97
|
+
text-decoration: none;
|
|
98
|
+
transition: color 0.2s;
|
|
99
|
+
}
|
|
100
|
+
.nc-editor-file-path a:hover {
|
|
101
|
+
color: white;
|
|
102
|
+
text-decoration: underline;
|
|
103
|
+
}
|
|
104
|
+
.nc-editor-header-actions {
|
|
105
|
+
display: flex;
|
|
106
|
+
gap: 6px;
|
|
107
|
+
}
|
|
108
|
+
.nc-editor-icon-btn {
|
|
109
|
+
background: rgba(255, 255, 255, 0.2);
|
|
110
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
111
|
+
color: white;
|
|
112
|
+
width: 28px;
|
|
113
|
+
height: 28px;
|
|
114
|
+
border-radius: 4px;
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
font-size: 14px;
|
|
120
|
+
transition: all 0.2s;
|
|
121
|
+
}
|
|
122
|
+
.nc-editor-icon-btn:hover {
|
|
123
|
+
background: rgba(255, 255, 255, 0.3);
|
|
124
|
+
}
|
|
125
|
+
.nc-editor-btn-danger {
|
|
126
|
+
background: rgba(239, 68, 68, 0.2) !important;
|
|
127
|
+
border-color: rgba(239, 68, 68, 0.4) !important;
|
|
128
|
+
}
|
|
129
|
+
.nc-editor-btn-danger:hover {
|
|
130
|
+
background: rgba(239, 68, 68, 0.4) !important;
|
|
131
|
+
}
|
|
132
|
+
.nc-editor-btn-danger:disabled {
|
|
133
|
+
opacity: 0.3;
|
|
134
|
+
cursor: not-allowed;
|
|
135
|
+
}
|
|
136
|
+
.nc-editor-title code {
|
|
137
|
+
background: rgba(255,255,255,0.2);
|
|
138
|
+
padding: 3px 8px;
|
|
139
|
+
border-radius: 4px;
|
|
140
|
+
font-size: 12px;
|
|
141
|
+
margin-left: 6px;
|
|
142
|
+
}
|
|
143
|
+
.nc-editor-mode-selector {
|
|
144
|
+
display: flex;
|
|
145
|
+
flex-direction: column;
|
|
146
|
+
gap: 4px;
|
|
147
|
+
padding: 16px 20px 12px 20px;
|
|
148
|
+
background: #181825;
|
|
149
|
+
border-bottom: 1px solid #313244;
|
|
150
|
+
}
|
|
151
|
+
.nc-editor-mode-label {
|
|
152
|
+
font-size: 10px;
|
|
153
|
+
color: #cdd6f4;
|
|
154
|
+
font-weight: 600;
|
|
155
|
+
text-transform: uppercase;
|
|
156
|
+
letter-spacing: 0.5px;
|
|
157
|
+
}
|
|
158
|
+
.nc-editor-mode-select {
|
|
159
|
+
background: #313244;
|
|
160
|
+
border: 1px solid #45475a;
|
|
161
|
+
border-radius: 6px;
|
|
162
|
+
padding: 8px 10px;
|
|
163
|
+
color: #cdd6f4;
|
|
164
|
+
font-size: 11px;
|
|
165
|
+
cursor: pointer;
|
|
166
|
+
outline: none;
|
|
167
|
+
}
|
|
168
|
+
.nc-editor-mode-select:focus {
|
|
169
|
+
border-color: #89b4fa;
|
|
170
|
+
}
|
|
171
|
+
.nc-editor-mode-description {
|
|
172
|
+
font-size: 10px;
|
|
173
|
+
color: #cdd6f4;
|
|
174
|
+
line-height: 1.4;
|
|
175
|
+
margin-top: 4px;
|
|
176
|
+
}
|
|
177
|
+
.nc-editor-close {
|
|
178
|
+
background: rgba(255,255,255,0.2);
|
|
179
|
+
border: none;
|
|
180
|
+
color: white;
|
|
181
|
+
width: 28px;
|
|
182
|
+
height: 28px;
|
|
183
|
+
border-radius: 6px;
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
display: flex;
|
|
186
|
+
align-items: center;
|
|
187
|
+
justify-content: center;
|
|
188
|
+
font-size: 18px;
|
|
189
|
+
font-weight: 300;
|
|
190
|
+
transition: background 0.2s;
|
|
191
|
+
}
|
|
192
|
+
.nc-editor-close:hover { background: rgba(255,255,255,0.3); }
|
|
193
|
+
.nc-editor-body {
|
|
194
|
+
flex: 1;
|
|
195
|
+
overflow-y: auto;
|
|
196
|
+
padding: 20px;
|
|
197
|
+
}
|
|
198
|
+
.nc-editor-body::-webkit-scrollbar {
|
|
199
|
+
width: 8px;
|
|
200
|
+
}
|
|
201
|
+
.nc-editor-body::-webkit-scrollbar-track {
|
|
202
|
+
background: #181825;
|
|
203
|
+
}
|
|
204
|
+
.nc-editor-body::-webkit-scrollbar-thumb {
|
|
205
|
+
background: #45475a;
|
|
206
|
+
border-radius: 4px;
|
|
207
|
+
}
|
|
208
|
+
.nc-editor-body::-webkit-scrollbar-thumb:hover {
|
|
209
|
+
background: #585b70;
|
|
210
|
+
}
|
|
211
|
+
.nc-editor-empty {
|
|
212
|
+
text-align: center;
|
|
213
|
+
padding: 40px 20px;
|
|
214
|
+
color: #6c7086;
|
|
215
|
+
}
|
|
216
|
+
.nc-editor-section {
|
|
217
|
+
margin-bottom: 16px;
|
|
218
|
+
background: #181825;
|
|
219
|
+
border-radius: 8px;
|
|
220
|
+
overflow: hidden;
|
|
221
|
+
border: 1px solid #313244;
|
|
222
|
+
}
|
|
223
|
+
.nc-editor-section-header {
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
justify-content: space-between;
|
|
227
|
+
padding: 12px 16px;
|
|
228
|
+
cursor: pointer;
|
|
229
|
+
user-select: none;
|
|
230
|
+
}
|
|
231
|
+
.nc-editor-section-header:hover { background: rgba(255,255,255,0.03); }
|
|
232
|
+
.nc-editor-section-title {
|
|
233
|
+
font-size: 11px;
|
|
234
|
+
font-weight: 600;
|
|
235
|
+
text-transform: uppercase;
|
|
236
|
+
letter-spacing: 0.5px;
|
|
237
|
+
color: #a6adc8;
|
|
238
|
+
}
|
|
239
|
+
.nc-editor-section-count {
|
|
240
|
+
font-size: 10px;
|
|
241
|
+
background: #313244;
|
|
242
|
+
padding: 2px 8px;
|
|
243
|
+
border-radius: 10px;
|
|
244
|
+
color: #6c7086;
|
|
245
|
+
}
|
|
246
|
+
.nc-editor-section-toggle {
|
|
247
|
+
width: 14px;
|
|
248
|
+
height: 14px;
|
|
249
|
+
fill: #6c7086;
|
|
250
|
+
transition: transform 0.2s;
|
|
251
|
+
}
|
|
252
|
+
.nc-editor-section.collapsed .nc-editor-section-toggle { transform: rotate(-90deg); }
|
|
253
|
+
.nc-editor-section-content { padding: 12px 16px 16px; }
|
|
254
|
+
.nc-editor-section.collapsed .nc-editor-section-content { display: none; }
|
|
255
|
+
.nc-editor-field {
|
|
256
|
+
display: flex;
|
|
257
|
+
flex-direction: column;
|
|
258
|
+
gap: 6px;
|
|
259
|
+
margin-bottom: 12px;
|
|
260
|
+
}
|
|
261
|
+
.nc-editor-field:last-child { margin-bottom: 0; }
|
|
262
|
+
.nc-editor-label {
|
|
263
|
+
font-size: 10px;
|
|
264
|
+
color: #bac2de;
|
|
265
|
+
font-weight: 500;
|
|
266
|
+
font-family: 'Fira Code', monospace;
|
|
267
|
+
}
|
|
268
|
+
.nc-editor-input {
|
|
269
|
+
background: #313244;
|
|
270
|
+
border: 1px solid #45475a;
|
|
271
|
+
border: 1px solid #45475a;
|
|
272
|
+
border-radius: 6px;
|
|
273
|
+
padding: 8px 10px;
|
|
274
|
+
color: #cdd6f4;
|
|
275
|
+
font-size: 11px;
|
|
276
|
+
width: 100%;
|
|
277
|
+
}
|
|
278
|
+
.nc-editor-input:focus {
|
|
279
|
+
outline: none;
|
|
280
|
+
border-color: #89b4fa;
|
|
281
|
+
background: #3a3d52;
|
|
282
|
+
}
|
|
283
|
+
.nc-editor-select {
|
|
284
|
+
cursor: pointer;
|
|
285
|
+
}
|
|
286
|
+
.nc-editor-select option {
|
|
287
|
+
background: #313244;
|
|
288
|
+
color: #cdd6f4;
|
|
289
|
+
}
|
|
290
|
+
.nc-editor-slider-group {
|
|
291
|
+
display: flex;
|
|
292
|
+
flex-direction: column;
|
|
293
|
+
gap: 4px;
|
|
294
|
+
}
|
|
295
|
+
.nc-editor-slider-wrapper {
|
|
296
|
+
display: flex;
|
|
297
|
+
gap: 8px;
|
|
298
|
+
align-items: center;
|
|
299
|
+
}
|
|
300
|
+
.nc-editor-slider {
|
|
301
|
+
flex: 1;
|
|
302
|
+
}
|
|
303
|
+
.nc-editor-slider-value {
|
|
304
|
+
background: #313244;
|
|
305
|
+
border: 1px solid #45475a;
|
|
306
|
+
border-radius: 4px;
|
|
307
|
+
padding: 4px 8px;
|
|
308
|
+
color: #cdd6f4;
|
|
309
|
+
font-size: 11px;
|
|
310
|
+
min-width: 50px;
|
|
311
|
+
text-align: center;
|
|
312
|
+
}
|
|
313
|
+
.nc-editor-color-wrap {
|
|
314
|
+
display: flex;
|
|
315
|
+
gap: 8px;
|
|
316
|
+
align-items: center;
|
|
317
|
+
}
|
|
318
|
+
.nc-editor-color-wrap input[type="color"] {
|
|
319
|
+
width: 40px;
|
|
320
|
+
height: 36px;
|
|
321
|
+
border: 1px solid #45475a;
|
|
322
|
+
border-radius: 6px;
|
|
323
|
+
cursor: pointer;
|
|
324
|
+
padding: 0;
|
|
325
|
+
}
|
|
326
|
+
.nc-editor-footer {
|
|
327
|
+
display: flex;
|
|
328
|
+
justify-content: flex-end;
|
|
329
|
+
padding: 16px 20px;
|
|
330
|
+
background: #181825;
|
|
331
|
+
border-top: 1px solid #313244;
|
|
332
|
+
gap: 10px;
|
|
333
|
+
flex-shrink: 0;
|
|
334
|
+
}
|
|
335
|
+
.nc-editor-btn {
|
|
336
|
+
padding: 8px 16px;
|
|
337
|
+
border-radius: 6px;
|
|
338
|
+
font-size: 11px;
|
|
339
|
+
font-weight: 600;
|
|
340
|
+
cursor: pointer;
|
|
341
|
+
border: none;
|
|
342
|
+
transition: all 0.2s;
|
|
343
|
+
}
|
|
344
|
+
.nc-editor-btn-secondary {
|
|
345
|
+
background: #313244;
|
|
346
|
+
color: #cdd6f4;
|
|
347
|
+
}
|
|
348
|
+
.nc-editor-btn-secondary:hover { background: #45475a; }
|
|
349
|
+
.nc-editor-footer {
|
|
350
|
+
position: sticky;
|
|
351
|
+
bottom: 0;
|
|
352
|
+
left: 0;
|
|
353
|
+
right: 0;
|
|
354
|
+
display: flex;
|
|
355
|
+
justify-content: flex-end;
|
|
356
|
+
gap: 8px;
|
|
357
|
+
padding: 16px 20px;
|
|
358
|
+
background: #1e1e2e;
|
|
359
|
+
border-top: 1px solid #313244;
|
|
360
|
+
flex-shrink: 0;
|
|
361
|
+
z-index: 10;
|
|
362
|
+
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.2);
|
|
363
|
+
}
|
|
364
|
+
.nc-editor-btn {
|
|
365
|
+
padding: 8px 16px;
|
|
366
|
+
border-radius: 6px;
|
|
367
|
+
font-size: 11px;
|
|
368
|
+
font-weight: 600;
|
|
369
|
+
cursor: pointer;
|
|
370
|
+
border: none;
|
|
371
|
+
transition: all 0.2s;
|
|
372
|
+
}
|
|
373
|
+
.nc-editor-btn-primary {
|
|
374
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
375
|
+
color: white;
|
|
376
|
+
}
|
|
377
|
+
.nc-editor-btn-primary:hover {
|
|
378
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
379
|
+
transform: translateY(-1px);
|
|
380
|
+
}
|
|
381
|
+
input[type="range"] {
|
|
382
|
+
height: 6px;
|
|
383
|
+
-webkit-appearance: none;
|
|
384
|
+
background: #45475a;
|
|
385
|
+
border-radius: 3px;
|
|
386
|
+
width: 100%;
|
|
387
|
+
}
|
|
388
|
+
input[type="range"]::-webkit-slider-thumb {
|
|
389
|
+
-webkit-appearance: none;
|
|
390
|
+
width: 16px;
|
|
391
|
+
height: 16px;
|
|
392
|
+
background: #667eea;
|
|
393
|
+
border-radius: 50%;
|
|
394
|
+
cursor: pointer;
|
|
395
|
+
}
|
|
396
|
+
.nc-editor-checkbox {
|
|
397
|
+
width: 18px;
|
|
398
|
+
height: 18px;
|
|
399
|
+
accent-color: #667eea;
|
|
400
|
+
cursor: pointer;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* Confirmation Modal */
|
|
404
|
+
.nc-editor-modal {
|
|
405
|
+
position: fixed;
|
|
406
|
+
inset: 0;
|
|
407
|
+
background: rgba(0, 0, 0, 0.8);
|
|
408
|
+
backdrop-filter: blur(4px);
|
|
409
|
+
display: none;
|
|
410
|
+
align-items: center;
|
|
411
|
+
justify-content: center;
|
|
412
|
+
z-index: 1000002;
|
|
413
|
+
animation: fadeIn 0.15s ease;
|
|
414
|
+
}
|
|
415
|
+
.nc-editor-modal.active {
|
|
416
|
+
display: flex;
|
|
417
|
+
}
|
|
418
|
+
@keyframes fadeIn {
|
|
419
|
+
from { opacity: 0; }
|
|
420
|
+
to { opacity: 1; }
|
|
421
|
+
}
|
|
422
|
+
.nc-editor-modal-content {
|
|
423
|
+
background: #1e1e2e;
|
|
424
|
+
border-radius: 12px;
|
|
425
|
+
width: 90%;
|
|
426
|
+
max-width: 420px;
|
|
427
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
|
428
|
+
border: 1px solid #313244;
|
|
429
|
+
animation: slideUp 0.2s ease;
|
|
430
|
+
}
|
|
431
|
+
@keyframes slideUp {
|
|
432
|
+
from { transform: translateY(20px); opacity: 0; }
|
|
433
|
+
to { transform: translateY(0); opacity: 1; }
|
|
434
|
+
}
|
|
435
|
+
.nc-editor-modal-header {
|
|
436
|
+
padding: 20px;
|
|
437
|
+
border-radius: 12px 12px 0 0;
|
|
438
|
+
display: flex;
|
|
439
|
+
align-items: center;
|
|
440
|
+
gap: 12px;
|
|
441
|
+
}
|
|
442
|
+
.nc-editor-modal-icon {
|
|
443
|
+
width: 32px;
|
|
444
|
+
height: 32px;
|
|
445
|
+
background: rgba(255, 255, 255, 0.2);
|
|
446
|
+
border-radius: 50%;
|
|
447
|
+
display: flex;
|
|
448
|
+
align-items: center;
|
|
449
|
+
justify-content: center;
|
|
450
|
+
font-size: 18px;
|
|
451
|
+
}
|
|
452
|
+
.nc-editor-modal-title {
|
|
453
|
+
font-size: 16px;
|
|
454
|
+
font-weight: 600;
|
|
455
|
+
color: white;
|
|
456
|
+
}
|
|
457
|
+
.nc-editor-modal-body {
|
|
458
|
+
padding: 24px;
|
|
459
|
+
color: #cdd6f4;
|
|
460
|
+
line-height: 1.6;
|
|
461
|
+
}
|
|
462
|
+
.nc-editor-modal-component {
|
|
463
|
+
font-family: 'Fira Code', monospace;
|
|
464
|
+
background: #181825;
|
|
465
|
+
padding: 8px 12px;
|
|
466
|
+
border-radius: 6px;
|
|
467
|
+
color: #89b4fa;
|
|
468
|
+
margin: 12px 0;
|
|
469
|
+
border: 1px solid #313244;
|
|
470
|
+
}
|
|
471
|
+
.nc-editor-modal-footer {
|
|
472
|
+
padding: 16px 24px;
|
|
473
|
+
display: flex;
|
|
474
|
+
gap: 12px;
|
|
475
|
+
justify-content: flex-end;
|
|
476
|
+
border-top: 1px solid #313244;
|
|
477
|
+
}
|
|
478
|
+
.nc-editor-modal-btn {
|
|
479
|
+
padding: 10px 20px;
|
|
480
|
+
border-radius: 8px;
|
|
481
|
+
font-size: 13px;
|
|
482
|
+
font-weight: 600;
|
|
483
|
+
cursor: pointer;
|
|
484
|
+
border: none;
|
|
485
|
+
transition: all 0.2s;
|
|
486
|
+
}
|
|
487
|
+
.nc-editor-modal-btn-cancel {
|
|
488
|
+
background: #313244;
|
|
489
|
+
color: #cdd6f4;
|
|
490
|
+
}
|
|
491
|
+
.nc-editor-modal-btn-cancel:hover {
|
|
492
|
+
background: #45475a;
|
|
493
|
+
}
|
|
494
|
+
.nc-editor-modal-btn-delete {
|
|
495
|
+
background: linear-gradient(135deg, #dc2626 0%, #ef4444 100%);
|
|
496
|
+
color: white;
|
|
497
|
+
}
|
|
498
|
+
.nc-editor-modal-btn-delete:hover {
|
|
499
|
+
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4);
|
|
500
|
+
transform: translateY(-1px);
|
|
501
|
+
}
|
|
502
|
+
`;
|
|
503
|
+
document.head.appendChild(style);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private createDrawer(): void {
|
|
507
|
+
this.drawer = document.createElement('div');
|
|
508
|
+
this.drawer.className = 'nc-editor-overlay';
|
|
509
|
+
this.drawer.innerHTML = `
|
|
510
|
+
<div class="nc-editor-drawer" id="ncEditorDrawer">
|
|
511
|
+
<div class="nc-editor-header">
|
|
512
|
+
<div style="flex: 1;">
|
|
513
|
+
<div class="nc-editor-title">Edit Component<code id="ncEditorTagName"></code></div>
|
|
514
|
+
<div class="nc-editor-file-path" id="ncEditorFilePath"></div>
|
|
515
|
+
</div>
|
|
516
|
+
<div class="nc-editor-header-actions">
|
|
517
|
+
<button class="nc-editor-icon-btn" id="ncEditorCopy" title="Copy Component Values">📋</button>
|
|
518
|
+
<button class="nc-editor-icon-btn" id="ncEditorPaste" title="Paste Component Values">📄</button>
|
|
519
|
+
<button class="nc-editor-icon-btn" id="ncEditorReset" title="Reset to Default">↺</button>
|
|
520
|
+
<button class="nc-editor-icon-btn nc-editor-btn-danger" id="ncEditorDelete" title="Delete Instance">🗑️</button>
|
|
521
|
+
<button class="nc-editor-icon-btn" id="ncEditorCloseBtn" title="Close" style="margin-left: 8px;">✕</button>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
<div class="nc-editor-body" id="ncEditorContent"></div>
|
|
525
|
+
<div class="nc-editor-footer">
|
|
526
|
+
<button class="nc-editor-btn nc-editor-btn-secondary" id="ncEditorClose">Close</button>
|
|
527
|
+
<button class="nc-editor-btn nc-editor-btn-primary" id="ncEditorSave">Apply Changes</button>
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
`;
|
|
531
|
+
|
|
532
|
+
this.drawer.querySelector('#ncEditorClose')?.addEventListener('click', () => this.close());
|
|
533
|
+
this.drawer.querySelector('#ncEditorCloseBtn')?.addEventListener('click', () => this.close());
|
|
534
|
+
this.drawer.querySelector('#ncEditorSave')?.addEventListener('click', () => this.save());
|
|
535
|
+
this.drawer.querySelector('#ncEditorCopy')?.addEventListener('click', () => this.copyValues());
|
|
536
|
+
this.drawer.querySelector('#ncEditorPaste')?.addEventListener('click', () => this.pasteValues());
|
|
537
|
+
this.drawer.querySelector('#ncEditorReset')?.addEventListener('click', () => this.resetToDefault());
|
|
538
|
+
this.drawer.querySelector('#ncEditorDelete')?.addEventListener('click', () => this.deleteInstance());
|
|
539
|
+
document.addEventListener('keydown', (e) => {
|
|
540
|
+
if (e.key === 'Escape' && this.drawer?.classList.contains('active')) this.close();
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// Listen for close editor event from outline panel
|
|
544
|
+
document.addEventListener('nc-close-editor', () => {
|
|
545
|
+
if (this.drawer?.classList.contains('active')) {
|
|
546
|
+
this.close();
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Listen for requests for current element from outline panel
|
|
551
|
+
document.addEventListener('nc-request-current-element', () => {
|
|
552
|
+
if (this.currentElement) {
|
|
553
|
+
document.dispatchEvent(new CustomEvent('nc-current-element-response', {
|
|
554
|
+
detail: { element: this.currentElement }
|
|
555
|
+
}));
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
document.body.appendChild(this.drawer);
|
|
560
|
+
|
|
561
|
+
// Create confirmation modal
|
|
562
|
+
this.createConfirmModal();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private createConfirmModal(): void {
|
|
566
|
+
const modal = document.createElement('div');
|
|
567
|
+
modal.className = 'nc-editor-modal';
|
|
568
|
+
modal.id = 'ncEditorModal';
|
|
569
|
+
modal.innerHTML = `
|
|
570
|
+
<div class="nc-editor-modal-content">
|
|
571
|
+
<div class="nc-editor-modal-header">
|
|
572
|
+
<div class="nc-editor-modal-icon">🗑️</div>
|
|
573
|
+
<div class="nc-editor-modal-title">Delete Component Instance</div>
|
|
574
|
+
</div>
|
|
575
|
+
<div class="nc-editor-modal-body">
|
|
576
|
+
<p>Are you sure you want to delete this component instance?</p>
|
|
577
|
+
<div class="nc-editor-modal-component" id="ncModalComponentName"></div>
|
|
578
|
+
<p style="color: #ef4444; font-size: 12px; margin-top: 16px;">
|
|
579
|
+
⚠️ This will permanently remove it from the HTML file. This action cannot be undone.
|
|
580
|
+
</p>
|
|
581
|
+
</div>
|
|
582
|
+
<div class="nc-editor-modal-footer">
|
|
583
|
+
<button class="nc-editor-modal-btn nc-editor-modal-btn-cancel" id="ncModalCancel">Cancel</button>
|
|
584
|
+
<button class="nc-editor-modal-btn nc-editor-modal-btn-delete" id="ncModalConfirm">Delete</button>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
`;
|
|
588
|
+
|
|
589
|
+
modal.addEventListener('click', (e) => {
|
|
590
|
+
if (e.target === modal) {
|
|
591
|
+
modal.classList.remove('active');
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
document.body.appendChild(modal);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
private showConfirmModal(componentName: string): Promise<boolean> {
|
|
599
|
+
return new Promise((resolve) => {
|
|
600
|
+
const modal = document.getElementById('ncEditorModal');
|
|
601
|
+
const nameEl = document.getElementById('ncModalComponentName');
|
|
602
|
+
const cancelBtn = document.getElementById('ncModalCancel');
|
|
603
|
+
const confirmBtn = document.getElementById('ncModalConfirm');
|
|
604
|
+
|
|
605
|
+
if (!modal || !nameEl || !cancelBtn || !confirmBtn) {
|
|
606
|
+
resolve(false);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
nameEl.textContent = `<${componentName}>`;
|
|
611
|
+
modal.classList.add('active');
|
|
612
|
+
|
|
613
|
+
const cleanup = () => {
|
|
614
|
+
modal.classList.remove('active');
|
|
615
|
+
cancelBtn.removeEventListener('click', handleCancel);
|
|
616
|
+
confirmBtn.removeEventListener('click', handleConfirm);
|
|
617
|
+
document.removeEventListener('keydown', handleEscape);
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const handleCancel = () => {
|
|
621
|
+
cleanup();
|
|
622
|
+
resolve(false);
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
const handleConfirm = () => {
|
|
626
|
+
cleanup();
|
|
627
|
+
resolve(true);
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
const handleEscape = (e: KeyboardEvent) => {
|
|
631
|
+
if (e.key === 'Escape') {
|
|
632
|
+
handleCancel();
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
cancelBtn.addEventListener('click', handleCancel);
|
|
637
|
+
confirmBtn.addEventListener('click', handleConfirm);
|
|
638
|
+
document.addEventListener('keydown', handleEscape);
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Copy component values to clipboard (Unity-like)
|
|
646
|
+
*/
|
|
647
|
+
private copyValues(): void {
|
|
648
|
+
if (!this.currentElement || !this.currentMetadata) return;
|
|
649
|
+
|
|
650
|
+
const snapshot: any = {
|
|
651
|
+
tagName: this.currentMetadata.tagName,
|
|
652
|
+
attributes: {},
|
|
653
|
+
styles: {}
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
// Copy attributes
|
|
657
|
+
this.currentMetadata.attributes?.forEach(attr => {
|
|
658
|
+
const value = this.currentElement!.getAttribute(attr.name);
|
|
659
|
+
if (value !== null) {
|
|
660
|
+
snapshot.attributes[attr.name] = value;
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// Copy inline styles
|
|
665
|
+
const styleProps = ['color', 'backgroundColor', 'padding', 'margin', 'borderRadius',
|
|
666
|
+
'borderWidth', 'borderColor', 'fontSize', 'width', 'height'];
|
|
667
|
+
styleProps.forEach(prop => {
|
|
668
|
+
const value = (this.currentElement!.style as any)[prop];
|
|
669
|
+
if (value) {
|
|
670
|
+
snapshot.styles[prop] = value;
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
this.componentSnapshot = snapshot;
|
|
675
|
+
this.showToast('Values copied to clipboard');
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Paste component values (Unity-like)
|
|
680
|
+
*/
|
|
681
|
+
private pasteValues(): void {
|
|
682
|
+
if (!this.componentSnapshot || !this.currentElement) {
|
|
683
|
+
this.showToast('No values to paste', 'warning');
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Apply attributes
|
|
688
|
+
for (const [key, value] of Object.entries(this.componentSnapshot.attributes || {})) {
|
|
689
|
+
this.currentElement.setAttribute(key, value as string);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Apply styles
|
|
693
|
+
for (const [key, value] of Object.entries(this.componentSnapshot.styles || {})) {
|
|
694
|
+
(this.currentElement.style as any)[key] = value;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
this.showToast('Values pasted successfully');
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Reset component to default values (Unity-like)
|
|
702
|
+
*/
|
|
703
|
+
private resetToDefault(): void {
|
|
704
|
+
if (!this.currentElement || !this.currentMetadata) return;
|
|
705
|
+
|
|
706
|
+
// Reset attributes to original values
|
|
707
|
+
this.originalAttributes.forEach((value, key) => {
|
|
708
|
+
if (value) {
|
|
709
|
+
this.currentElement!.setAttribute(key, value);
|
|
710
|
+
} else {
|
|
711
|
+
this.currentElement!.removeAttribute(key);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
// Reset styles to original values
|
|
716
|
+
this.originalStyles.forEach((value, key) => {
|
|
717
|
+
if (value) {
|
|
718
|
+
(this.currentElement!.style as any)[key] = value;
|
|
719
|
+
} else {
|
|
720
|
+
(this.currentElement!.style as any)[key] = '';
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
this.showToast('Reset to default values');
|
|
725
|
+
|
|
726
|
+
// Refresh the editor UI
|
|
727
|
+
this.close();
|
|
728
|
+
setTimeout(() => this.open(this.currentElement!, this.currentMetadata!), 100);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Show toast notification
|
|
733
|
+
*/
|
|
734
|
+
private showToast(message: string, type: 'success' | 'warning' | 'error' = 'success'): void {
|
|
735
|
+
const toast = document.createElement('div');
|
|
736
|
+
toast.style.cssText = `
|
|
737
|
+
position: fixed;
|
|
738
|
+
bottom: 20px;
|
|
739
|
+
right: 20px;
|
|
740
|
+
background: ${type === 'success' ? '#10b981' : type === 'warning' ? '#f59e0b' : '#ef4444'};
|
|
741
|
+
color: white;
|
|
742
|
+
padding: 12px 20px;
|
|
743
|
+
border-radius: 8px;
|
|
744
|
+
font-size: 14px;
|
|
745
|
+
font-weight: 500;
|
|
746
|
+
z-index: 1000003;
|
|
747
|
+
animation: slideIn 0.3s ease;
|
|
748
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
749
|
+
`;
|
|
750
|
+
toast.textContent = message;
|
|
751
|
+
document.body.appendChild(toast);
|
|
752
|
+
|
|
753
|
+
setTimeout(() => {
|
|
754
|
+
toast.style.animation = 'slideOut 0.3s ease';
|
|
755
|
+
setTimeout(() => toast.remove(), 300);
|
|
756
|
+
}, 2000);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Delete component instance from HTML (Instance mode only)
|
|
761
|
+
*/
|
|
762
|
+
private async deleteInstance(): Promise<void> {
|
|
763
|
+
if (!this.currentElement || !this.currentMetadata) return;
|
|
764
|
+
|
|
765
|
+
const tagName = this.currentMetadata.tagName;
|
|
766
|
+
|
|
767
|
+
// Show custom confirmation modal
|
|
768
|
+
const confirmed = await this.showConfirmModal(tagName);
|
|
769
|
+
|
|
770
|
+
if (!confirmed) return;
|
|
771
|
+
|
|
772
|
+
try {
|
|
773
|
+
const viewPath = window.location.pathname.replace(/^\//, '');
|
|
774
|
+
const htmlPath = viewPath === '' ? 'views/public/home.html' : `views/${viewPath}.html`;
|
|
775
|
+
|
|
776
|
+
const response = await fetch('/api/dev/component/delete-instance', {
|
|
777
|
+
method: 'POST',
|
|
778
|
+
headers: { 'Content-Type': 'application/json' },
|
|
779
|
+
body: JSON.stringify({
|
|
780
|
+
tagName,
|
|
781
|
+
htmlPath: `src/${htmlPath}`,
|
|
782
|
+
outerHTML: this.currentElement.outerHTML
|
|
783
|
+
})
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
const result = await response.json();
|
|
787
|
+
|
|
788
|
+
if (result.success) {
|
|
789
|
+
this.showToast('Instance deleted successfully');
|
|
790
|
+
this.currentElement.remove();
|
|
791
|
+
this.close();
|
|
792
|
+
} else {
|
|
793
|
+
this.showToast(result.error || 'Failed to delete instance', 'error');
|
|
794
|
+
}
|
|
795
|
+
} catch (error) {
|
|
796
|
+
console.error('[DevTools] Delete failed:', error);
|
|
797
|
+
this.showToast('Delete failed', 'error');
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
open(element: HTMLElement, metadata: ExtendedMetadata): void {
|
|
802
|
+
if (!this.drawer) return;
|
|
803
|
+
|
|
804
|
+
console.log('[ComponentEditor] Opening with metadata:', metadata);
|
|
805
|
+
console.log('[ComponentEditor] Attributes:', metadata.attributes);
|
|
806
|
+
|
|
807
|
+
// Clean up previous element's outline before switching to new element
|
|
808
|
+
if (this.currentElement && this.currentElement !== element) {
|
|
809
|
+
this.currentElement.style.outline = '';
|
|
810
|
+
this.currentElement.style.outlineOffset = '';
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
this.currentElement = element;
|
|
814
|
+
this.currentMetadata = metadata;
|
|
815
|
+
this.originalStyles.clear();
|
|
816
|
+
this.originalAttributes.clear();
|
|
817
|
+
|
|
818
|
+
// Store original attribute values
|
|
819
|
+
metadata.attributes?.forEach(attr => {
|
|
820
|
+
const currentValue = element.getAttribute(attr.name);
|
|
821
|
+
this.originalAttributes.set(attr.name, currentValue || '');
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
const tagNameEl = this.drawer.querySelector('#ncEditorTagName');
|
|
825
|
+
if (tagNameEl) tagNameEl.textContent = `<${metadata.tagName}>`;
|
|
826
|
+
|
|
827
|
+
// Display file path
|
|
828
|
+
const filePathEl = this.drawer.querySelector('#ncEditorFilePath');
|
|
829
|
+
if (filePathEl && metadata.filePath) {
|
|
830
|
+
filePathEl.innerHTML = `
|
|
831
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
832
|
+
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
|
|
833
|
+
<polyline points="13 2 13 9 20 9"></polyline>
|
|
834
|
+
</svg>
|
|
835
|
+
<span>${metadata.filePath}</span>
|
|
836
|
+
`;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const contentEl = this.drawer.querySelector('#ncEditorContent');
|
|
840
|
+
if (contentEl) {
|
|
841
|
+
contentEl.innerHTML = this.buildDynamicContent(element, metadata);
|
|
842
|
+
this.attachListeners(contentEl as HTMLElement, element);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
this.drawer.classList.add('active');
|
|
846
|
+
element.style.outline = '2px dashed rgba(102, 126, 234, 0.6)';
|
|
847
|
+
element.style.outlineOffset = '4px';
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
private buildDynamicContent(element: HTMLElement, meta: ExtendedMetadata): string {
|
|
851
|
+
const chevron = '<svg class="nc-editor-section-toggle" viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>';
|
|
852
|
+
let html = '';
|
|
853
|
+
let sectionCount = 0;
|
|
854
|
+
|
|
855
|
+
// Element Properties section
|
|
856
|
+
sectionCount++;
|
|
857
|
+
const elementId = element.getAttribute('id') || '';
|
|
858
|
+
const elementClass = element.getAttribute('class') || '';
|
|
859
|
+
html += this.buildSection('Element Properties', 2, chevron, sectionCount === 1, `
|
|
860
|
+
<div class="nc-editor-field">
|
|
861
|
+
<label class="nc-editor-label">id</label>
|
|
862
|
+
<input type="text" class="nc-editor-input" data-attr="id" value="${elementId}" placeholder="element-id">
|
|
863
|
+
</div>
|
|
864
|
+
<div class="nc-editor-field">
|
|
865
|
+
<label class="nc-editor-label">class</label>
|
|
866
|
+
<input type="text" class="nc-editor-input" data-attr="class" value="${elementClass}" placeholder="class-names">
|
|
867
|
+
</div>
|
|
868
|
+
`);
|
|
869
|
+
|
|
870
|
+
// Attributes section
|
|
871
|
+
if (meta.attributes && meta.attributes.length > 0) {
|
|
872
|
+
// Filter attributes based on conditions if they exist
|
|
873
|
+
const componentClass = (element.constructor as any);
|
|
874
|
+
const conditions = componentClass.attributeConditions || {};
|
|
875
|
+
|
|
876
|
+
const visibleAttributes = meta.attributes.filter(attr => {
|
|
877
|
+
// If there's a condition for this attribute, check it
|
|
878
|
+
if (conditions[attr.name]) {
|
|
879
|
+
return conditions[attr.name](element);
|
|
880
|
+
}
|
|
881
|
+
// If no condition, always show
|
|
882
|
+
return true;
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
if (visibleAttributes.length > 0) {
|
|
886
|
+
sectionCount++;
|
|
887
|
+
html += this.buildSection('Attributes', visibleAttributes.length, chevron, sectionCount === 1,
|
|
888
|
+
visibleAttributes.map(attr => this.buildAttributeField(element, attr)).join('')
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// CSS Variables section
|
|
894
|
+
if (meta.cssVariables && meta.cssVariables.length > 0) {
|
|
895
|
+
sectionCount++;
|
|
896
|
+
html += this.buildSection('CSS Variables', meta.cssVariables.length, chevron, sectionCount === 1,
|
|
897
|
+
meta.cssVariables.map(v => this.buildCssVarField(element, v)).join('')
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Host Styles section
|
|
902
|
+
if (meta.hostStyles && meta.hostStyles.length > 0) {
|
|
903
|
+
sectionCount++;
|
|
904
|
+
html += this.buildSection('Host Styles', meta.hostStyles.length, chevron, sectionCount === 1,
|
|
905
|
+
meta.hostStyles.map(s => this.buildHostStyleField(element, s)).join('')
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Common styles section (always show for quick edits)
|
|
910
|
+
sectionCount++;
|
|
911
|
+
const cs = getComputedStyle(element);
|
|
912
|
+
html += this.buildSection('Quick Styles', 4, chevron, sectionCount === 1 && html === '', `
|
|
913
|
+
${this.buildStyleField('color', 'color', cs.color)}
|
|
914
|
+
${this.buildStyleField('backgroundColor', 'bg-color', cs.backgroundColor)}
|
|
915
|
+
${this.buildStyleField('padding', 'padding', cs.padding)}
|
|
916
|
+
${this.buildStyleField('borderRadius', 'radius', cs.borderRadius)}
|
|
917
|
+
`);
|
|
918
|
+
|
|
919
|
+
if (html === '') {
|
|
920
|
+
html = '<div class="nc-editor-empty">No editable properties found</div>';
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
return html;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
private buildSection(title: string, count: number, chevron: string, expanded: boolean, content: string): string {
|
|
927
|
+
return `
|
|
928
|
+
<div class="nc-editor-section ${expanded ? '' : 'collapsed'}">
|
|
929
|
+
<div class="nc-editor-section-header">
|
|
930
|
+
<span class="nc-editor-section-title">${title}</span>
|
|
931
|
+
<span class="nc-editor-section-count">${count}</span>
|
|
932
|
+
${chevron}
|
|
933
|
+
</div>
|
|
934
|
+
<div class="nc-editor-section-content">${content}</div>
|
|
935
|
+
</div>
|
|
936
|
+
`;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
private buildAttributeField(element: HTMLElement, attr: { name: string; type: string; defaultValue: string; variantOptions?: string[] }): string {
|
|
940
|
+
const currentValue = element.getAttribute(attr.name) || attr.defaultValue || '';
|
|
941
|
+
|
|
942
|
+
if (attr.type === 'boolean') {
|
|
943
|
+
const checked = element.hasAttribute(attr.name) ? 'checked' : '';
|
|
944
|
+
return `<div class="nc-editor-field">
|
|
945
|
+
<label class="nc-editor-label">${attr.name}</label>
|
|
946
|
+
<input type="checkbox" class="nc-editor-checkbox" data-attr="${attr.name}" ${checked}>
|
|
947
|
+
</div>`;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (attr.type === 'number') {
|
|
951
|
+
// Slider for numbers with visual value display
|
|
952
|
+
const numValue = parseInt(currentValue) || 0;
|
|
953
|
+
return `<div class="nc-editor-field">
|
|
954
|
+
<label class="nc-editor-label">${attr.name}</label>
|
|
955
|
+
<div class="nc-editor-slider-group">
|
|
956
|
+
<div class="nc-editor-slider-wrapper">
|
|
957
|
+
<input type="range" class="nc-editor-input nc-editor-slider"
|
|
958
|
+
data-attr="${attr.name}"
|
|
959
|
+
data-attr-type="number-slider"
|
|
960
|
+
value="${numValue}"
|
|
961
|
+
min="0" max="100" step="1">
|
|
962
|
+
<span class="nc-editor-slider-value" data-slider-display="${attr.name}">${numValue}</span>
|
|
963
|
+
</div>
|
|
964
|
+
<input type="number" class="nc-editor-input"
|
|
965
|
+
data-attr="${attr.name}"
|
|
966
|
+
data-attr-type="number-input"
|
|
967
|
+
value="${numValue}"
|
|
968
|
+
style="margin-top: 4px;">
|
|
969
|
+
</div>
|
|
970
|
+
</div>`;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Variant dropdown
|
|
974
|
+
if (attr.type === 'variant' && attr.variantOptions && attr.variantOptions.length > 0) {
|
|
975
|
+
const emptyOption = `<option value="" ${!currentValue ? 'selected' : ''}>(none)</option>`;
|
|
976
|
+
const options = attr.variantOptions.map(opt =>
|
|
977
|
+
`<option value="${opt}" ${opt === currentValue ? 'selected' : ''}>${opt}</option>`
|
|
978
|
+
).join('');
|
|
979
|
+
|
|
980
|
+
return `<div class="nc-editor-field">
|
|
981
|
+
<label class="nc-editor-label">${attr.name}</label>
|
|
982
|
+
<select class="nc-editor-input nc-editor-select" data-attr="${attr.name}">
|
|
983
|
+
${emptyOption}
|
|
984
|
+
${options}
|
|
985
|
+
</select>
|
|
986
|
+
</div>`;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Color attribute detection
|
|
990
|
+
if (attr.name.includes('color') || attr.name.includes('bg')) {
|
|
991
|
+
return `<div class="nc-editor-field">
|
|
992
|
+
<label class="nc-editor-label">${attr.name}</label>
|
|
993
|
+
<div class="nc-editor-color-wrap">
|
|
994
|
+
<input type="color" data-attr="${attr.name}" value="${currentValue || '#000000'}">
|
|
995
|
+
<input type="text" class="nc-editor-input" data-attr-text="${attr.name}" value="${currentValue}">
|
|
996
|
+
</div>
|
|
997
|
+
</div>`;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Get placeholder from component's static attributePlaceholders property
|
|
1001
|
+
const componentClass = (element.constructor as any);
|
|
1002
|
+
const placeholder = componentClass.attributePlaceholders?.[attr.name] || '';
|
|
1003
|
+
|
|
1004
|
+
return `<div class="nc-editor-field">
|
|
1005
|
+
<label class="nc-editor-label">${attr.name}</label>
|
|
1006
|
+
<input type="text" class="nc-editor-input" data-attr="${attr.name}" value="${currentValue}" placeholder="${placeholder}">
|
|
1007
|
+
</div>`;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
private buildCssVarField(element: HTMLElement, cssVar: { name: string; defaultValue: string }): string {
|
|
1011
|
+
const isColor = cssVar.name.includes('color') || cssVar.defaultValue.startsWith('#') || cssVar.defaultValue.startsWith('rgb');
|
|
1012
|
+
|
|
1013
|
+
if (isColor) {
|
|
1014
|
+
const hex = this.rgbToHex(cssVar.defaultValue);
|
|
1015
|
+
return `<div class="nc-editor-field">
|
|
1016
|
+
<span class="nc-editor-label">${cssVar.name.replace('--', '')}</span>
|
|
1017
|
+
<div class="nc-editor-color-wrap">
|
|
1018
|
+
<input type="color" data-cssvar="${cssVar.name}" value="${hex}">
|
|
1019
|
+
<input type="text" class="nc-editor-input" data-cssvar-text="${cssVar.name}" value="${cssVar.defaultValue}">
|
|
1020
|
+
</div>
|
|
1021
|
+
</div>`;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return `<div class="nc-editor-field">
|
|
1025
|
+
<span class="nc-editor-label">${cssVar.name.replace('--', '')}</span>
|
|
1026
|
+
<input type="text" class="nc-editor-input" data-cssvar="${cssVar.name}" value="${cssVar.defaultValue}">
|
|
1027
|
+
</div>`;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
private buildHostStyleField(element: HTMLElement, style: { property: string; value: string }): string {
|
|
1031
|
+
const isColor = style.property.includes('color') || style.value.startsWith('#') || style.value.startsWith('rgb');
|
|
1032
|
+
|
|
1033
|
+
if (isColor) {
|
|
1034
|
+
const hex = this.rgbToHex(style.value);
|
|
1035
|
+
return `<div class="nc-editor-field">
|
|
1036
|
+
<span class="nc-editor-label">${style.property}</span>
|
|
1037
|
+
<div class="nc-editor-color-wrap">
|
|
1038
|
+
<input type="color" data-host-style="${style.property}" value="${hex}">
|
|
1039
|
+
<input type="text" class="nc-editor-input" data-host-style-text="${style.property}" value="${style.value}">
|
|
1040
|
+
</div>
|
|
1041
|
+
</div>`;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const numValue = parseFloat(style.value);
|
|
1045
|
+
if (!isNaN(numValue) && style.value.includes('px')) {
|
|
1046
|
+
return `<div class="nc-editor-field">
|
|
1047
|
+
<span class="nc-editor-label">${style.property}</span>
|
|
1048
|
+
<input type="range" data-host-style="${style.property}" value="${numValue}" min="0" max="50">
|
|
1049
|
+
<input type="number" class="nc-editor-input" data-host-style-num="${style.property}" value="${numValue}" style="width:45px;">
|
|
1050
|
+
</div>`;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
return `<div class="nc-editor-field">
|
|
1054
|
+
<span class="nc-editor-label">${style.property}</span>
|
|
1055
|
+
<input type="text" class="nc-editor-input" data-host-style="${style.property}" value="${style.value}">
|
|
1056
|
+
</div>`;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
private buildStyleField(prop: string, label: string, value: string): string {
|
|
1060
|
+
const isColor = prop.includes('color') || prop.includes('Color');
|
|
1061
|
+
|
|
1062
|
+
if (isColor) {
|
|
1063
|
+
const hex = this.rgbToHex(value);
|
|
1064
|
+
return `<div class="nc-editor-field">
|
|
1065
|
+
<span class="nc-editor-label">${label}</span>
|
|
1066
|
+
<div class="nc-editor-color-wrap">
|
|
1067
|
+
<input type="color" data-style="${prop}" value="${hex}">
|
|
1068
|
+
<input type="text" class="nc-editor-input" data-style-text="${prop}" value="${value}">
|
|
1069
|
+
</div>
|
|
1070
|
+
</div>`;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const numValue = parseFloat(value) || 0;
|
|
1074
|
+
return `<div class="nc-editor-field">
|
|
1075
|
+
<span class="nc-editor-label">${label}</span>
|
|
1076
|
+
<input type="range" data-style="${prop}" value="${numValue}" min="0" max="50">
|
|
1077
|
+
<input type="number" class="nc-editor-input" data-style-num="${prop}" value="${numValue}" style="width:45px;">
|
|
1078
|
+
</div>`;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
private attachListeners(container: HTMLElement, element: HTMLElement): void {
|
|
1082
|
+
// Section toggles
|
|
1083
|
+
container.querySelectorAll('.nc-editor-section-header').forEach(h => {
|
|
1084
|
+
h.addEventListener('click', () => h.parentElement?.classList.toggle('collapsed'));
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
// Attribute inputs
|
|
1088
|
+
container.querySelectorAll('[data-attr]').forEach(input => {
|
|
1089
|
+
const eventType = (input as HTMLInputElement).type === 'range' ? 'input' : 'change';
|
|
1090
|
+
|
|
1091
|
+
input.addEventListener(eventType, (e) => {
|
|
1092
|
+
const t = e.target as HTMLInputElement;
|
|
1093
|
+
const attrType = t.dataset.attrType;
|
|
1094
|
+
const attrName = t.dataset.attr!;
|
|
1095
|
+
|
|
1096
|
+
if (t.type === 'checkbox') {
|
|
1097
|
+
if (t.checked) element.setAttribute(attrName, '');
|
|
1098
|
+
else element.removeAttribute(attrName);
|
|
1099
|
+
} else if (attrType === 'number-slider') {
|
|
1100
|
+
// Update both slider and number input
|
|
1101
|
+
element.setAttribute(attrName, t.value);
|
|
1102
|
+
const display = container.querySelector(`[data-slider-display="${attrName}"]`);
|
|
1103
|
+
if (display) display.textContent = t.value;
|
|
1104
|
+
const numberInput = container.querySelector(`[data-attr="${attrName}"][data-attr-type="number-input"]`) as HTMLInputElement;
|
|
1105
|
+
if (numberInput) numberInput.value = t.value;
|
|
1106
|
+
} else if (attrType === 'number-input') {
|
|
1107
|
+
// Update both number input and slider
|
|
1108
|
+
element.setAttribute(attrName, t.value);
|
|
1109
|
+
const slider = container.querySelector(`[data-attr="${attrName}"][data-attr-type="number-slider"]`) as HTMLInputElement;
|
|
1110
|
+
if (slider) slider.value = t.value;
|
|
1111
|
+
const display = container.querySelector(`[data-slider-display="${attrName}"]`);
|
|
1112
|
+
if (display) display.textContent = t.value;
|
|
1113
|
+
} else if (t.type === 'color') {
|
|
1114
|
+
// Update color and text input
|
|
1115
|
+
element.setAttribute(attrName, t.value);
|
|
1116
|
+
const textInput = container.querySelector(`[data-attr-text="${attrName}"]`) as HTMLInputElement;
|
|
1117
|
+
if (textInput) textInput.value = t.value;
|
|
1118
|
+
} else {
|
|
1119
|
+
// For dropdowns and text inputs
|
|
1120
|
+
if (t.value === '') {
|
|
1121
|
+
element.removeAttribute(attrName);
|
|
1122
|
+
} else {
|
|
1123
|
+
element.setAttribute(attrName, t.value);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Check if this attribute change should trigger a rebuild (for conditional attributes)
|
|
1128
|
+
const componentClass = (element.constructor as any);
|
|
1129
|
+
const conditions = componentClass.attributeConditions || {};
|
|
1130
|
+
|
|
1131
|
+
// If any other attribute has a condition that depends on this one, rebuild
|
|
1132
|
+
if (Object.keys(conditions).some(key => key !== attrName)) {
|
|
1133
|
+
// Check if changing this attribute affects visibility of other attributes
|
|
1134
|
+
// by testing if this is something conditions check (like 'layout')
|
|
1135
|
+
if (attrName === 'layout' || Object.values(conditions).some((fn: any) => {
|
|
1136
|
+
// Simple heuristic: if condition function mentions this attribute
|
|
1137
|
+
return fn.toString().includes(`'${attrName}'`);
|
|
1138
|
+
})) {
|
|
1139
|
+
// Save section states before rebuilding
|
|
1140
|
+
const contentEl = this.drawer?.querySelector('#ncEditorContent');
|
|
1141
|
+
const sectionStates = new Map<string, boolean>();
|
|
1142
|
+
if (contentEl) {
|
|
1143
|
+
contentEl.querySelectorAll('.nc-editor-section').forEach((section: Element) => {
|
|
1144
|
+
const title = section.querySelector('.nc-editor-section-title')?.textContent || '';
|
|
1145
|
+
const isCollapsed = section.classList.contains('collapsed');
|
|
1146
|
+
sectionStates.set(title, isCollapsed);
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// Rebuild the editor content with new conditional visibility
|
|
1151
|
+
setTimeout(() => {
|
|
1152
|
+
if (this.currentElement && this.currentMetadata) {
|
|
1153
|
+
const contentEl = this.drawer?.querySelector('#ncEditorContent');
|
|
1154
|
+
if (contentEl) {
|
|
1155
|
+
contentEl.innerHTML = this.buildDynamicContent(this.currentElement, this.currentMetadata);
|
|
1156
|
+
this.attachListeners(contentEl as HTMLElement, this.currentElement);
|
|
1157
|
+
|
|
1158
|
+
// Restore section states
|
|
1159
|
+
contentEl.querySelectorAll('.nc-editor-section').forEach((section: Element) => {
|
|
1160
|
+
const title = section.querySelector('.nc-editor-section-title')?.textContent || '';
|
|
1161
|
+
const wasCollapsed = sectionStates.get(title);
|
|
1162
|
+
if (wasCollapsed === true) {
|
|
1163
|
+
section.classList.add('collapsed');
|
|
1164
|
+
} else if (wasCollapsed === false) {
|
|
1165
|
+
section.classList.remove('collapsed');
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}, 50);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
// Color text inputs
|
|
1177
|
+
container.querySelectorAll('[data-attr-text]').forEach(input => {
|
|
1178
|
+
input.addEventListener('change', (e) => {
|
|
1179
|
+
const t = e.target as HTMLInputElement;
|
|
1180
|
+
const attrName = t.dataset.attrText!;
|
|
1181
|
+
element.setAttribute(attrName, t.value);
|
|
1182
|
+
const colorInput = container.querySelector(`[data-attr="${attrName}"][type="color"]`) as HTMLInputElement;
|
|
1183
|
+
if (colorInput && /^#[0-9A-F]{6}$/i.test(t.value)) {
|
|
1184
|
+
colorInput.value = t.value;
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
// CSS Variable inputs
|
|
1190
|
+
container.querySelectorAll('[data-cssvar]').forEach(input => {
|
|
1191
|
+
input.addEventListener('input', (e) => {
|
|
1192
|
+
const t = e.target as HTMLInputElement;
|
|
1193
|
+
element.style.setProperty(t.dataset.cssvar!, t.value);
|
|
1194
|
+
const txt = container.querySelector(`[data-cssvar-text="${t.dataset.cssvar}"]`) as HTMLInputElement;
|
|
1195
|
+
if (txt) txt.value = t.value;
|
|
1196
|
+
});
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
// Host style inputs
|
|
1200
|
+
container.querySelectorAll('[data-host-style]').forEach(input => {
|
|
1201
|
+
input.addEventListener('input', (e) => {
|
|
1202
|
+
const t = e.target as HTMLInputElement;
|
|
1203
|
+
const prop = t.dataset.hostStyle!;
|
|
1204
|
+
const val = t.type === 'range' ? t.value + 'px' : t.value;
|
|
1205
|
+
(element.style as any)[prop] = val;
|
|
1206
|
+
|
|
1207
|
+
const num = container.querySelector(`[data-host-style-num="${prop}"]`) as HTMLInputElement;
|
|
1208
|
+
if (num && t.type === 'range') num.value = t.value;
|
|
1209
|
+
const txt = container.querySelector(`[data-host-style-text="${prop}"]`) as HTMLInputElement;
|
|
1210
|
+
if (txt) txt.value = val;
|
|
1211
|
+
});
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
container.querySelectorAll('[data-host-style-num]').forEach(input => {
|
|
1215
|
+
input.addEventListener('input', (e) => {
|
|
1216
|
+
const t = e.target as HTMLInputElement;
|
|
1217
|
+
const prop = t.dataset.hostStyleNum!;
|
|
1218
|
+
(element.style as any)[prop] = t.value + 'px';
|
|
1219
|
+
const range = container.querySelector(`[data-host-style="${prop}"]`) as HTMLInputElement;
|
|
1220
|
+
if (range) range.value = t.value;
|
|
1221
|
+
});
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
// Quick style inputs
|
|
1225
|
+
container.querySelectorAll('[data-style]').forEach(input => {
|
|
1226
|
+
input.addEventListener('input', (e) => {
|
|
1227
|
+
const t = e.target as HTMLInputElement;
|
|
1228
|
+
const prop = t.dataset.style!;
|
|
1229
|
+
const val = t.type === 'range' ? t.value + 'px' : t.value;
|
|
1230
|
+
(element.style as any)[prop] = val;
|
|
1231
|
+
|
|
1232
|
+
const num = container.querySelector(`[data-style-num="${prop}"]`) as HTMLInputElement;
|
|
1233
|
+
if (num && t.type === 'range') num.value = t.value;
|
|
1234
|
+
const txt = container.querySelector(`[data-style-text="${prop}"]`) as HTMLInputElement;
|
|
1235
|
+
if (txt) txt.value = val;
|
|
1236
|
+
});
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
container.querySelectorAll('[data-style-num]').forEach(input => {
|
|
1240
|
+
input.addEventListener('input', (e) => {
|
|
1241
|
+
const t = e.target as HTMLInputElement;
|
|
1242
|
+
const prop = t.dataset.styleNum!;
|
|
1243
|
+
(element.style as any)[prop] = t.value + 'px';
|
|
1244
|
+
const range = container.querySelector(`[data-style="${prop}"]`) as HTMLInputElement;
|
|
1245
|
+
if (range) range.value = t.value;
|
|
1246
|
+
});
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
private rgbToHex(rgb: string): string {
|
|
1251
|
+
if (rgb.startsWith('#')) return rgb;
|
|
1252
|
+
if (rgb === 'transparent' || rgb === 'rgba(0, 0, 0, 0)') return '#ffffff';
|
|
1253
|
+
const m = rgb.match(/\d+/g);
|
|
1254
|
+
if (!m || m.length < 3) return '#000000';
|
|
1255
|
+
return '#' + m.slice(0, 3).map(n => parseInt(n).toString(16).padStart(2, '0')).join('');
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
private async save(): Promise<void> {
|
|
1259
|
+
if (!this.currentMetadata || !this.currentElement) return;
|
|
1260
|
+
await this.saveInstanceChanges();
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
private async saveInstanceChanges(): Promise<void> {
|
|
1264
|
+
if (!this.currentElement || !this.currentMetadata) return;
|
|
1265
|
+
|
|
1266
|
+
// Collect ALL current attribute values (not just from metadata)
|
|
1267
|
+
const attributes: Record<string, string> = {};
|
|
1268
|
+
const attrs = this.currentElement.attributes;
|
|
1269
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
1270
|
+
const attr = attrs[i];
|
|
1271
|
+
if (attr.name !== 'style' && attr.name !== 'class') {
|
|
1272
|
+
attributes[attr.name] = attr.value;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// NO inline styles - attributes only!
|
|
1277
|
+
|
|
1278
|
+
const payload = {
|
|
1279
|
+
tagName: this.currentMetadata.tagName,
|
|
1280
|
+
viewPath: window.location.pathname,
|
|
1281
|
+
attributes,
|
|
1282
|
+
elementIndex: this.getElementIndex()
|
|
1283
|
+
};
|
|
1284
|
+
|
|
1285
|
+
console.log('[DevTools] Saving instance changes:', payload);
|
|
1286
|
+
|
|
1287
|
+
try {
|
|
1288
|
+
const response = await fetch('/api/dev/component/save-instance', {
|
|
1289
|
+
method: 'POST',
|
|
1290
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1291
|
+
body: JSON.stringify(payload)
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
if (response.ok) {
|
|
1295
|
+
const result = await response.json();
|
|
1296
|
+
console.log('[DevTools] Save successful:', result);
|
|
1297
|
+
this.close();
|
|
1298
|
+
} else {
|
|
1299
|
+
const error = await response.json();
|
|
1300
|
+
console.error('[DevTools] Failed to save instance changes:', error);
|
|
1301
|
+
alert(`Failed to save: ${error.message || 'Unknown error'}`);
|
|
1302
|
+
}
|
|
1303
|
+
} catch (error) {
|
|
1304
|
+
console.error('[DevTools] Error saving instance changes:', error);
|
|
1305
|
+
alert(`Error saving: ${error instanceof Error ? error.message : String(error)}`);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
private getElementIndex(): number {
|
|
1312
|
+
if (!this.currentElement || !this.currentMetadata) return 0;
|
|
1313
|
+
|
|
1314
|
+
const allElements = Array.from(dom.queryAll(this.currentMetadata.tagName));
|
|
1315
|
+
return allElements.indexOf(this.currentElement);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
private async saveOld(): Promise<void> {
|
|
1319
|
+
if (!this.currentMetadata || !this.currentElement) return;
|
|
1320
|
+
|
|
1321
|
+
const changes: any = {
|
|
1322
|
+
tagName: this.currentMetadata.tagName,
|
|
1323
|
+
filePath: this.currentMetadata.filePath,
|
|
1324
|
+
styleChanges: {},
|
|
1325
|
+
attributeChanges: {},
|
|
1326
|
+
cssVarChanges: {}
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
// Collect style changes from the element
|
|
1330
|
+
const el = this.currentElement;
|
|
1331
|
+
['color', 'backgroundColor', 'padding', 'margin', 'borderRadius', 'borderWidth', 'borderColor'].forEach(prop => {
|
|
1332
|
+
const val = (el.style as any)[prop];
|
|
1333
|
+
if (val) changes.styleChanges[prop] = val;
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
try {
|
|
1337
|
+
await fetch('/api/dev/component/edit', {
|
|
1338
|
+
method: 'POST',
|
|
1339
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1340
|
+
body: JSON.stringify(changes)
|
|
1341
|
+
});
|
|
1342
|
+
setTimeout(() => this.close(), 500);
|
|
1343
|
+
} catch (e) {
|
|
1344
|
+
console.error('[ComponentEditor] Save failed:', e);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
close(): void {
|
|
1349
|
+
this.drawer?.classList.remove('active');
|
|
1350
|
+
document.getElementById('ncEditorModal')?.classList.remove('active');
|
|
1351
|
+
if (this.currentElement) {
|
|
1352
|
+
this.currentElement.style.outline = '';
|
|
1353
|
+
this.currentElement.style.outlineOffset = '';
|
|
1354
|
+
}
|
|
1355
|
+
this.currentElement = null;
|
|
1356
|
+
this.currentMetadata = null;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
destroy(): void {
|
|
1360
|
+
this.drawer?.remove();
|
|
1361
|
+
(window.dom?.query?.('#nativecore-editor-styles') || document.getElementById('nativecore-editor-styles'))?.remove();
|
|
1362
|
+
}
|
|
1363
|
+
}
|