Add Craft.js site builder (v2) - complete rebuild from GrapesJS

Rebuilt the visual site builder from scratch using Craft.js, React 18,
and TypeScript. The new editor renders directly in the DOM (no iframe),
supports 40+ components, multi-page with shared header/footer, 16
templates, full-spectrum color/gradient controls, custom head code
injection, save/publish workflow, and auto-save.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-05 18:31:16 -07:00
parent b511a6684d
commit 91a6b6f34b
103 changed files with 26296 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
import React, { useState, useCallback } from 'react';
import { useEditor } from '@craftjs/core';
import { TopBar } from '../panels/topbar/TopBar';
import { LeftPanel } from '../panels/left/LeftPanel';
import { RightPanel } from '../panels/right/RightPanel';
import { Canvas } from './Canvas';
import { ContextMenu } from '../panels/context-menu/ContextMenu';
import { useContextMenu } from '../hooks/useContextMenu';
import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts';
import { DeviceMode } from '../types';
export const EditorShell: React.FC = () => {
const [device, setDevice] = useState<DeviceMode>('desktop');
const { menuState, show: showMenu, hide: hideMenu } = useContextMenu();
const { query } = useEditor();
// Register keyboard shortcuts
useKeyboardShortcuts();
const handleContextMenu = useCallback((e: React.MouseEvent) => {
e.preventDefault();
// Find the selected node id
let nodeId: string | null = null;
try {
const selected = query.getEvent('selected').all();
if (selected.length > 0) {
nodeId = selected[0];
}
} catch {
// No selection
}
showMenu(e.clientX, e.clientY, nodeId);
}, [query, showMenu]);
return (
<div className="editor-app">
<TopBar device={device} onDeviceChange={setDevice} />
<div className="editor-container">
<LeftPanel />
<div onContextMenu={handleContextMenu} style={{ flex: 1, display: 'flex', minWidth: 0 }}>
<Canvas device={device} />
</div>
<RightPanel />
</div>
<ContextMenu
visible={menuState.visible}
x={menuState.x}
y={menuState.y}
nodeId={menuState.nodeId}
onClose={hideMenu}
/>
</div>
);
};