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

This commit is contained in:
2025-12-23 22:24:54 +00:00
parent 3b7a325651
commit ea7a6c4749
7 changed files with 1398 additions and 14 deletions

39
PRD.md
View File

@@ -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

View File

@@ -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) {
</div>
<Tabs defaultValue="guide" className="space-y-6">
<TabsList className="grid w-full grid-cols-4 lg:grid-cols-12 max-w-full">
<TabsList className="grid w-full grid-cols-4 lg:grid-cols-13 max-w-full">
<TabsTrigger value="guide">
<Sparkle className="mr-2" size={16} />
Guide
</TabsTrigger>
<TabsTrigger value="packages">
<Package className="mr-2" size={16} />
Packages
</TabsTrigger>
<TabsTrigger value="pages">
<MapTrifold className="mr-2" size={16} />
Page Routes
@@ -259,6 +264,10 @@ export function Level4({ user, onLogout, onNavigate, onPreview }: Level4Props) {
<QuickGuide />
</TabsContent>
<TabsContent value="packages" className="space-y-6">
<PackageManager />
</TabsContent>
<TabsContent value="pages" className="space-y-6">
<PageRoutesManager />
</TabsContent>

View File

@@ -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<PackageManifest[]>([])
const [installedPackages, setInstalledPackages] = useState<InstalledPackage[]>([])
const [selectedPackage, setSelectedPackage] = useState<{ manifest: PackageManifest; content: PackageContent } | null>(null)
const [searchQuery, setSearchQuery] = useState('')
const [categoryFilter, setCategoryFilter] = useState<string>('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 (
<div className="flex flex-col h-full">
<div className="flex items-center justify-between p-6 border-b">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-lg bg-gradient-to-br from-purple-500 to-purple-700 flex items-center justify-center">
<Package size={24} weight="duotone" className="text-white" />
</div>
<div>
<h2 className="text-2xl font-bold">Package Manager</h2>
<p className="text-sm text-muted-foreground">Install pre-built applications and features</p>
</div>
</div>
{onClose && (
<Button variant="ghost" onClick={onClose}>
Close
</Button>
)}
</div>
<div className="flex-1 overflow-hidden">
<Tabs defaultValue="all" className="h-full flex flex-col">
<div className="px-6 pt-4">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="all">All Packages</TabsTrigger>
<TabsTrigger value="installed">
Installed ({installedList.length})
</TabsTrigger>
<TabsTrigger value="available">
Available ({availableList.length})
</TabsTrigger>
</TabsList>
</div>
<div className="px-6 py-4 space-y-3 border-b">
<div className="relative">
<MagnifyingGlass className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" size={20} />
<Input
placeholder="Search packages..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
</div>
<div className="flex gap-3">
<Select value={categoryFilter} onValueChange={setCategoryFilter}>
<SelectTrigger className="w-[180px]">
<Funnel size={16} className="mr-2" />
<SelectValue placeholder="Category" />
</SelectTrigger>
<SelectContent>
{categories.map(cat => (
<SelectItem key={cat} value={cat}>
{cat === 'all' ? 'All Categories' : cat.charAt(0).toUpperCase() + cat.slice(1)}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={sortBy} onValueChange={(v) => setSortBy(v as any)}>
<SelectTrigger className="w-[180px]">
<TrendUp size={16} className="mr-2" />
<SelectValue placeholder="Sort by" />
</SelectTrigger>
<SelectContent>
<SelectItem value="downloads">Most Downloaded</SelectItem>
<SelectItem value="rating">Highest Rated</SelectItem>
<SelectItem value="name">Name</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<TabsContent value="all" className="flex-1 m-0">
<ScrollArea className="h-full">
<div className="p-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredPackages.map(pkg => (
<PackageCard
key={pkg.id}
package={pkg}
isInstalled={pkg.installed}
installedPackage={installedPackages.find(ip => ip.packageId === pkg.id)}
onViewDetails={() => {
setSelectedPackage(PACKAGE_CATALOG[pkg.id])
setShowDetails(true)
}}
onToggle={handleTogglePackage}
/>
))}
</div>
</ScrollArea>
</TabsContent>
<TabsContent value="installed" className="flex-1 m-0">
<ScrollArea className="h-full">
<div className="p-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{installedList.length === 0 ? (
<div className="col-span-full text-center py-12">
<Package size={48} className="mx-auto mb-4 text-muted-foreground" />
<p className="text-muted-foreground">No packages installed yet</p>
</div>
) : (
installedList.map(pkg => (
<PackageCard
key={pkg.id}
package={pkg}
isInstalled={true}
installedPackage={installedPackages.find(ip => ip.packageId === pkg.id)}
onViewDetails={() => {
setSelectedPackage(PACKAGE_CATALOG[pkg.id])
setShowDetails(true)
}}
onToggle={handleTogglePackage}
/>
))
)}
</div>
</ScrollArea>
</TabsContent>
<TabsContent value="available" className="flex-1 m-0">
<ScrollArea className="h-full">
<div className="p-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{availableList.map(pkg => (
<PackageCard
key={pkg.id}
package={pkg}
isInstalled={false}
installedPackage={undefined}
onViewDetails={() => {
setSelectedPackage(PACKAGE_CATALOG[pkg.id])
setShowDetails(true)
}}
onToggle={handleTogglePackage}
/>
))}
</div>
</ScrollArea>
</TabsContent>
</Tabs>
</div>
<Dialog open={showDetails} onOpenChange={setShowDetails}>
<DialogContent className="max-w-3xl max-h-[90vh] overflow-hidden flex flex-col">
{selectedPackage && (
<>
<DialogHeader>
<div className="flex items-start gap-4">
<div className="w-16 h-16 rounded-xl bg-gradient-to-br from-purple-500 to-purple-700 flex items-center justify-center text-3xl flex-shrink-0">
{selectedPackage.manifest.icon}
</div>
<div className="flex-1">
<DialogTitle className="text-2xl">{selectedPackage.manifest.name}</DialogTitle>
<DialogDescription className="mt-1">{selectedPackage.manifest.description}</DialogDescription>
<div className="flex items-center gap-3 mt-3">
<Badge variant="secondary">{selectedPackage.manifest.category}</Badge>
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Download size={14} />
<span>{selectedPackage.manifest.downloadCount.toLocaleString()}</span>
</div>
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Star size={14} weight="fill" className="text-yellow-500" />
<span>{selectedPackage.manifest.rating}</span>
</div>
</div>
</div>
</div>
</DialogHeader>
<Separator className="my-4" />
<ScrollArea className="flex-1">
<div className="space-y-6 pr-4">
<div>
<h4 className="font-semibold mb-2">Author</h4>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<User size={16} />
<span>{selectedPackage.manifest.author}</span>
</div>
</div>
<div>
<h4 className="font-semibold mb-2">Version</h4>
<p className="text-sm text-muted-foreground">{selectedPackage.manifest.version}</p>
</div>
<div>
<h4 className="font-semibold mb-2">Tags</h4>
<div className="flex flex-wrap gap-2">
{selectedPackage.manifest.tags.map(tag => (
<Badge key={tag} variant="outline">
<Tag size={12} className="mr-1" />
{tag}
</Badge>
))}
</div>
</div>
<div>
<h4 className="font-semibold mb-2">Includes</h4>
<div className="grid grid-cols-2 gap-3">
<div className="p-3 rounded-lg bg-muted">
<div className="font-medium text-sm">Data Models</div>
<div className="text-2xl font-bold text-primary">{selectedPackage.content.schemas.length}</div>
</div>
<div className="p-3 rounded-lg bg-muted">
<div className="font-medium text-sm">Pages</div>
<div className="text-2xl font-bold text-primary">{selectedPackage.content.pages.length}</div>
</div>
<div className="p-3 rounded-lg bg-muted">
<div className="font-medium text-sm">Workflows</div>
<div className="text-2xl font-bold text-primary">{selectedPackage.content.workflows.length}</div>
</div>
<div className="p-3 rounded-lg bg-muted">
<div className="font-medium text-sm">Scripts</div>
<div className="text-2xl font-bold text-primary">{selectedPackage.content.luaScripts.length}</div>
</div>
</div>
</div>
{selectedPackage.content.schemas.length > 0 && (
<div>
<h4 className="font-semibold mb-2">Data Models</h4>
<div className="space-y-2">
{selectedPackage.content.schemas.map(schema => (
<div key={schema.name} className="p-3 rounded-lg border">
<div className="font-medium">{schema.displayName || schema.name}</div>
<div className="text-sm text-muted-foreground">{schema.fields.length} fields</div>
</div>
))}
</div>
</div>
)}
{selectedPackage.content.pages.length > 0 && (
<div>
<h4 className="font-semibold mb-2">Pages</h4>
<div className="space-y-2">
{selectedPackage.content.pages.map(page => (
<div key={page.id} className="p-3 rounded-lg border">
<div className="font-medium">{page.title}</div>
<div className="text-sm text-muted-foreground font-mono">{page.path}</div>
</div>
))}
</div>
</div>
)}
</div>
</ScrollArea>
<DialogFooter className="mt-4">
{selectedPackage.manifest.installed ? (
<Button variant="destructive" onClick={() => handleUninstallPackage(selectedPackage.manifest.id)}>
<Trash size={16} className="mr-2" />
Uninstall
</Button>
) : (
<Button onClick={() => handleInstallPackage(selectedPackage.manifest.id)} disabled={installing}>
<Download size={16} className="mr-2" />
{installing ? 'Installing...' : 'Install Package'}
</Button>
)}
</DialogFooter>
</>
)}
</DialogContent>
</Dialog>
</div>
)
}
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 (
<Card className="flex flex-col hover:shadow-lg transition-shadow">
<CardHeader>
<div className="flex items-start gap-3">
<div className="w-12 h-12 rounded-lg bg-gradient-to-br from-purple-500 to-purple-700 flex items-center justify-center text-2xl flex-shrink-0">
{pkg.icon}
</div>
<div className="flex-1 min-w-0">
<CardTitle className="text-lg truncate">{pkg.name}</CardTitle>
<CardDescription className="line-clamp-2 mt-1">{pkg.description}</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="flex-1">
<div className="flex items-center gap-2 mb-3">
<Badge variant="secondary">{pkg.category}</Badge>
{isInstalled && (
<Badge variant={installedPackage?.enabled ? 'default' : 'outline'}>
{installedPackage?.enabled ? 'Active' : 'Disabled'}
</Badge>
)}
</div>
<div className="flex items-center gap-3 text-sm text-muted-foreground">
<div className="flex items-center gap-1">
<Download size={14} />
<span>{pkg.downloadCount.toLocaleString()}</span>
</div>
<div className="flex items-center gap-1">
<Star size={14} weight="fill" className="text-yellow-500" />
<span>{pkg.rating}</span>
</div>
</div>
</CardContent>
<CardFooter className="flex gap-2">
<Button variant="outline" onClick={onViewDetails} className="flex-1">
View Details
</Button>
{isInstalled && installedPackage && (
<Button
variant="ghost"
size="icon"
onClick={() => onToggle(pkg.id, !installedPackage.enabled)}
title={installedPackage.enabled ? 'Disable' : 'Enable'}
>
<Power size={18} weight={installedPackage.enabled ? 'fill' : 'regular'} />
</Button>
)}
</CardFooter>
</Card>
)
}

