From ea7a6c474927ef4b602be231f2564483b4f98a77 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Tue, 23 Dec 2025 22:24:54 +0000 Subject: [PATCH] Generated by Spark: Docker style package system --> I can drop in a forum, guestbook, retro game site, youtube clone, spotify clone and its all powered by existing database systema and generic workslows / scripts etc --- PRD.md | 39 +- src/components/Level4.tsx | 13 +- src/components/PackageManager.tsx | 522 +++++++++++++++++++++++ src/components/QuickGuide.tsx | 81 +++- src/lib/database.ts | 52 +++ src/lib/package-catalog.ts | 664 ++++++++++++++++++++++++++++++ src/lib/package-types.ts | 41 ++ 7 files changed, 1398 insertions(+), 14 deletions(-) create mode 100644 src/components/PackageManager.tsx create mode 100644 src/lib/package-catalog.ts create mode 100644 src/lib/package-types.ts diff --git a/PRD.md b/PRD.md index 09b4298fc..da9c5b7a2 100644 --- a/PRD.md +++ b/PRD.md @@ -1,7 +1,7 @@ -# PRD: MetaBuilder Visual Configuration System +# PRD: MetaBuilder Visual Configuration & Package System ## Mission Statement -Transform the MetaBuilder god-tier panel from a technical, code-heavy interface into an intuitive visual configuration system that empowers both technical and non-technical users to build sophisticated applications through GUI-based tools. +Transform the MetaBuilder god-tier panel into an intuitive visual configuration system with a Docker-style package manager that allows users to instantly deploy complete pre-built applications while retaining full customization capabilities. ## Experience Qualities 1. **Intuitive** - Users should discover features naturally without extensive documentation, with visual cues guiding them through complex configurations. @@ -9,11 +9,25 @@ Transform the MetaBuilder god-tier panel from a technical, code-heavy interface 3. **Efficient** - Common tasks that previously required typing or memorization are now accomplished through point-and-click interactions, dramatically reducing configuration time. ## Complexity Level -**Complex Application** (advanced functionality with multiple views) - This is a meta-framework for building applications with four distinct user levels, database management, visual component builders, and dynamic configuration systems. +**Complex Application** (advanced functionality with multiple views) - This is a meta-framework for building applications with four distinct user levels, database management, visual component builders, dynamic configuration systems, and a package management system for deploying pre-built applications. ## Essential Features -### 1. CSS Class Builder +### 1. Docker-Style Package System +**Functionality:** Browse, install, and manage pre-built applications (forum, guestbook, video platform, music streaming, games, e-commerce) that integrate with existing infrastructure +**Purpose:** Allow users to rapidly deploy complete applications without building from scratch, leveraging existing database and workflow systems +**Trigger:** User navigates to "Packages" tab in god-tier panel +**Progression:** Browse packages → Filter by category → View details (schemas, pages, workflows included) → Install package → Schemas/pages/workflows automatically added → Enable/disable as needed → Uninstall to remove +**Success Criteria:** +- 6+ pre-built packages available (Forum, Guestbook, Video Platform, Music Platform, Games Arcade, E-Commerce) +- One-click installation adds all schemas, pages, workflows, and Lua scripts +- Packages can be enabled/disabled without uninstalling +- Package data stored separately from core application data +- Clear visualization of what each package includes +- Search and filter by category, rating, downloads +- Seed data automatically loaded with packages + +### 2. CSS Class Builder **Functionality:** Visual selector for Tailwind CSS classes organized into logical categories **Purpose:** Eliminate the need to memorize or type CSS class names, reducing errors and speeding up styling **Trigger:** User clicks palette icon next to any className field in PropertyInspector @@ -24,7 +38,7 @@ Transform the MetaBuilder god-tier panel from a technical, code-heavy interface - 200+ predefined classes organized into 10 categories - Custom class input available for edge cases -### 2. Dynamic Dropdown Configuration +### 3. Dynamic Dropdown Configuration **Functionality:** Centralized management of dropdown option sets usable across multiple components **Purpose:** Prevent duplication and ensure consistency when the same options appear in multiple places **Trigger:** User navigates to "Dropdowns" tab in god-tier panel or components reference dropdown by name @@ -35,7 +49,7 @@ Transform the MetaBuilder god-tier panel from a technical, code-heavy interface - Visual GUI for managing options (no JSON required) - Pre-loaded with common examples (status, priority, category) -### 3. CSS Class Library Manager +### 4. CSS Class Library Manager **Functionality:** Manage the catalog of CSS classes available in the builder **Purpose:** Allow customization of available classes and organization for project-specific needs **Trigger:** User navigates to "CSS Classes" tab in god-tier panel @@ -46,7 +60,7 @@ Transform the MetaBuilder god-tier panel from a technical, code-heavy interface - Changes immediately reflected in CSS Class Builder - System initializes with comprehensive Tailwind utilities -### 4. Monaco Code Editor Integration +### 5. Monaco Code Editor Integration **Functionality:** Professional-grade code editor for JSON and Lua with syntax highlighting and validation **Purpose:** When code editing is necessary, provide best-in-class tooling comparable to VS Code **Trigger:** User opens SchemaEditor, LuaEditor, or JsonEditor components @@ -59,7 +73,7 @@ Transform the MetaBuilder god-tier panel from a technical, code-heavy interface - Minimap for navigation - Find/replace functionality -### 5. Enhanced Property Inspector +### 6. Enhanced Property Inspector **Functionality:** Context-aware property editor with specialized controls for different data types **Purpose:** Provide the right UI control for each property type automatically **Trigger:** User selects component in builder @@ -72,7 +86,7 @@ Transform the MetaBuilder god-tier panel from a technical, code-heavy interface - className fields have CSS Builder button - All changes saved to component props -### 6. Quick Guide System +### 7. Quick Guide System **Functionality:** Interactive documentation and tutorials for new features **Purpose:** Help users discover and learn new visual configuration tools **Trigger:** User opens "Guide" tab (default tab in god-tier panel) @@ -84,6 +98,11 @@ Transform the MetaBuilder god-tier panel from a technical, code-heavy interface - Provides best practices and tips ## Edge Case Handling +- **Package conflicts** - System prevents installing packages with conflicting schema names, shows warning +- **Package uninstall with dependencies** - Warns if other packages depend on the one being uninstalled +- **Disabled package schemas** - Schemas from disabled packages remain in database but are marked inactive +- **Package version mismatches** - System tracks installed version, warns if updates available +- **Corrupted package data** - Validation ensures package manifests are complete before installation - **Invalid CSS class names** - Custom class input validates and warns about non-standard classes - **Deleted dropdown config still referenced** - PropertyInspector gracefully handles missing configs, shows warning - **Large CSS class lists** - Scrollable interface with search/filter to handle 1000+ classes @@ -93,7 +112,7 @@ Transform the MetaBuilder god-tier panel from a technical, code-heavy interface - **Import/export conflicts** - Monaco editor validates JSON before import, shows detailed errors ## Design Direction -The interface should feel like a professional design tool (Figma, Webflow) rather than a developer IDE. Visual hierarchy emphasizes actions over configuration details. Color coding distinguishes different tool types (CSS = primary purple, Dropdowns = accent cyan, Code = muted gray). +The interface should feel like a professional design tool (Figma, Webflow) with the convenience of a package manager (Docker, npm). The package system uses card-based layouts with rich metadata (download counts, ratings, categories) to help users make informed decisions. Visual hierarchy emphasizes actions over configuration details. Color coding distinguishes different tool types (Packages = purple gradient, CSS = primary purple, Dropdowns = accent cyan, Code = muted gray). ## Color Selection diff --git a/src/components/Level4.tsx b/src/components/Level4.tsx index 07ecf1a61..b5f342c9e 100644 --- a/src/components/Level4.tsx +++ b/src/components/Level4.tsx @@ -8,7 +8,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' -import { SignOut, Database as DatabaseIcon, Lightning, Code, Eye, House, Download, Upload, BookOpen, HardDrives, MapTrifold, Tree, Users, Gear, Palette, ListDashes, Sparkle } from '@phosphor-icons/react' +import { SignOut, Database as DatabaseIcon, Lightning, Code, Eye, House, Download, Upload, BookOpen, HardDrives, MapTrifold, Tree, Users, Gear, Palette, ListDashes, Sparkle, Package } from '@phosphor-icons/react' import { toast } from 'sonner' import { SchemaEditorLevel4 } from './SchemaEditorLevel4' import { WorkflowEditor } from './WorkflowEditor' @@ -22,6 +22,7 @@ import { GodCredentialsSettings } from './GodCredentialsSettings' import { CssClassManager } from './CssClassManager' import { DropdownConfigManager } from './DropdownConfigManager' import { QuickGuide } from './QuickGuide' +import { PackageManager } from './PackageManager' import { Database } from '@/lib/database' import { seedDatabase } from '@/lib/seed-data' import type { User as UserType, AppConfiguration } from '@/lib/level-types' @@ -204,11 +205,15 @@ export function Level4({ user, onLogout, onNavigate, onPreview }: Level4Props) { - + Guide + + + Packages + Page Routes @@ -259,6 +264,10 @@ export function Level4({ user, onLogout, onNavigate, onPreview }: Level4Props) { + + + + diff --git a/src/components/PackageManager.tsx b/src/components/PackageManager.tsx new file mode 100644 index 000000000..d35940bdb --- /dev/null +++ b/src/components/PackageManager.tsx @@ -0,0 +1,522 @@ +import { useState, useEffect } from 'react' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { Badge } from '@/components/ui/badge' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { ScrollArea } from '@/components/ui/scroll-area' +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Separator } from '@/components/ui/separator' +import { toast } from 'sonner' +import { Database } from '@/lib/database' +import { PACKAGE_CATALOG } from '@/lib/package-catalog' +import type { PackageManifest, PackageContent, InstalledPackage } from '@/lib/package-types' +import { Package, Download, Trash, Power, MagnifyingGlass, Star, Tag, User, TrendUp, Funnel } from '@phosphor-icons/react' + +interface PackageManagerProps { + onClose?: () => void +} + +export function PackageManager({ onClose }: PackageManagerProps) { + const [packages, setPackages] = useState([]) + const [installedPackages, setInstalledPackages] = useState([]) + const [selectedPackage, setSelectedPackage] = useState<{ manifest: PackageManifest; content: PackageContent } | null>(null) + const [searchQuery, setSearchQuery] = useState('') + const [categoryFilter, setCategoryFilter] = useState('all') + const [sortBy, setSortBy] = useState<'name' | 'downloads' | 'rating'>('downloads') + const [showDetails, setShowDetails] = useState(false) + const [installing, setInstalling] = useState(false) + + useEffect(() => { + loadPackages() + }, []) + + const loadPackages = async () => { + const installed = await Database.getInstalledPackages() + setInstalledPackages(installed) + + const allPackages = Object.values(PACKAGE_CATALOG).map(pkg => ({ + ...pkg.manifest, + installed: installed.some(ip => ip.packageId === pkg.manifest.id), + })) + + setPackages(allPackages) + } + + const handleInstallPackage = async (packageId: string) => { + setInstalling(true) + try { + const packageEntry = PACKAGE_CATALOG[packageId] + if (!packageEntry) { + toast.error('Package not found') + return + } + + const { content, manifest } = packageEntry + + const currentSchemas = await Database.getSchemas() + const currentPages = await Database.getPages() + const currentWorkflows = await Database.getWorkflows() + const currentLuaScripts = await Database.getLuaScripts() + const currentHierarchy = await Database.getComponentHierarchy() + const currentConfigs = await Database.getComponentConfigs() + + const newSchemas = [...currentSchemas, ...content.schemas] + const newPages = [...currentPages, ...content.pages] + const newWorkflows = [...currentWorkflows, ...content.workflows] + const newLuaScripts = [...currentLuaScripts, ...content.luaScripts] + const newHierarchy = { ...currentHierarchy, ...content.componentHierarchy } + const newConfigs = { ...currentConfigs, ...content.componentConfigs } + + await Database.setSchemas(newSchemas) + await Database.setPages(newPages) + await Database.setWorkflows(newWorkflows) + await Database.setLuaScripts(newLuaScripts) + await Database.setComponentHierarchy(newHierarchy) + await Database.setComponentConfigs(newConfigs) + + if (content.cssClasses) { + const currentCssClasses = await Database.getCssClasses() + await Database.setCssClasses([...currentCssClasses, ...content.cssClasses]) + } + + if (content.dropdownConfigs) { + const currentDropdowns = await Database.getDropdownConfigs() + await Database.setDropdownConfigs([...currentDropdowns, ...content.dropdownConfigs]) + } + + if (content.seedData) { + await Database.setPackageData(packageId, content.seedData) + } + + const installedPackage: InstalledPackage = { + packageId: manifest.id, + installedAt: Date.now(), + version: manifest.version, + enabled: true, + } + + await Database.installPackage(installedPackage) + + toast.success(`${manifest.name} installed successfully!`) + await loadPackages() + setShowDetails(false) + } catch (error) { + console.error('Installation error:', error) + toast.error('Failed to install package') + } finally { + setInstalling(false) + } + } + + const handleUninstallPackage = async (packageId: string) => { + try { + const packageEntry = PACKAGE_CATALOG[packageId] + if (!packageEntry) { + toast.error('Package not found') + return + } + + const { content, manifest } = packageEntry + + const currentSchemas = await Database.getSchemas() + const currentPages = await Database.getPages() + const currentWorkflows = await Database.getWorkflows() + const currentLuaScripts = await Database.getLuaScripts() + + const packageSchemaNames = content.schemas.map(s => s.name) + const packagePageIds = content.pages.map(p => p.id) + const packageWorkflowIds = content.workflows.map(w => w.id) + const packageLuaIds = content.luaScripts.map(l => l.id) + + const filteredSchemas = currentSchemas.filter(s => !packageSchemaNames.includes(s.name)) + const filteredPages = currentPages.filter(p => !packagePageIds.includes(p.id)) + const filteredWorkflows = currentWorkflows.filter(w => !packageWorkflowIds.includes(w.id)) + const filteredLuaScripts = currentLuaScripts.filter(l => !packageLuaIds.includes(l.id)) + + await Database.setSchemas(filteredSchemas) + await Database.setPages(filteredPages) + await Database.setWorkflows(filteredWorkflows) + await Database.setLuaScripts(filteredLuaScripts) + + await Database.deletePackageData(packageId) + await Database.uninstallPackage(packageId) + + toast.success(`${manifest.name} uninstalled successfully!`) + await loadPackages() + setShowDetails(false) + } catch (error) { + console.error('Uninstallation error:', error) + toast.error('Failed to uninstall package') + } + } + + const handleTogglePackage = async (packageId: string, enabled: boolean) => { + try { + await Database.togglePackageEnabled(packageId, enabled) + toast.success(enabled ? 'Package enabled' : 'Package disabled') + await loadPackages() + } catch (error) { + console.error('Toggle error:', error) + toast.error('Failed to toggle package') + } + } + + const filteredPackages = packages + .filter(pkg => { + const matchesSearch = + pkg.name.toLowerCase().includes(searchQuery.toLowerCase()) || + pkg.description.toLowerCase().includes(searchQuery.toLowerCase()) || + pkg.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())) + + const matchesCategory = categoryFilter === 'all' || pkg.category === categoryFilter + + return matchesSearch && matchesCategory + }) + .sort((a, b) => { + if (sortBy === 'name') return a.name.localeCompare(b.name) + if (sortBy === 'downloads') return b.downloadCount - a.downloadCount + if (sortBy === 'rating') return b.rating - a.rating + return 0 + }) + + const categories = ['all', ...Array.from(new Set(packages.map(p => p.category)))] + + const installedList = packages.filter(p => p.installed) + const availableList = packages.filter(p => !p.installed) + + return ( +
+
+
+
+ +
+
+

