claudeship 0.2.12 → 0.2.15
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 +18 -0
- package/apps/server/dist/app.module.js +10 -0
- package/apps/server/dist/app.module.js.map +1 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.js +109 -1
- package/apps/server/dist/chat/prompts/fullstack-express-prompt.js.map +1 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.js +109 -1
- package/apps/server/dist/chat/prompts/fullstack-fastapi-prompt.js.map +1 -1
- package/apps/server/dist/chat/prompts/web-system-prompt.d.ts +1 -1
- package/apps/server/dist/chat/prompts/web-system-prompt.js +156 -0
- package/apps/server/dist/chat/prompts/web-system-prompt.js.map +1 -1
- package/apps/server/dist/checkpoint/checkpoint.controller.d.ts +19 -0
- package/apps/server/dist/checkpoint/checkpoint.controller.js +93 -0
- package/apps/server/dist/checkpoint/checkpoint.controller.js.map +1 -0
- package/apps/server/dist/checkpoint/checkpoint.module.d.ts +2 -0
- package/apps/server/dist/checkpoint/checkpoint.module.js +25 -0
- package/apps/server/dist/checkpoint/checkpoint.module.js.map +1 -0
- package/apps/server/dist/checkpoint/checkpoint.service.d.ts +41 -0
- package/apps/server/dist/checkpoint/checkpoint.service.js +261 -0
- package/apps/server/dist/checkpoint/checkpoint.service.js.map +1 -0
- package/apps/server/dist/database/database.controller.d.ts +23 -0
- package/apps/server/dist/database/database.controller.js +109 -0
- package/apps/server/dist/database/database.controller.js.map +1 -0
- package/apps/server/dist/database/database.module.d.ts +2 -0
- package/apps/server/dist/database/database.module.js +25 -0
- package/apps/server/dist/database/database.module.js.map +1 -0
- package/apps/server/dist/database/database.service.d.ts +32 -0
- package/apps/server/dist/database/database.service.js +238 -0
- package/apps/server/dist/database/database.service.js.map +1 -0
- package/apps/server/dist/env/env.controller.d.ts +14 -0
- package/apps/server/dist/env/env.controller.js +84 -0
- package/apps/server/dist/env/env.controller.js.map +1 -0
- package/apps/server/dist/env/env.module.d.ts +2 -0
- package/apps/server/dist/env/env.module.js +25 -0
- package/apps/server/dist/env/env.module.js.map +1 -0
- package/apps/server/dist/env/env.service.d.ts +21 -0
- package/apps/server/dist/env/env.service.js +194 -0
- package/apps/server/dist/env/env.service.js.map +1 -0
- package/apps/server/dist/preview/preview.controller.d.ts +5 -0
- package/apps/server/dist/preview/preview.controller.js +41 -0
- package/apps/server/dist/preview/preview.controller.js.map +1 -1
- package/apps/server/dist/preview/preview.service.d.ts +20 -0
- package/apps/server/dist/preview/preview.service.js +51 -2
- package/apps/server/dist/preview/preview.service.js.map +1 -1
- package/apps/server/dist/project/project.controller.d.ts +10 -1
- package/apps/server/dist/project/project.controller.js +57 -0
- package/apps/server/dist/project/project.controller.js.map +1 -1
- package/apps/server/dist/project/project.service.d.ts +15 -0
- package/apps/server/dist/project/project.service.js +111 -0
- package/apps/server/dist/project/project.service.js.map +1 -1
- package/apps/server/dist/project-context/project-context.controller.d.ts +42 -0
- package/apps/server/dist/project-context/project-context.controller.js +127 -0
- package/apps/server/dist/project-context/project-context.controller.js.map +1 -0
- package/apps/server/dist/project-context/project-context.module.d.ts +2 -0
- package/apps/server/dist/project-context/project-context.module.js +25 -0
- package/apps/server/dist/project-context/project-context.module.js.map +1 -0
- package/apps/server/dist/project-context/project-context.service.d.ts +36 -0
- package/apps/server/dist/project-context/project-context.service.js +260 -0
- package/apps/server/dist/project-context/project-context.service.js.map +1 -0
- package/apps/server/dist/testing/testing.controller.d.ts +24 -0
- package/apps/server/dist/testing/testing.controller.js +126 -0
- package/apps/server/dist/testing/testing.controller.js.map +1 -0
- package/apps/server/dist/testing/testing.module.d.ts +2 -0
- package/apps/server/dist/testing/testing.module.js +26 -0
- package/apps/server/dist/testing/testing.module.js.map +1 -0
- package/apps/server/dist/testing/testing.service.d.ts +62 -0
- package/apps/server/dist/testing/testing.service.js +269 -0
- package/apps/server/dist/testing/testing.service.js.map +1 -0
- package/apps/server/dist/tsconfig.tsbuildinfo +1 -1
- package/apps/server/package.json +1 -1
- package/apps/web/.next/BUILD_ID +1 -1
- package/apps/web/.next/app-build-manifest.json +5 -5
- package/apps/web/.next/build-manifest.json +2 -2
- package/apps/web/.next/cache/.previewinfo +1 -1
- package/apps/web/.next/cache/.rscinfo +1 -1
- package/apps/web/.next/cache/.tsbuildinfo +1 -1
- package/apps/web/.next/cache/config.json +3 -3
- package/apps/web/.next/cache/eslint/.cache_j3uhuz +1 -1
- package/apps/web/.next/cache/webpack/client-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/index.pack +0 -0
- package/apps/web/.next/prerender-manifest.json +10 -10
- package/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/_not-found.html +1 -1
- package/apps/web/.next/server/app/_not-found.rsc +2 -2
- package/apps/web/.next/server/app/index.html +1 -1
- package/apps/web/.next/server/app/index.rsc +3 -3
- package/apps/web/.next/server/app/page.js +2 -2
- package/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/project/[id]/page.js +2 -2
- package/apps/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/apps/web/.next/server/app/settings.html +1 -1
- package/apps/web/.next/server/app/settings.rsc +3 -3
- package/apps/web/.next/server/chunks/392.js +1 -1
- package/apps/web/.next/server/pages/404.html +1 -1
- package/apps/web/.next/server/pages/500.html +1 -1
- package/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/apps/web/.next/static/chunks/574-1fe2bcd6cfb41646.js +1 -0
- package/apps/web/.next/static/chunks/app/page-f19cfa58541ca83d.js +1 -0
- package/apps/web/.next/static/chunks/app/project/[id]/page-dffaa1d02f012216.js +1 -0
- package/apps/web/.next/static/chunks/app/settings/page-d1318c2fd58729a5.js +1 -0
- package/apps/web/.next/static/css/0a24552d9794f8c8.css +3 -0
- package/apps/web/.next/trace +18 -17
- package/apps/web/node_modules/.bin/eslint +2 -2
- package/apps/web/package.json +2 -1
- package/apps/web/src/components/checkpoint/CheckpointPanel.tsx +384 -0
- package/apps/web/src/components/database/DatabasePanel.tsx +405 -0
- package/apps/web/src/components/env/EnvPanel.tsx +356 -0
- package/apps/web/src/components/preview/ConsoleViewer.tsx +270 -0
- package/apps/web/src/components/preview/ErrorOverlay.tsx +189 -0
- package/apps/web/src/components/preview/PreviewPanel.tsx +148 -6
- package/apps/web/src/components/testing/TestRunner.tsx +481 -0
- package/apps/web/src/components/ui/tabs.tsx +55 -0
- package/apps/web/src/components/visual-editor/VisualEditor.tsx +382 -0
- package/apps/web/src/components/workspace/WorkspaceLayout.tsx +66 -4
- package/apps/web/src/lib/api.ts +5 -2
- package/package.json +1 -1
- package/apps/web/.next/static/chunks/298-6f3d6b321c288cd3.js +0 -1
- package/apps/web/.next/static/chunks/app/page-3d093f7f480a8599.js +0 -1
- package/apps/web/.next/static/chunks/app/project/[id]/page-e5cda6f9050b0a52.js +0 -1
- package/apps/web/.next/static/chunks/app/settings/page-92d28565c3d8c755.js +0 -1
- package/apps/web/.next/static/css/8f946046a2047594.css +0 -3
- /package/apps/web/.next/static/{aXT20mSdxaem1-z8VH2F1 → mkY_TTl_ho_ehDKiX10AN}/_buildManifest.js +0 -0
- /package/apps/web/.next/static/{aXT20mSdxaem1-z8VH2F1 → mkY_TTl_ho_ehDKiX10AN}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useRef, useCallback } from "react";
|
|
4
|
+
import {
|
|
5
|
+
MousePointer2,
|
|
6
|
+
Move,
|
|
7
|
+
Maximize2,
|
|
8
|
+
Type,
|
|
9
|
+
Palette,
|
|
10
|
+
Box,
|
|
11
|
+
X,
|
|
12
|
+
RefreshCw,
|
|
13
|
+
} from "lucide-react";
|
|
14
|
+
import { Button } from "@/components/ui/button";
|
|
15
|
+
import { Input } from "@/components/ui/input";
|
|
16
|
+
|
|
17
|
+
interface SelectedElement {
|
|
18
|
+
selector: string;
|
|
19
|
+
tagName: string;
|
|
20
|
+
className: string;
|
|
21
|
+
id: string;
|
|
22
|
+
styles: Record<string, string>;
|
|
23
|
+
rect: DOMRect;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface VisualEditorProps {
|
|
27
|
+
projectId: string;
|
|
28
|
+
previewUrl: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const STYLE_CATEGORIES = {
|
|
32
|
+
layout: ["display", "position", "width", "height", "margin", "padding"],
|
|
33
|
+
typography: ["font-size", "font-weight", "color", "text-align", "line-height"],
|
|
34
|
+
background: ["background-color", "background-image", "opacity"],
|
|
35
|
+
border: ["border", "border-radius", "box-shadow"],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function VisualEditor({ projectId, previewUrl }: VisualEditorProps) {
|
|
39
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
40
|
+
const [isEnabled, setIsEnabled] = useState(false);
|
|
41
|
+
const [selectedElement, setSelectedElement] = useState<SelectedElement | null>(null);
|
|
42
|
+
const [hoveredRect, setHoveredRect] = useState<DOMRect | null>(null);
|
|
43
|
+
const [editedStyles, setEditedStyles] = useState<Record<string, string>>({});
|
|
44
|
+
|
|
45
|
+
// Inject editor script into iframe
|
|
46
|
+
const injectEditorScript = useCallback(() => {
|
|
47
|
+
const iframe = iframeRef.current;
|
|
48
|
+
if (!iframe?.contentWindow) return;
|
|
49
|
+
|
|
50
|
+
const script = `
|
|
51
|
+
(function() {
|
|
52
|
+
if (window.__visualEditorInjected) return;
|
|
53
|
+
window.__visualEditorInjected = true;
|
|
54
|
+
|
|
55
|
+
let hoveredElement = null;
|
|
56
|
+
let selectedElement = null;
|
|
57
|
+
const overlay = document.createElement('div');
|
|
58
|
+
overlay.id = '__visual-editor-overlay';
|
|
59
|
+
overlay.style.cssText = 'position:fixed;pointer-events:none;z-index:99999;border:2px solid #3b82f6;background:rgba(59,130,246,0.1);transition:all 0.1s;display:none;';
|
|
60
|
+
document.body.appendChild(overlay);
|
|
61
|
+
|
|
62
|
+
const selectionOverlay = document.createElement('div');
|
|
63
|
+
selectionOverlay.id = '__visual-editor-selection';
|
|
64
|
+
selectionOverlay.style.cssText = 'position:fixed;pointer-events:none;z-index:99998;border:2px solid #10b981;background:rgba(16,185,129,0.1);display:none;';
|
|
65
|
+
document.body.appendChild(selectionOverlay);
|
|
66
|
+
|
|
67
|
+
function getSelector(el) {
|
|
68
|
+
if (el.id) return '#' + el.id;
|
|
69
|
+
if (el.className && typeof el.className === 'string') {
|
|
70
|
+
const classes = el.className.split(' ').filter(c => c && !c.startsWith('__'));
|
|
71
|
+
if (classes.length) return el.tagName.toLowerCase() + '.' + classes.join('.');
|
|
72
|
+
}
|
|
73
|
+
return el.tagName.toLowerCase();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getStyles(el) {
|
|
77
|
+
const computed = window.getComputedStyle(el);
|
|
78
|
+
return {
|
|
79
|
+
'display': computed.display,
|
|
80
|
+
'position': computed.position,
|
|
81
|
+
'width': computed.width,
|
|
82
|
+
'height': computed.height,
|
|
83
|
+
'margin': computed.margin,
|
|
84
|
+
'padding': computed.padding,
|
|
85
|
+
'font-size': computed.fontSize,
|
|
86
|
+
'font-weight': computed.fontWeight,
|
|
87
|
+
'color': computed.color,
|
|
88
|
+
'text-align': computed.textAlign,
|
|
89
|
+
'line-height': computed.lineHeight,
|
|
90
|
+
'background-color': computed.backgroundColor,
|
|
91
|
+
'border': computed.border,
|
|
92
|
+
'border-radius': computed.borderRadius,
|
|
93
|
+
'box-shadow': computed.boxShadow,
|
|
94
|
+
'opacity': computed.opacity,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
document.addEventListener('mousemove', function(e) {
|
|
99
|
+
if (!window.__visualEditorEnabled) return;
|
|
100
|
+
|
|
101
|
+
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
102
|
+
if (!el || el === overlay || el === selectionOverlay) return;
|
|
103
|
+
if (el === hoveredElement) return;
|
|
104
|
+
|
|
105
|
+
hoveredElement = el;
|
|
106
|
+
const rect = el.getBoundingClientRect();
|
|
107
|
+
overlay.style.display = 'block';
|
|
108
|
+
overlay.style.left = rect.left + 'px';
|
|
109
|
+
overlay.style.top = rect.top + 'px';
|
|
110
|
+
overlay.style.width = rect.width + 'px';
|
|
111
|
+
overlay.style.height = rect.height + 'px';
|
|
112
|
+
|
|
113
|
+
window.parent.postMessage({
|
|
114
|
+
type: 'visualEditor:hover',
|
|
115
|
+
rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height }
|
|
116
|
+
}, '*');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
document.addEventListener('click', function(e) {
|
|
120
|
+
if (!window.__visualEditorEnabled) return;
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
e.stopPropagation();
|
|
123
|
+
|
|
124
|
+
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
125
|
+
if (!el || el === overlay || el === selectionOverlay) return;
|
|
126
|
+
|
|
127
|
+
selectedElement = el;
|
|
128
|
+
const rect = el.getBoundingClientRect();
|
|
129
|
+
selectionOverlay.style.display = 'block';
|
|
130
|
+
selectionOverlay.style.left = rect.left + 'px';
|
|
131
|
+
selectionOverlay.style.top = rect.top + 'px';
|
|
132
|
+
selectionOverlay.style.width = rect.width + 'px';
|
|
133
|
+
selectionOverlay.style.height = rect.height + 'px';
|
|
134
|
+
|
|
135
|
+
window.parent.postMessage({
|
|
136
|
+
type: 'visualEditor:select',
|
|
137
|
+
element: {
|
|
138
|
+
selector: getSelector(el),
|
|
139
|
+
tagName: el.tagName.toLowerCase(),
|
|
140
|
+
className: el.className || '',
|
|
141
|
+
id: el.id || '',
|
|
142
|
+
styles: getStyles(el),
|
|
143
|
+
rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height }
|
|
144
|
+
}
|
|
145
|
+
}, '*');
|
|
146
|
+
}, true);
|
|
147
|
+
|
|
148
|
+
window.addEventListener('message', function(e) {
|
|
149
|
+
if (e.data.type === 'visualEditor:enable') {
|
|
150
|
+
window.__visualEditorEnabled = true;
|
|
151
|
+
document.body.style.cursor = 'crosshair';
|
|
152
|
+
} else if (e.data.type === 'visualEditor:disable') {
|
|
153
|
+
window.__visualEditorEnabled = false;
|
|
154
|
+
document.body.style.cursor = '';
|
|
155
|
+
overlay.style.display = 'none';
|
|
156
|
+
selectionOverlay.style.display = 'none';
|
|
157
|
+
} else if (e.data.type === 'visualEditor:updateStyle' && selectedElement) {
|
|
158
|
+
selectedElement.style[e.data.property] = e.data.value;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
})();
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const doc = iframe.contentDocument;
|
|
166
|
+
if (doc) {
|
|
167
|
+
// Remove existing script if any
|
|
168
|
+
const existing = doc.getElementById("__visual-editor-script");
|
|
169
|
+
if (existing) existing.remove();
|
|
170
|
+
|
|
171
|
+
const scriptEl = doc.createElement("script");
|
|
172
|
+
scriptEl.id = "__visual-editor-script";
|
|
173
|
+
scriptEl.textContent = script;
|
|
174
|
+
doc.body.appendChild(scriptEl);
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
// Cross-origin - can't inject script
|
|
178
|
+
console.warn("Visual editor: Cannot inject script (cross-origin)");
|
|
179
|
+
}
|
|
180
|
+
}, []);
|
|
181
|
+
|
|
182
|
+
// Handle messages from iframe
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
const handleMessage = (e: MessageEvent) => {
|
|
185
|
+
if (e.data.type === "visualEditor:hover") {
|
|
186
|
+
setHoveredRect(e.data.rect);
|
|
187
|
+
} else if (e.data.type === "visualEditor:select") {
|
|
188
|
+
setSelectedElement(e.data.element);
|
|
189
|
+
setEditedStyles(e.data.element.styles);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
window.addEventListener("message", handleMessage);
|
|
194
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
195
|
+
}, []);
|
|
196
|
+
|
|
197
|
+
// Toggle editor mode
|
|
198
|
+
const toggleEditor = useCallback(() => {
|
|
199
|
+
const iframe = iframeRef.current;
|
|
200
|
+
if (!iframe?.contentWindow) return;
|
|
201
|
+
|
|
202
|
+
if (!isEnabled) {
|
|
203
|
+
injectEditorScript();
|
|
204
|
+
iframe.contentWindow.postMessage({ type: "visualEditor:enable" }, "*");
|
|
205
|
+
} else {
|
|
206
|
+
iframe.contentWindow.postMessage({ type: "visualEditor:disable" }, "*");
|
|
207
|
+
setSelectedElement(null);
|
|
208
|
+
setHoveredRect(null);
|
|
209
|
+
}
|
|
210
|
+
setIsEnabled(!isEnabled);
|
|
211
|
+
}, [isEnabled, injectEditorScript]);
|
|
212
|
+
|
|
213
|
+
// Update style
|
|
214
|
+
const updateStyle = (property: string, value: string) => {
|
|
215
|
+
const iframe = iframeRef.current;
|
|
216
|
+
if (!iframe?.contentWindow || !selectedElement) return;
|
|
217
|
+
|
|
218
|
+
setEditedStyles((prev) => ({ ...prev, [property]: value }));
|
|
219
|
+
iframe.contentWindow.postMessage(
|
|
220
|
+
{ type: "visualEditor:updateStyle", property, value },
|
|
221
|
+
"*"
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Generate Tailwind classes (simplified)
|
|
226
|
+
const generateTailwindClass = (property: string, value: string): string => {
|
|
227
|
+
// This is a simplified version - full implementation would need more mappings
|
|
228
|
+
const mappings: Record<string, Record<string, string>> = {
|
|
229
|
+
display: { flex: "flex", grid: "grid", block: "block", none: "hidden" },
|
|
230
|
+
"text-align": { center: "text-center", left: "text-left", right: "text-right" },
|
|
231
|
+
"font-weight": { "700": "font-bold", "600": "font-semibold", "400": "font-normal" },
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
return mappings[property]?.[value] || "";
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<div className="flex h-full flex-col">
|
|
239
|
+
{/* Header */}
|
|
240
|
+
<div className="flex items-center justify-between border-b px-4 py-3">
|
|
241
|
+
<h3 className="font-medium flex items-center gap-2">
|
|
242
|
+
<MousePointer2 className="h-4 w-4" />
|
|
243
|
+
Visual Editor
|
|
244
|
+
</h3>
|
|
245
|
+
<Button
|
|
246
|
+
variant={isEnabled ? "secondary" : "outline"}
|
|
247
|
+
size="sm"
|
|
248
|
+
onClick={toggleEditor}
|
|
249
|
+
>
|
|
250
|
+
{isEnabled ? "Disable" : "Enable"} Inspector
|
|
251
|
+
</Button>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<div className="flex flex-1 overflow-hidden">
|
|
255
|
+
{/* Preview */}
|
|
256
|
+
<div className="flex-1 bg-muted/30 relative">
|
|
257
|
+
<iframe
|
|
258
|
+
ref={iframeRef}
|
|
259
|
+
src={previewUrl}
|
|
260
|
+
className="w-full h-full border-0"
|
|
261
|
+
onLoad={injectEditorScript}
|
|
262
|
+
/>
|
|
263
|
+
{isEnabled && (
|
|
264
|
+
<div className="absolute top-2 left-2 px-2 py-1 bg-blue-500 text-white text-xs rounded">
|
|
265
|
+
Inspector Mode
|
|
266
|
+
</div>
|
|
267
|
+
)}
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
{/* Style Panel */}
|
|
271
|
+
{selectedElement && (
|
|
272
|
+
<div className="w-72 border-l overflow-auto">
|
|
273
|
+
<div className="p-4 space-y-4">
|
|
274
|
+
{/* Element Info */}
|
|
275
|
+
<div className="space-y-2">
|
|
276
|
+
<div className="flex items-center justify-between">
|
|
277
|
+
<span className="text-sm font-medium">Selected Element</span>
|
|
278
|
+
<Button
|
|
279
|
+
variant="ghost"
|
|
280
|
+
size="sm"
|
|
281
|
+
onClick={() => setSelectedElement(null)}
|
|
282
|
+
className="h-6 w-6 p-0"
|
|
283
|
+
>
|
|
284
|
+
<X className="h-4 w-4" />
|
|
285
|
+
</Button>
|
|
286
|
+
</div>
|
|
287
|
+
<code className="block text-xs bg-muted p-2 rounded">
|
|
288
|
+
{selectedElement.selector}
|
|
289
|
+
</code>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{/* Layout */}
|
|
293
|
+
<div className="space-y-2">
|
|
294
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
295
|
+
<Box className="h-4 w-4" />
|
|
296
|
+
Layout
|
|
297
|
+
</div>
|
|
298
|
+
{STYLE_CATEGORIES.layout.map((prop) => (
|
|
299
|
+
<div key={prop} className="flex items-center gap-2">
|
|
300
|
+
<label className="text-xs text-muted-foreground w-20 shrink-0">
|
|
301
|
+
{prop}
|
|
302
|
+
</label>
|
|
303
|
+
<Input
|
|
304
|
+
value={editedStyles[prop] || ""}
|
|
305
|
+
onChange={(e) => updateStyle(prop, e.target.value)}
|
|
306
|
+
className="h-7 text-xs font-mono"
|
|
307
|
+
/>
|
|
308
|
+
</div>
|
|
309
|
+
))}
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
{/* Typography */}
|
|
313
|
+
<div className="space-y-2">
|
|
314
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
315
|
+
<Type className="h-4 w-4" />
|
|
316
|
+
Typography
|
|
317
|
+
</div>
|
|
318
|
+
{STYLE_CATEGORIES.typography.map((prop) => (
|
|
319
|
+
<div key={prop} className="flex items-center gap-2">
|
|
320
|
+
<label className="text-xs text-muted-foreground w-20 shrink-0">
|
|
321
|
+
{prop}
|
|
322
|
+
</label>
|
|
323
|
+
<Input
|
|
324
|
+
value={editedStyles[prop] || ""}
|
|
325
|
+
onChange={(e) => updateStyle(prop, e.target.value)}
|
|
326
|
+
className="h-7 text-xs font-mono"
|
|
327
|
+
/>
|
|
328
|
+
</div>
|
|
329
|
+
))}
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
{/* Background */}
|
|
333
|
+
<div className="space-y-2">
|
|
334
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
335
|
+
<Palette className="h-4 w-4" />
|
|
336
|
+
Background
|
|
337
|
+
</div>
|
|
338
|
+
{STYLE_CATEGORIES.background.map((prop) => (
|
|
339
|
+
<div key={prop} className="flex items-center gap-2">
|
|
340
|
+
<label className="text-xs text-muted-foreground w-20 shrink-0">
|
|
341
|
+
{prop}
|
|
342
|
+
</label>
|
|
343
|
+
<Input
|
|
344
|
+
value={editedStyles[prop] || ""}
|
|
345
|
+
onChange={(e) => updateStyle(prop, e.target.value)}
|
|
346
|
+
className="h-7 text-xs font-mono"
|
|
347
|
+
/>
|
|
348
|
+
</div>
|
|
349
|
+
))}
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
{/* Border */}
|
|
353
|
+
<div className="space-y-2">
|
|
354
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
355
|
+
<Maximize2 className="h-4 w-4" />
|
|
356
|
+
Border
|
|
357
|
+
</div>
|
|
358
|
+
{STYLE_CATEGORIES.border.map((prop) => (
|
|
359
|
+
<div key={prop} className="flex items-center gap-2">
|
|
360
|
+
<label className="text-xs text-muted-foreground w-20 shrink-0">
|
|
361
|
+
{prop}
|
|
362
|
+
</label>
|
|
363
|
+
<Input
|
|
364
|
+
value={editedStyles[prop] || ""}
|
|
365
|
+
onChange={(e) => updateStyle(prop, e.target.value)}
|
|
366
|
+
className="h-7 text-xs font-mono"
|
|
367
|
+
/>
|
|
368
|
+
</div>
|
|
369
|
+
))}
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
{/* Note */}
|
|
373
|
+
<p className="text-xs text-muted-foreground mt-4">
|
|
374
|
+
Changes are preview only. Use the AI to update source code.
|
|
375
|
+
</p>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
)}
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
);
|
|
382
|
+
}
|
|
@@ -5,7 +5,19 @@ import { ChatPanel } from "@/components/chat/ChatPanel";
|
|
|
5
5
|
import { PreviewPanel } from "@/components/preview/PreviewPanel";
|
|
6
6
|
import { FileExplorer } from "@/components/file/FileExplorer";
|
|
7
7
|
import { FileViewer } from "@/components/file/FileViewer";
|
|
8
|
-
import {
|
|
8
|
+
import { DatabasePanel } from "@/components/database/DatabasePanel";
|
|
9
|
+
import { TestRunner } from "@/components/testing/TestRunner";
|
|
10
|
+
import { CheckpointPanel } from "@/components/checkpoint/CheckpointPanel";
|
|
11
|
+
import { EnvPanel } from "@/components/env/EnvPanel";
|
|
12
|
+
import {
|
|
13
|
+
FolderTree,
|
|
14
|
+
X,
|
|
15
|
+
Eye,
|
|
16
|
+
Database,
|
|
17
|
+
FlaskConical,
|
|
18
|
+
GitBranch,
|
|
19
|
+
Settings2,
|
|
20
|
+
} from "lucide-react";
|
|
9
21
|
import { Button } from "@/components/ui/button";
|
|
10
22
|
import { useTranslation } from "@/lib/i18n";
|
|
11
23
|
|
|
@@ -19,10 +31,21 @@ interface SelectedFile {
|
|
|
19
31
|
extension: string;
|
|
20
32
|
}
|
|
21
33
|
|
|
34
|
+
type RightPanelTab = "preview" | "database" | "testing" | "checkpoint" | "env";
|
|
35
|
+
|
|
36
|
+
const tabConfig: { id: RightPanelTab; icon: React.ReactNode; label: string }[] = [
|
|
37
|
+
{ id: "preview", icon: <Eye className="h-4 w-4" />, label: "Preview" },
|
|
38
|
+
{ id: "database", icon: <Database className="h-4 w-4" />, label: "Database" },
|
|
39
|
+
{ id: "testing", icon: <FlaskConical className="h-4 w-4" />, label: "Testing" },
|
|
40
|
+
{ id: "checkpoint", icon: <GitBranch className="h-4 w-4" />, label: "Checkpoint" },
|
|
41
|
+
{ id: "env", icon: <Settings2 className="h-4 w-4" />, label: "Env" },
|
|
42
|
+
];
|
|
43
|
+
|
|
22
44
|
export function WorkspaceLayout({ projectId }: WorkspaceLayoutProps) {
|
|
23
45
|
const { t } = useTranslation();
|
|
24
46
|
const [showFileExplorer, setShowFileExplorer] = useState(false);
|
|
25
47
|
const [selectedFile, setSelectedFile] = useState<SelectedFile | null>(null);
|
|
48
|
+
const [activeTab, setActiveTab] = useState<RightPanelTab>("preview");
|
|
26
49
|
|
|
27
50
|
const handleFileSelect = (path: string, content: string) => {
|
|
28
51
|
const extension = path.split(".").pop() || "";
|
|
@@ -33,6 +56,23 @@ export function WorkspaceLayout({ projectId }: WorkspaceLayoutProps) {
|
|
|
33
56
|
setSelectedFile(null);
|
|
34
57
|
};
|
|
35
58
|
|
|
59
|
+
const renderTabContent = () => {
|
|
60
|
+
switch (activeTab) {
|
|
61
|
+
case "preview":
|
|
62
|
+
return <PreviewPanel projectId={projectId} />;
|
|
63
|
+
case "database":
|
|
64
|
+
return <DatabasePanel projectId={projectId} />;
|
|
65
|
+
case "testing":
|
|
66
|
+
return <TestRunner projectId={projectId} />;
|
|
67
|
+
case "checkpoint":
|
|
68
|
+
return <CheckpointPanel projectId={projectId} />;
|
|
69
|
+
case "env":
|
|
70
|
+
return <EnvPanel projectId={projectId} />;
|
|
71
|
+
default:
|
|
72
|
+
return <PreviewPanel projectId={projectId} />;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
36
76
|
return (
|
|
37
77
|
<div className="flex h-[calc(100vh-3.5rem)]">
|
|
38
78
|
{/* File Explorer Toggle Button */}
|
|
@@ -65,12 +105,34 @@ export function WorkspaceLayout({ projectId }: WorkspaceLayoutProps) {
|
|
|
65
105
|
<ChatPanel projectId={projectId} />
|
|
66
106
|
</div>
|
|
67
107
|
|
|
68
|
-
{/*
|
|
108
|
+
{/* Right Panel - 70% width */}
|
|
69
109
|
<div
|
|
70
|
-
className="flex-1"
|
|
110
|
+
className="flex-1 flex flex-col"
|
|
71
111
|
style={{ width: showFileExplorer ? "calc(70%)" : "70%" }}
|
|
72
112
|
>
|
|
73
|
-
|
|
113
|
+
{/* Tab Bar */}
|
|
114
|
+
<div className="flex items-center border-b bg-muted/30 px-2">
|
|
115
|
+
{tabConfig.map((tab) => (
|
|
116
|
+
<button
|
|
117
|
+
key={tab.id}
|
|
118
|
+
onClick={() => setActiveTab(tab.id)}
|
|
119
|
+
className={`flex items-center gap-1.5 px-3 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
|
|
120
|
+
activeTab === tab.id
|
|
121
|
+
? "border-primary text-primary"
|
|
122
|
+
: "border-transparent text-muted-foreground hover:text-foreground"
|
|
123
|
+
}`}
|
|
124
|
+
title={tab.label}
|
|
125
|
+
>
|
|
126
|
+
{tab.icon}
|
|
127
|
+
<span className="hidden lg:inline">{tab.label}</span>
|
|
128
|
+
</button>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{/* Tab Content */}
|
|
133
|
+
<div className="flex-1 overflow-hidden">
|
|
134
|
+
{renderTabContent()}
|
|
135
|
+
</div>
|
|
74
136
|
</div>
|
|
75
137
|
|
|
76
138
|
{/* File Viewer Modal */}
|
package/apps/web/src/lib/api.ts
CHANGED
|
@@ -46,8 +46,11 @@ export const api = {
|
|
|
46
46
|
method: "PUT",
|
|
47
47
|
body: data ? JSON.stringify(data) : undefined,
|
|
48
48
|
}),
|
|
49
|
-
delete: <T>(endpoint: string) =>
|
|
50
|
-
request<T>(endpoint, {
|
|
49
|
+
delete: <T>(endpoint: string, data?: unknown) =>
|
|
50
|
+
request<T>(endpoint, {
|
|
51
|
+
method: "DELETE",
|
|
52
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
53
|
+
}),
|
|
51
54
|
|
|
52
55
|
uploadFiles: async (
|
|
53
56
|
projectId: string,
|