mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
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:
39
PRD.md
39
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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
522
src/components/PackageManager.tsx
Normal file
522
src/components/PackageManager.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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
664
src/lib/package-catalog.ts
Normal 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
41
src/lib/package-types.ts
Normal 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[]
|
||||
}
|
||||
Reference in New Issue
Block a user