From 1019a6384f3f40d9d99b2a418f187568933be3c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:55:43 +0000 Subject: [PATCH 01/14] Initial plan From 1bc65d99107107fbce84a4e3f1a3d7044fbcca25 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 18:02:40 +0000 Subject: [PATCH 02/14] Add core design management features - storage service, My Designs page, save/share functionality Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com> --- apps/playground/src/App.tsx | 2 + apps/playground/src/pages/Home.tsx | 31 +- apps/playground/src/pages/MyDesigns.tsx | 408 ++++++++++++++++++ apps/playground/src/pages/Studio.tsx | 205 ++++++++- apps/playground/src/services/designStorage.ts | 178 ++++++++ 5 files changed, 806 insertions(+), 18 deletions(-) create mode 100644 apps/playground/src/pages/MyDesigns.tsx create mode 100644 apps/playground/src/services/designStorage.ts diff --git a/apps/playground/src/App.tsx b/apps/playground/src/App.tsx index d9d93da86..bf24eb94f 100644 --- a/apps/playground/src/App.tsx +++ b/apps/playground/src/App.tsx @@ -1,6 +1,7 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { Home } from './pages/Home'; import { Studio } from './pages/Studio'; +import { MyDesigns } from './pages/MyDesigns'; import '@object-ui/components'; // Import lazy-loaded plugins @@ -17,6 +18,7 @@ export default function App() { } /> + } /> } /> {/* Default redirect to first example if typed manually specifically */} } /> diff --git a/apps/playground/src/pages/Home.tsx b/apps/playground/src/pages/Home.tsx index 0f5acd25b..63b35ee33 100644 --- a/apps/playground/src/pages/Home.tsx +++ b/apps/playground/src/pages/Home.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { exampleCategories } from '../data/examples'; -import { LayoutTemplate, ArrowRight, Component, Layers, Database, Shield, Box } from 'lucide-react'; +import { LayoutTemplate, ArrowRight, Component, Layers, Database, Shield, Box, FolderOpen } from 'lucide-react'; const CategoryIcon = ({ name }: { name: string }) => { switch (name) { @@ -29,16 +29,25 @@ export const Home = () => { Object UI Studio - - - - - GitHub - +
+ + + + + + GitHub + +
diff --git a/apps/playground/src/pages/MyDesigns.tsx b/apps/playground/src/pages/MyDesigns.tsx new file mode 100644 index 000000000..af49bb61b --- /dev/null +++ b/apps/playground/src/pages/MyDesigns.tsx @@ -0,0 +1,408 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { designStorage, Design } from '../services/designStorage'; +import { + Plus, + FileJson, + Trash2, + Copy, + Share2, + Download, + ArrowLeft, + Clock, + Tag, + Search, + Grid3x3, + List +} from 'lucide-react'; + +export const MyDesigns = () => { + const navigate = useNavigate(); + const [designs, setDesigns] = useState([]); + const [searchQuery, setSearchQuery] = useState(''); + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); + const [showImportModal, setShowImportModal] = useState(false); + const [importJson, setImportJson] = useState(''); + const [importName, setImportName] = useState(''); + + useEffect(() => { + loadDesigns(); + }, []); + + const loadDesigns = () => { + setDesigns(designStorage.getAllDesigns()); + }; + + const handleDelete = (id: string) => { + if (confirm('Are you sure you want to delete this design?')) { + designStorage.deleteDesign(id); + loadDesigns(); + } + }; + + const handleClone = (id: string) => { + try { + const cloned = designStorage.cloneDesign(id); + loadDesigns(); + navigate(`/studio/${cloned.id}`); + } catch (error) { + alert('Error cloning design: ' + (error as Error).message); + } + }; + + const handleShare = (id: string) => { + try { + const shareUrl = designStorage.shareDesign(id); + navigator.clipboard.writeText(shareUrl); + alert('Share link copied to clipboard!'); + } catch (error) { + alert('Error sharing design: ' + (error as Error).message); + } + }; + + const handleExport = (id: string) => { + try { + const json = designStorage.exportDesign(id); + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `design-${id}.json`; + a.click(); + URL.revokeObjectURL(url); + } catch (error) { + alert('Error exporting design: ' + (error as Error).message); + } + }; + + const handleImport = () => { + try { + const imported = designStorage.importDesign(importJson, importName || undefined); + setShowImportModal(false); + setImportJson(''); + setImportName(''); + loadDesigns(); + navigate(`/studio/${imported.id}`); + } catch (error) { + alert('Error importing design: ' + (error as Error).message); + } + }; + + const filteredDesigns = designs.filter(d => + d.name.toLowerCase().includes(searchQuery.toLowerCase()) || + d.description?.toLowerCase().includes(searchQuery.toLowerCase()) || + d.tags?.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())) + ); + + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); + }; + + return ( +
+ {/* Header */} +
+
+
+ + + My Designs + +
+
+ + +
+
+
+ + {/* Main Content */} +
+ {/* Search and View Controls */} +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 pr-4 py-2 border-2 border-gray-200 rounded-xl focus:border-indigo-500 focus:outline-none transition-colors" + /> +
+
+ + +
+
+ + {/* Designs Display */} + {filteredDesigns.length === 0 ? ( +
+
+ +
+

+ {searchQuery ? 'No designs found' : 'No designs yet'} +

+

+ {searchQuery ? 'Try a different search term' : 'Create your first design or import a template'} +

+ {!searchQuery && ( + + )} +
+ ) : viewMode === 'grid' ? ( +
+ {filteredDesigns.map((design) => ( +
+
navigate(`/studio/${design.id}`)} + className="p-6 border-b border-gray-100" + > +

+ {design.name} +

+ {design.description && ( +

+ {design.description} +

+ )} +
+ + {formatDate(design.updatedAt)} +
+ {design.tags && design.tags.length > 0 && ( +
+ {design.tags.map((tag) => ( + + + {tag} + + ))} +
+ )} +
+
+ + + + +
+
+ ))} +
+ ) : ( +
+ {filteredDesigns.map((design, index) => ( +
navigate(`/studio/${design.id}`)} + > +
+

{design.name}

+ {design.description && ( +

{design.description}

+ )} +
+ + + {formatDate(design.updatedAt)} + + {design.tags && design.tags.length > 0 && ( + + + {design.tags.join(', ')} + + )} +
+
+
+ + + + +
+
+ ))} +
+ )} +
+ + {/* Import Modal */} + {showImportModal && ( +
+
+
+

Import Design

+

Paste your JSON schema below

+
+
+
+ + setImportName(e.target.value)} + placeholder="My Imported Design" + className="w-full px-4 py-2 border-2 border-gray-200 rounded-xl focus:border-indigo-500 focus:outline-none transition-colors" + /> +
+
+ +