View File

@@ -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() {
<p className="text-sm text-muted-foreground">Learn how to use the new visual configuration tools</p>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div className="grid md:grid-cols-3 gap-4">
<Card className="p-6 space-y-3 border-2 border-purple-500/20 bg-purple-500/5">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-purple-500/20 flex items-center justify-center">
<Package className="text-purple-600" size={20} weight="duotone" />
</div>
<div>
<h3 className="font-semibold">Package System</h3>
<Badge variant="secondary" className="text-xs">Docker-Style Apps</Badge>
</div>
</div>
<p className="text-sm text-muted-foreground">
Install complete pre-built applications like forums, guestbooks, video platforms, and e-commerce stores with one click!
</p>
<div className="pt-2 space-y-1 text-xs">
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-purple-600" />
<span>6+ ready-to-use applications</span>
</div>
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-purple-600" />
<span>Automatic schema & workflow setup</span>
</div>
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-purple-600" />
<span>Enable/disable packages anytime</span>
</div>
</div>
</Card>
<Card className="p-6 space-y-3 border-2 border-primary/20 bg-primary/5">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-primary/20 flex items-center justify-center">
@@ -73,6 +102,54 @@ export function QuickGuide() {
<Card className="p-6">
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="packages">
<AccordionTrigger className="text-base font-semibold">
<div className="flex items-center gap-2">
<Package size={18} weight="duotone" />
How to use the Package System
</div>
</AccordionTrigger>
<AccordionContent className="space-y-3 text-sm">
<div className="space-y-2">
<p className="font-medium">Step 1: Browse available packages</p>
<ul className="list-disc list-inside space-y-1 text-muted-foreground ml-4">
<li>Go to the "Packages" tab</li>
<li>Browse packages by category (Social, Entertainment, Gaming, E-Commerce)</li>
<li>Use search and filters to find what you need</li>
<li>Click "View Details" to see what's included in each package</li>
</ul>
</div>
<div className="space-y-2">
<p className="font-medium">Step 2: Install a package</p>
<ul className="list-disc list-inside space-y-1 text-muted-foreground ml-4">
<li>Click "Install Package" in the details dialog</li>
<li>The system automatically adds schemas, pages, workflows, and Lua scripts</li>
<li>Seed data is loaded if included with the package</li>
<li>Package is enabled by default after installation</li>
</ul>
</div>
<div className="space-y-2">
<p className="font-medium">Step 3: Manage installed packages</p>
<ul className="list-disc list-inside space-y-1 text-muted-foreground ml-4">
<li>View installed packages in the "Installed" tab</li>
<li>Enable/disable packages with the power button</li>
<li>Uninstall packages to remove all associated data</li>
<li>Customize package schemas and pages as needed</li>
</ul>
</div>
<div className="p-4 bg-muted rounded-lg mt-3">
<p className="text-xs font-medium mb-2">Available Packages:</p>
<ul className="text-xs text-muted-foreground space-y-1">
<li>• <strong>Classic Forum</strong> - Discussion boards with threads and categories</li>
<li>• <strong>Retro Guestbook</strong> - 90s-style visitor messages</li>
<li>• <strong>Video Platform</strong> - YouTube-style video sharing</li>
<li>• <strong>Music Streaming</strong> - Spotify-style music platform</li>
<li>• <strong>Retro Games Arcade</strong> - Classic games with leaderboards</li>
<li>• <strong>E-Commerce Store</strong> - Complete online shop</li>
</ul>
</div>
</AccordionContent>
</AccordionItem>
<AccordionItem value="css">
<AccordionTrigger className="text-base font-semibold">
<div className="flex items-center gap-2">

View File

@@ -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<string> {
@@ -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<InstalledPackage[]> {
return (await window.spark.kv.get<InstalledPackage[]>(DB_KEYS.INSTALLED_PACKAGES)) || []
}
static async setInstalledPackages(packages: InstalledPackage[]): Promise<void> {
await window.spark.kv.set(DB_KEYS.INSTALLED_PACKAGES, packages)
}
static async installPackage(packageData: InstalledPackage): Promise<void> {
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<void> {
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<void> {
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<Record<string, any[]>> {
const allData = (await window.spark.kv.get<Record<string, Record<string, any[]>>>(DB_KEYS.PACKAGE_DATA)) || {}
return allData[packageId] || {}
}
static async setPackageData(packageId: string, data: Record<string, any[]>): Promise<void> {
const allData = (await window.spark.kv.get<Record<string, Record<string, any[]>>>(DB_KEYS.PACKAGE_DATA)) || {}
allData[packageId] = data
await window.spark.kv.set(DB_KEYS.PACKAGE_DATA, allData)
}
static async deletePackageData(packageId: string): Promise<void> {
const allData = (await window.spark.kv.get<Record<string, Record<string, any[]>>>(DB_KEYS.PACKAGE_DATA)) || {}
delete allData[packageId]
await window.spark.kv.set(DB_KEYS.PACKAGE_DATA, allData)
}
}

664
src/lib/package-catalog.ts Normal file
View File

@@ -0,0 +1,664 @@
import type { PackageManifest, PackageContent } from './package-types'
export const PACKAGE_CATALOG: Record<string, { manifest: PackageManifest; content: PackageContent }> = {
'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: {},
},
},
}

41
src/lib/package-types.ts Normal file
View File

@@ -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<string, any>
componentConfigs: Record<string, any>
cssClasses?: any[]
dropdownConfigs?: any[]
seedData?: Record<string, any[]>
}
export interface InstalledPackage {
packageId: string
installedAt: number
version: string
enabled: boolean
}
export interface PackageRegistry {
packages: PackageManifest[]
installed: InstalledPackage[]
}