Package Manager

+

Install pre-built applications and features

+
+
+ {onClose && ( + + )} +
+ +
+ +
+ + All Packages + + Installed ({installedList.length}) + + + Available ({availableList.length}) + + +
+ +
+
+ + setSearchQuery(e.target.value)} + className="pl-10" + /> +
+ +
+ + + +
+
+ + + +
+ {filteredPackages.map(pkg => ( + ip.packageId === pkg.id)} + onViewDetails={() => { + setSelectedPackage(PACKAGE_CATALOG[pkg.id]) + setShowDetails(true) + }} + onToggle={handleTogglePackage} + /> + ))} +
+
+
+ + + +
+ {installedList.length === 0 ? ( +
+ +

No packages installed yet

+
+ ) : ( + installedList.map(pkg => ( + ip.packageId === pkg.id)} + onViewDetails={() => { + setSelectedPackage(PACKAGE_CATALOG[pkg.id]) + setShowDetails(true) + }} + onToggle={handleTogglePackage} + /> + )) + )} +
+
+
+ + + +
+ {availableList.map(pkg => ( + { + setSelectedPackage(PACKAGE_CATALOG[pkg.id]) + setShowDetails(true) + }} + onToggle={handleTogglePackage} + /> + ))} +
+
+
+
+
+ + + + {selectedPackage && ( + <> + +
+
+ {selectedPackage.manifest.icon} +
+
+ {selectedPackage.manifest.name} + {selectedPackage.manifest.description} +
+ {selectedPackage.manifest.category} +
+ + {selectedPackage.manifest.downloadCount.toLocaleString()} +
+
+ + {selectedPackage.manifest.rating} +
+
+
+
+
+ + + + +
+
+

Author

+
+ + {selectedPackage.manifest.author} +
+
+ +
+

Version

+

{selectedPackage.manifest.version}

+
+ +
+

Tags

+
+ {selectedPackage.manifest.tags.map(tag => ( + + + {tag} + + ))} +
+
+ +
+

Includes

+
+
+
Data Models
+
{selectedPackage.content.schemas.length}
+
+
+
Pages
+
{selectedPackage.content.pages.length}
+
+
+
Workflows
+
{selectedPackage.content.workflows.length}
+
+
+
Scripts
+
{selectedPackage.content.luaScripts.length}
+
+
+
+ + {selectedPackage.content.schemas.length > 0 && ( +
+

Data Models

+
+ {selectedPackage.content.schemas.map(schema => ( +
+
{schema.displayName || schema.name}
+
{schema.fields.length} fields
+
+ ))} +
+
+ )} + + {selectedPackage.content.pages.length > 0 && ( +
+

Pages

+
+ {selectedPackage.content.pages.map(page => ( +
+
{page.title}
+
{page.path}
+
+ ))} +
+
+ )} +
+
+ + + {selectedPackage.manifest.installed ? ( + + ) : ( + + )} + + + )} +
+
+
+ ) +} + +interface PackageCardProps { + package: PackageManifest + isInstalled: boolean + installedPackage?: InstalledPackage + onViewDetails: () => void + onToggle: (packageId: string, enabled: boolean) => void +} + +function PackageCard({ package: pkg, isInstalled, installedPackage, onViewDetails, onToggle }: PackageCardProps) { + return ( + + +
+
+ {pkg.icon} +
+
+ {pkg.name} + {pkg.description} +
+
+
+ + +
+ {pkg.category} + {isInstalled && ( + + {installedPackage?.enabled ? 'Active' : 'Disabled'} + + )} +
+ +
+
+ + {pkg.downloadCount.toLocaleString()} +
+
+ + {pkg.rating} +
+
+
+ + + + {isInstalled && installedPackage && ( + + )} + +
+ ) +} diff --git a/src/components/QuickGuide.tsx b/src/components/QuickGuide.tsx index 2fd8729c0..e9dcdb655 100644 --- a/src/components/QuickGuide.tsx +++ b/src/components/QuickGuide.tsx @@ -1,7 +1,7 @@ import { Card } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion' -import { Palette, ListDashes, Code, Sparkle } from '@phosphor-icons/react' +import { Palette, ListDashes, Code, Sparkle, Package } from '@phosphor-icons/react' export function QuickGuide() { return ( @@ -11,7 +11,36 @@ export function QuickGuide() {

Learn how to use the new visual configuration tools

-
+
+ +
+
+ +
+
+

Package System

+ Docker-Style Apps +
+
+

+ Install complete pre-built applications like forums, guestbooks, video platforms, and e-commerce stores with one click! +

+
+
+
+ 6+ ready-to-use applications +
+
+
+ Automatic schema & workflow setup +
+
+
+ Enable/disable packages anytime +
+
+ +
@@ -73,6 +102,54 @@ export function QuickGuide() { + + +
+ + How to use the Package System +
+
+ +
+

Step 1: Browse available packages

+
    +
  • Go to the "Packages" tab
  • +
  • Browse packages by category (Social, Entertainment, Gaming, E-Commerce)
  • +
  • Use search and filters to find what you need
  • +
  • Click "View Details" to see what's included in each package
  • +
+
+
+

Step 2: Install a package

+
    +
  • Click "Install Package" in the details dialog
  • +
  • The system automatically adds schemas, pages, workflows, and Lua scripts
  • +
  • Seed data is loaded if included with the package
  • +
  • Package is enabled by default after installation
  • +
+
+
+

Step 3: Manage installed packages

+
    +
  • View installed packages in the "Installed" tab
  • +
  • Enable/disable packages with the power button
  • +
  • Uninstall packages to remove all associated data
  • +
  • Customize package schemas and pages as needed
  • +
+
+
+

Available Packages:

+
    +
  • Classic Forum - Discussion boards with threads and categories
  • +
  • Retro Guestbook - 90s-style visitor messages
  • +
  • Video Platform - YouTube-style video sharing
  • +
  • Music Streaming - Spotify-style music platform
  • +
  • Retro Games Arcade - Classic games with leaderboards
  • +
  • E-Commerce Store - Complete online shop
  • +
+
+
+
diff --git a/src/lib/database.ts b/src/lib/database.ts index 349ab7dac..c7d0edb7b 100644 --- a/src/lib/database.ts +++ b/src/lib/database.ts @@ -7,6 +7,7 @@ import type { Comment, } from './level-types' import type { ModelSchema } from './schema-types' +import type { InstalledPackage } from './package-types' export interface CssCategory { name: string @@ -77,6 +78,8 @@ export const DB_KEYS = { GOD_CREDENTIALS_EXPIRY_DURATION: 'db_god_credentials_expiry_duration', CSS_CLASSES: 'db_css_classes', DROPDOWN_CONFIGS: 'db_dropdown_configs', + INSTALLED_PACKAGES: 'db_installed_packages', + PACKAGE_DATA: 'db_package_data', } as const export async function hashPassword(password: string): Promise { @@ -672,4 +675,53 @@ export class Database { await window.spark.kv.delete(DB_KEYS.CSS_CLASSES) await window.spark.kv.delete(DB_KEYS.DROPDOWN_CONFIGS) } + + static async getInstalledPackages(): Promise { + return (await window.spark.kv.get(DB_KEYS.INSTALLED_PACKAGES)) || [] + } + + static async setInstalledPackages(packages: InstalledPackage[]): Promise { + await window.spark.kv.set(DB_KEYS.INSTALLED_PACKAGES, packages) + } + + static async installPackage(packageData: InstalledPackage): Promise { + const packages = await this.getInstalledPackages() + const exists = packages.find(p => p.packageId === packageData.packageId) + if (!exists) { + packages.push(packageData) + await this.setInstalledPackages(packages) + } + } + + static async uninstallPackage(packageId: string): Promise { + const packages = await this.getInstalledPackages() + const filtered = packages.filter(p => p.packageId !== packageId) + await this.setInstalledPackages(filtered) + } + + static async togglePackageEnabled(packageId: string, enabled: boolean): Promise { + const packages = await this.getInstalledPackages() + const pkg = packages.find(p => p.packageId === packageId) + if (pkg) { + pkg.enabled = enabled + await this.setInstalledPackages(packages) + } + } + + static async getPackageData(packageId: string): Promise> { + const allData = (await window.spark.kv.get>>(DB_KEYS.PACKAGE_DATA)) || {} + return allData[packageId] || {} + } + + static async setPackageData(packageId: string, data: Record): Promise { + const allData = (await window.spark.kv.get>>(DB_KEYS.PACKAGE_DATA)) || {} + allData[packageId] = data + await window.spark.kv.set(DB_KEYS.PACKAGE_DATA, allData) + } + + static async deletePackageData(packageId: string): Promise { + const allData = (await window.spark.kv.get>>(DB_KEYS.PACKAGE_DATA)) || {} + delete allData[packageId] + await window.spark.kv.set(DB_KEYS.PACKAGE_DATA, allData) + } } diff --git a/src/lib/package-catalog.ts b/src/lib/package-catalog.ts new file mode 100644 index 000000000..5b2eddb65 --- /dev/null +++ b/src/lib/package-catalog.ts @@ -0,0 +1,664 @@ +import type { PackageManifest, PackageContent } from './package-types' + +export const PACKAGE_CATALOG: Record = { + 'forum-classic': { + manifest: { + id: 'forum-classic', + name: 'Classic Forum', + version: '1.0.0', + description: 'Full-featured discussion forum with threads, categories, user profiles, and moderation tools. Perfect for building community discussions.', + author: 'MetaBuilder Team', + category: 'social', + icon: '💬', + screenshots: [], + tags: ['forum', 'discussion', 'community', 'threads'], + dependencies: [], + createdAt: Date.now(), + updatedAt: Date.now(), + downloadCount: 1247, + rating: 4.7, + installed: false, + }, + content: { + schemas: [ + { + name: 'ForumCategory', + displayName: 'Forum Category', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'name', type: 'string', label: 'Category Name', required: true }, + { name: 'description', type: 'text', label: 'Description', required: false }, + { name: 'order', type: 'number', label: 'Display Order', required: true, defaultValue: 0 }, + { name: 'icon', type: 'string', label: 'Icon', required: false }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + { + name: 'ForumThread', + displayName: 'Forum Thread', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'categoryId', type: 'string', label: 'Category ID', required: true }, + { name: 'title', type: 'string', label: 'Thread Title', required: true }, + { name: 'authorId', type: 'string', label: 'Author ID', required: true }, + { name: 'content', type: 'text', label: 'Content', required: true }, + { name: 'isPinned', type: 'boolean', label: 'Pinned', required: false, defaultValue: false }, + { name: 'isLocked', type: 'boolean', label: 'Locked', required: false, defaultValue: false }, + { name: 'views', type: 'number', label: 'View Count', required: true, defaultValue: 0 }, + { name: 'replyCount', type: 'number', label: 'Reply Count', required: true, defaultValue: 0 }, + { name: 'lastReplyAt', type: 'number', label: 'Last Reply At', required: false }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + { name: 'updatedAt', type: 'number', label: 'Updated At', required: false }, + ], + }, + { + name: 'ForumPost', + displayName: 'Forum Post', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'threadId', type: 'string', label: 'Thread ID', required: true }, + { name: 'authorId', type: 'string', label: 'Author ID', required: true }, + { name: 'content', type: 'text', label: 'Content', required: true }, + { name: 'likes', type: 'number', label: 'Like Count', required: true, defaultValue: 0 }, + { name: 'isEdited', type: 'boolean', label: 'Edited', required: false, defaultValue: false }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + { name: 'updatedAt', type: 'number', label: 'Updated At', required: false }, + ], + }, + ], + pages: [ + { + id: 'page_forum_home', + path: '/forum', + title: 'Forum Home', + level: 2, + componentTree: [], + requiresAuth: true, + requiredRole: 'user', + }, + { + id: 'page_forum_category', + path: '/forum/category/:id', + title: 'Forum Category', + level: 2, + componentTree: [], + requiresAuth: true, + requiredRole: 'user', + }, + { + id: 'page_forum_thread', + path: '/forum/thread/:id', + title: 'Forum Thread', + level: 2, + componentTree: [], + requiresAuth: true, + requiredRole: 'user', + }, + ], + workflows: [ + { + id: 'workflow_create_thread', + name: 'Create Forum Thread', + description: 'Workflow for creating a new forum thread', + nodes: [], + edges: [], + enabled: true, + }, + { + id: 'workflow_post_reply', + name: 'Post Forum Reply', + description: 'Workflow for posting a reply to a thread', + nodes: [], + edges: [], + enabled: true, + }, + ], + luaScripts: [ + { + id: 'lua_forum_thread_count', + name: 'Get Thread Count', + description: 'Count threads in a category', + code: 'function countThreads(categoryId)\n return 0\nend\nreturn countThreads', + parameters: [{ name: 'categoryId', type: 'string' }], + returnType: 'number', + }, + ], + componentHierarchy: {}, + componentConfigs: {}, + seedData: { + ForumCategory: [ + { id: 'cat_1', name: 'General Discussion', description: 'Talk about anything', order: 1, icon: '💭', createdAt: Date.now() }, + { id: 'cat_2', name: 'Announcements', description: 'Official announcements', order: 0, icon: '📢', createdAt: Date.now() }, + ], + }, + }, + }, + 'guestbook-retro': { + manifest: { + id: 'guestbook-retro', + name: 'Retro Guestbook', + version: '1.0.0', + description: 'Nostalgic 90s-style guestbook with animated GIFs, custom backgrounds, and visitor messages. Perfect for retro-themed websites.', + author: 'MetaBuilder Team', + category: 'content', + icon: '📖', + screenshots: [], + tags: ['guestbook', 'retro', '90s', 'nostalgia'], + dependencies: [], + createdAt: Date.now(), + updatedAt: Date.now(), + downloadCount: 892, + rating: 4.5, + installed: false, + }, + content: { + schemas: [ + { + name: 'GuestbookEntry', + displayName: 'Guestbook Entry', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'authorName', type: 'string', label: 'Name', required: true }, + { name: 'authorEmail', type: 'string', label: 'Email', required: false }, + { name: 'authorWebsite', type: 'string', label: 'Website', required: false }, + { name: 'message', type: 'text', label: 'Message', required: true }, + { name: 'backgroundColor', type: 'string', label: 'Background Color', required: false }, + { name: 'textColor', type: 'string', label: 'Text Color', required: false }, + { name: 'gifUrl', type: 'string', label: 'GIF URL', required: false }, + { name: 'approved', type: 'boolean', label: 'Approved', required: true, defaultValue: false }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + ], + pages: [ + { + id: 'page_guestbook', + path: '/guestbook', + title: 'Guestbook', + level: 1, + componentTree: [], + requiresAuth: false, + }, + ], + workflows: [], + luaScripts: [], + componentHierarchy: {}, + componentConfigs: {}, + seedData: { + GuestbookEntry: [ + { + id: 'entry_1', + authorName: 'WebMaster99', + authorWebsite: 'http://coolsite.net', + message: 'Cool site! Check out mine too!', + backgroundColor: '#FF00FF', + textColor: '#00FF00', + approved: true, + createdAt: Date.now() - 86400000 + }, + ], + }, + }, + }, + 'youtube-clone': { + manifest: { + id: 'youtube-clone', + name: 'Video Platform', + version: '1.0.0', + description: 'Complete video sharing platform with upload, streaming, comments, likes, subscriptions, and playlists. Build your own YouTube!', + author: 'MetaBuilder Team', + category: 'entertainment', + icon: '🎥', + screenshots: [], + tags: ['video', 'streaming', 'media', 'youtube'], + dependencies: [], + createdAt: Date.now(), + updatedAt: Date.now(), + downloadCount: 2156, + rating: 4.8, + installed: false, + }, + content: { + schemas: [ + { + name: 'Video', + displayName: 'Video', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'title', type: 'string', label: 'Title', required: true }, + { name: 'description', type: 'text', label: 'Description', required: false }, + { name: 'uploaderId', type: 'string', label: 'Uploader ID', required: true }, + { name: 'videoUrl', type: 'string', label: 'Video URL', required: true }, + { name: 'thumbnailUrl', type: 'string', label: 'Thumbnail URL', required: false }, + { name: 'duration', type: 'number', label: 'Duration (seconds)', required: true }, + { name: 'views', type: 'number', label: 'Views', required: true, defaultValue: 0 }, + { name: 'likes', type: 'number', label: 'Likes', required: true, defaultValue: 0 }, + { name: 'dislikes', type: 'number', label: 'Dislikes', required: true, defaultValue: 0 }, + { name: 'category', type: 'string', label: 'Category', required: false }, + { name: 'tags', type: 'json', label: 'Tags', required: false }, + { name: 'published', type: 'boolean', label: 'Published', required: true, defaultValue: false }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + { + name: 'VideoComment', + displayName: 'Video Comment', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'videoId', type: 'string', label: 'Video ID', required: true }, + { name: 'userId', type: 'string', label: 'User ID', required: true }, + { name: 'content', type: 'text', label: 'Content', required: true }, + { name: 'likes', type: 'number', label: 'Likes', required: true, defaultValue: 0 }, + { name: 'parentId', type: 'string', label: 'Parent Comment ID', required: false }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + { + name: 'Subscription', + displayName: 'Subscription', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'subscriberId', type: 'string', label: 'Subscriber ID', required: true }, + { name: 'channelId', type: 'string', label: 'Channel ID', required: true }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + { + name: 'Playlist', + displayName: 'Playlist', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'name', type: 'string', label: 'Name', required: true }, + { name: 'description', type: 'text', label: 'Description', required: false }, + { name: 'ownerId', type: 'string', label: 'Owner ID', required: true }, + { name: 'videoIds', type: 'json', label: 'Video IDs', required: true }, + { name: 'isPublic', type: 'boolean', label: 'Public', required: true, defaultValue: true }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + ], + pages: [ + { + id: 'page_video_home', + path: '/videos', + title: 'Video Home', + level: 2, + componentTree: [], + requiresAuth: false, + }, + { + id: 'page_video_watch', + path: '/watch/:id', + title: 'Watch Video', + level: 2, + componentTree: [], + requiresAuth: false, + }, + { + id: 'page_video_upload', + path: '/upload', + title: 'Upload Video', + level: 2, + componentTree: [], + requiresAuth: true, + requiredRole: 'user', + }, + { + id: 'page_channel', + path: '/channel/:id', + title: 'Channel', + level: 2, + componentTree: [], + requiresAuth: false, + }, + ], + workflows: [], + luaScripts: [], + componentHierarchy: {}, + componentConfigs: {}, + }, + }, + 'spotify-clone': { + manifest: { + id: 'spotify-clone', + name: 'Music Streaming Platform', + version: '1.0.0', + description: 'Full music streaming service with playlists, albums, artists, search, and playback controls. Create your own Spotify!', + author: 'MetaBuilder Team', + category: 'entertainment', + icon: '🎵', + screenshots: [], + tags: ['music', 'streaming', 'audio', 'spotify'], + dependencies: [], + createdAt: Date.now(), + updatedAt: Date.now(), + downloadCount: 1823, + rating: 4.6, + installed: false, + }, + content: { + schemas: [ + { + name: 'Artist', + displayName: 'Artist', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'name', type: 'string', label: 'Name', required: true }, + { name: 'bio', type: 'text', label: 'Biography', required: false }, + { name: 'imageUrl', type: 'string', label: 'Image URL', required: false }, + { name: 'genre', type: 'string', label: 'Genre', required: false }, + { name: 'verified', type: 'boolean', label: 'Verified', required: true, defaultValue: false }, + { name: 'followers', type: 'number', label: 'Followers', required: true, defaultValue: 0 }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + { + name: 'Album', + displayName: 'Album', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'title', type: 'string', label: 'Title', required: true }, + { name: 'artistId', type: 'string', label: 'Artist ID', required: true }, + { name: 'coverUrl', type: 'string', label: 'Cover URL', required: false }, + { name: 'releaseDate', type: 'number', label: 'Release Date', required: false }, + { name: 'genre', type: 'string', label: 'Genre', required: false }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + { + name: 'Track', + displayName: 'Track', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'title', type: 'string', label: 'Title', required: true }, + { name: 'artistId', type: 'string', label: 'Artist ID', required: true }, + { name: 'albumId', type: 'string', label: 'Album ID', required: false }, + { name: 'audioUrl', type: 'string', label: 'Audio URL', required: true }, + { name: 'duration', type: 'number', label: 'Duration (seconds)', required: true }, + { name: 'trackNumber', type: 'number', label: 'Track Number', required: false }, + { name: 'plays', type: 'number', label: 'Play Count', required: true, defaultValue: 0 }, + { name: 'likes', type: 'number', label: 'Likes', required: true, defaultValue: 0 }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + { + name: 'MusicPlaylist', + displayName: 'Playlist', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'name', type: 'string', label: 'Name', required: true }, + { name: 'description', type: 'text', label: 'Description', required: false }, + { name: 'ownerId', type: 'string', label: 'Owner ID', required: true }, + { name: 'coverUrl', type: 'string', label: 'Cover URL', required: false }, + { name: 'trackIds', type: 'json', label: 'Track IDs', required: true }, + { name: 'isPublic', type: 'boolean', label: 'Public', required: true, defaultValue: true }, + { name: 'followers', type: 'number', label: 'Followers', required: true, defaultValue: 0 }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + ], + pages: [ + { + id: 'page_music_home', + path: '/music', + title: 'Music Home', + level: 2, + componentTree: [], + requiresAuth: false, + }, + { + id: 'page_music_search', + path: '/search', + title: 'Search Music', + level: 2, + componentTree: [], + requiresAuth: false, + }, + { + id: 'page_music_artist', + path: '/artist/:id', + title: 'Artist', + level: 2, + componentTree: [], + requiresAuth: false, + }, + { + id: 'page_music_album', + path: '/album/:id', + title: 'Album', + level: 2, + componentTree: [], + requiresAuth: false, + }, + { + id: 'page_music_playlist', + path: '/playlist/:id', + title: 'Playlist', + level: 2, + componentTree: [], + requiresAuth: false, + }, + ], + workflows: [], + luaScripts: [], + componentHierarchy: {}, + componentConfigs: {}, + }, + }, + 'retro-games': { + manifest: { + id: 'retro-games', + name: 'Retro Games Arcade', + version: '1.0.0', + description: 'Classic arcade games collection with high scores, leaderboards, and achievements. Includes Snake, Tetris, Pong, and more!', + author: 'MetaBuilder Team', + category: 'gaming', + icon: '🕹️', + screenshots: [], + tags: ['games', 'arcade', 'retro', 'entertainment'], + dependencies: [], + createdAt: Date.now(), + updatedAt: Date.now(), + downloadCount: 1567, + rating: 4.9, + installed: false, + }, + content: { + schemas: [ + { + name: 'Game', + displayName: 'Game', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'name', type: 'string', label: 'Name', required: true }, + { name: 'description', type: 'text', label: 'Description', required: false }, + { name: 'thumbnailUrl', type: 'string', label: 'Thumbnail URL', required: false }, + { name: 'gameType', type: 'string', label: 'Game Type', required: true }, + { name: 'difficulty', type: 'string', label: 'Difficulty', required: false }, + { name: 'playCount', type: 'number', label: 'Play Count', required: true, defaultValue: 0 }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + { + name: 'HighScore', + displayName: 'High Score', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'gameId', type: 'string', label: 'Game ID', required: true }, + { name: 'userId', type: 'string', label: 'User ID', required: true }, + { name: 'playerName', type: 'string', label: 'Player Name', required: true }, + { name: 'score', type: 'number', label: 'Score', required: true }, + { name: 'level', type: 'number', label: 'Level Reached', required: false }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + { + name: 'Achievement', + displayName: 'Achievement', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'name', type: 'string', label: 'Name', required: true }, + { name: 'description', type: 'text', label: 'Description', required: false }, + { name: 'gameId', type: 'string', label: 'Game ID', required: true }, + { name: 'iconUrl', type: 'string', label: 'Icon URL', required: false }, + { name: 'requirement', type: 'string', label: 'Requirement', required: true }, + { name: 'points', type: 'number', label: 'Points', required: true, defaultValue: 10 }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + { + name: 'UserAchievement', + displayName: 'User Achievement', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'userId', type: 'string', label: 'User ID', required: true }, + { name: 'achievementId', type: 'string', label: 'Achievement ID', required: true }, + { name: 'unlockedAt', type: 'number', label: 'Unlocked At', required: true }, + ], + }, + ], + pages: [ + { + id: 'page_arcade_home', + path: '/arcade', + title: 'Arcade Home', + level: 2, + componentTree: [], + requiresAuth: false, + }, + { + id: 'page_game_play', + path: '/arcade/play/:id', + title: 'Play Game', + level: 2, + componentTree: [], + requiresAuth: false, + }, + { + id: 'page_leaderboard', + path: '/arcade/leaderboard', + title: 'Leaderboard', + level: 2, + componentTree: [], + requiresAuth: false, + }, + ], + workflows: [], + luaScripts: [], + componentHierarchy: {}, + componentConfigs: {}, + seedData: { + Game: [ + { id: 'game_snake', name: 'Snake', description: 'Classic snake game', gameType: 'snake', difficulty: 'medium', playCount: 0, createdAt: Date.now() }, + { id: 'game_tetris', name: 'Tetris', description: 'Block-stacking puzzle', gameType: 'tetris', difficulty: 'medium', playCount: 0, createdAt: Date.now() }, + { id: 'game_pong', name: 'Pong', description: 'Classic paddle game', gameType: 'pong', difficulty: 'easy', playCount: 0, createdAt: Date.now() }, + ], + }, + }, + }, + 'ecommerce-basic': { + manifest: { + id: 'ecommerce-basic', + name: 'E-Commerce Store', + version: '1.0.0', + description: 'Complete online store with products, shopping cart, checkout, orders, and inventory management. Start selling online!', + author: 'MetaBuilder Team', + category: 'ecommerce', + icon: '🛒', + screenshots: [], + tags: ['ecommerce', 'shop', 'store', 'products'], + dependencies: [], + createdAt: Date.now(), + updatedAt: Date.now(), + downloadCount: 2341, + rating: 4.7, + installed: false, + }, + content: { + schemas: [ + { + name: 'Product', + displayName: 'Product', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'name', type: 'string', label: 'Name', required: true }, + { name: 'description', type: 'text', label: 'Description', required: false }, + { name: 'price', type: 'number', label: 'Price', required: true }, + { name: 'salePrice', type: 'number', label: 'Sale Price', required: false }, + { name: 'imageUrl', type: 'string', label: 'Image URL', required: false }, + { name: 'category', type: 'string', label: 'Category', required: false }, + { name: 'stock', type: 'number', label: 'Stock Quantity', required: true, defaultValue: 0 }, + { name: 'sku', type: 'string', label: 'SKU', required: false }, + { name: 'featured', type: 'boolean', label: 'Featured', required: true, defaultValue: false }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + { + name: 'Cart', + displayName: 'Shopping Cart', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'userId', type: 'string', label: 'User ID', required: true }, + { name: 'items', type: 'json', label: 'Items', required: true }, + { name: 'totalAmount', type: 'number', label: 'Total Amount', required: true, defaultValue: 0 }, + { name: 'updatedAt', type: 'number', label: 'Updated At', required: true }, + ], + }, + { + name: 'Order', + displayName: 'Order', + fields: [ + { name: 'id', type: 'string', label: 'ID', required: true, primaryKey: true }, + { name: 'userId', type: 'string', label: 'User ID', required: true }, + { name: 'items', type: 'json', label: 'Items', required: true }, + { name: 'totalAmount', type: 'number', label: 'Total Amount', required: true }, + { name: 'status', type: 'string', label: 'Status', required: true }, + { name: 'shippingAddress', type: 'json', label: 'Shipping Address', required: true }, + { name: 'paymentMethod', type: 'string', label: 'Payment Method', required: false }, + { name: 'createdAt', type: 'number', label: 'Created At', required: true }, + ], + }, + ], + pages: [ + { + id: 'page_shop_home', + path: '/shop', + title: 'Shop', + level: 2, + componentTree: [], + requiresAuth: false, + }, + { + id: 'page_product_detail', + path: '/product/:id', + title: 'Product Details', + level: 2, + componentTree: [], + requiresAuth: false, + }, + { + id: 'page_cart', + path: '/cart', + title: 'Shopping Cart', + level: 2, + componentTree: [], + requiresAuth: true, + requiredRole: 'user', + }, + { + id: 'page_checkout', + path: '/checkout', + title: 'Checkout', + level: 2, + componentTree: [], + requiresAuth: true, + requiredRole: 'user', + }, + ], + workflows: [], + luaScripts: [], + componentHierarchy: {}, + componentConfigs: {}, + }, + }, +} diff --git a/src/lib/package-types.ts b/src/lib/package-types.ts new file mode 100644 index 000000000..aa8d077e9 --- /dev/null +++ b/src/lib/package-types.ts @@ -0,0 +1,41 @@ +export interface PackageManifest { + id: string + name: string + version: string + description: string + author: string + category: 'social' | 'entertainment' | 'productivity' | 'gaming' | 'ecommerce' | 'content' | 'other' + icon: string + screenshots: string[] + tags: string[] + dependencies: string[] + createdAt: number + updatedAt: number + downloadCount: number + rating: number + installed: boolean +} + +export interface PackageContent { + schemas: any[] + pages: any[] + workflows: any[] + luaScripts: any[] + componentHierarchy: Record + componentConfigs: Record + cssClasses?: any[] + dropdownConfigs?: any[] + seedData?: Record +} + +export interface InstalledPackage { + packageId: string + installedAt: number + version: string + enabled: boolean +} + +export interface PackageRegistry { + packages: PackageManifest[] + installed: InstalledPackage[] +}