From 79632c291361dfbe87c2ee39eafe387451768301 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sat, 27 Dec 2025 17:31:18 +0000 Subject: [PATCH] refactor: modularize package import/export flow --- .../managers/package/PackageImportExport.tsx | 564 ++---------------- .../package/import-export/ExportDialog.tsx | 224 +++++++ .../package/import-export/ImportDialog.tsx | 45 ++ .../package/import-export/StatusUI.tsx | 54 ++ .../import-export/createFileSelector.ts | 17 + .../package/import-export/defaults.ts | 24 + .../import-export/executePackageImport.ts | 9 + .../import-export/generatePackageExport.ts | 63 ++ .../import-export/generateSnapshotExport.ts | 30 + .../package/import-export/validateManifest.ts | 9 + 10 files changed, 525 insertions(+), 514 deletions(-) create mode 100644 frontends/nextjs/src/components/managers/package/import-export/ExportDialog.tsx create mode 100644 frontends/nextjs/src/components/managers/package/import-export/ImportDialog.tsx create mode 100644 frontends/nextjs/src/components/managers/package/import-export/StatusUI.tsx create mode 100644 frontends/nextjs/src/components/managers/package/import-export/createFileSelector.ts create mode 100644 frontends/nextjs/src/components/managers/package/import-export/defaults.ts create mode 100644 frontends/nextjs/src/components/managers/package/import-export/executePackageImport.ts create mode 100644 frontends/nextjs/src/components/managers/package/import-export/generatePackageExport.ts create mode 100644 frontends/nextjs/src/components/managers/package/import-export/generateSnapshotExport.ts create mode 100644 frontends/nextjs/src/components/managers/package/import-export/validateManifest.ts diff --git a/frontends/nextjs/src/components/managers/package/PackageImportExport.tsx b/frontends/nextjs/src/components/managers/package/PackageImportExport.tsx index f5836b3f3..25377e76b 100644 --- a/frontends/nextjs/src/components/managers/package/PackageImportExport.tsx +++ b/frontends/nextjs/src/components/managers/package/PackageImportExport.tsx @@ -1,32 +1,15 @@ -import { useState, useRef } from 'react' -import { Button } from '@/components/ui' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui' -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui' -import { Label } from '@/components/ui' -import { Input } from '@/components/ui' -import { Textarea } from '@/components/ui' -import { Checkbox } from '@/components/ui' -import { ScrollArea } from '@/components/ui' -import { Separator } from '@/components/ui' +import { useRef, useState } from 'react' import { toast } from 'sonner' -import { Database } from '@/lib/database' -import { exportPackageAsZip, importPackageFromZip, downloadZip, exportDatabaseSnapshot } from '@/lib/packages/core/package-export' -import type { PackageManifest, PackageContent } from '@/lib/package-types' +import type { PackageManifest } from '@/lib/package-types' import type { ExportPackageOptions } from '@/lib/packages/core/package-export' -import { installPackage } from '@/lib/api/packages' -import { - Export, - ArrowSquareIn, - FileArchive, - FileArrowDown, - FileArrowUp, - Package, - CloudArrowDown, - Database as DatabaseIcon, - CheckCircle, - Warning, - Image as ImageIcon, -} from '@phosphor-icons/react' +import { createFileSelector } from './import-export/createFileSelector' +import { executePackageImport } from './import-export/executePackageImport' +import { generatePackageExport } from './import-export/generatePackageExport' +import { generateSnapshotExport } from './import-export/generateSnapshotExport' +import { validateManifest } from './import-export/validateManifest' +import { defaultExportOptions, defaultManifest } from './import-export/defaults' +import { ImportDialog } from './import-export/ImportDialog' +import { ExportDialog } from './import-export/ExportDialog' interface PackageImportExportProps { open: boolean @@ -34,82 +17,27 @@ interface PackageImportExportProps { mode: 'export' | 'import' } +const createInitialManifest = () => ({ ...defaultManifest, tags: [...(defaultManifest.tags || [])] }) +const createInitialExportOptions = () => ({ ...defaultExportOptions }) + export function PackageImportExport({ open, onOpenChange, mode }: PackageImportExportProps) { const [exporting, setExporting] = useState(false) const [importing, setImporting] = useState(false) - const [exportOptions, setExportOptions] = useState({ - includeAssets: true, - includeSchemas: true, - includePages: true, - includeWorkflows: true, - includeLuaScripts: true, - includeComponentHierarchy: true, - includeComponentConfigs: true, - includeCssClasses: true, - includeDropdownConfigs: true, - includeSeedData: true, - }) - const [manifest, setManifest] = useState>({ - name: '', - version: '1.0.0', - description: '', - author: '', - category: 'other', - tags: [], - }) + const [exportOptions, setExportOptions] = useState(createInitialExportOptions) + const [manifest, setManifest] = useState>(createInitialManifest) const [tagInput, setTagInput] = useState('') const fileInputRef = useRef(null) const handleExport = async () => { - if (!manifest.name) { - toast.error('Please provide a package name') + const validationError = validateManifest(manifest) + if (validationError) { + toast.error(validationError) return } setExporting(true) try { - const schemas = await Database.getSchemas() - const pages = await Database.getPages() - const workflows = await Database.getWorkflows() - const luaScripts = await Database.getLuaScripts() - const componentHierarchy = await Database.getComponentHierarchy() - const componentConfigs = await Database.getComponentConfigs() - const cssClasses = await Database.getCssClasses() - const dropdownConfigs = await Database.getDropdownConfigs() - - const fullManifest: PackageManifest = { - id: `pkg_${Date.now()}`, - name: manifest.name!, - version: manifest.version || '1.0.0', - description: manifest.description || '', - author: manifest.author || 'Anonymous', - category: manifest.category as any || 'other', - icon: '📦', - screenshots: [], - tags: manifest.tags || [], - dependencies: [], - createdAt: Date.now(), - updatedAt: Date.now(), - downloadCount: 0, - rating: 0, - installed: false, - } - - const content: PackageContent = { - schemas: exportOptions.includeSchemas ? schemas : [], - pages: exportOptions.includePages ? pages : [], - workflows: exportOptions.includeWorkflows ? workflows : [], - luaScripts: exportOptions.includeLuaScripts ? luaScripts : [], - componentHierarchy: exportOptions.includeComponentHierarchy ? componentHierarchy : {}, - componentConfigs: exportOptions.includeComponentConfigs ? componentConfigs : {}, - cssClasses: exportOptions.includeCssClasses ? cssClasses : undefined, - dropdownConfigs: exportOptions.includeDropdownConfigs ? dropdownConfigs : undefined, - } - - const blob = await exportPackageAsZip(fullManifest, content, [], exportOptions) - const fileName = `${manifest.name.toLowerCase().replace(/\s+/g, '-')}-${manifest.version}.zip` - downloadZip(blob, fileName) - + await generatePackageExport(manifest, exportOptions) toast.success('Package exported successfully!') onOpenChange(false) } catch (error) { @@ -123,29 +51,7 @@ export function PackageImportExport({ open, onOpenChange, mode }: PackageImportE const handleExportSnapshot = async () => { setExporting(true) try { - const schemas = await Database.getSchemas() - const pages = await Database.getPages() - const workflows = await Database.getWorkflows() - const luaScripts = await Database.getLuaScripts() - const componentHierarchy = await Database.getComponentHierarchy() - const componentConfigs = await Database.getComponentConfigs() - const cssClasses = await Database.getCssClasses() - const dropdownConfigs = await Database.getDropdownConfigs() - - const blob = await exportDatabaseSnapshot( - schemas, - pages, - workflows, - luaScripts, - componentHierarchy, - componentConfigs, - cssClasses, - dropdownConfigs - ) - - const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5) - downloadZip(blob, `database-snapshot-${timestamp}.zip`) - + await generateSnapshotExport() toast.success('Database snapshot exported successfully!') onOpenChange(false) } catch (error) { @@ -159,13 +65,11 @@ export function PackageImportExport({ open, onOpenChange, mode }: PackageImportE const handleImport = async (file: File) => { setImporting(true) try { - const { manifest: importedManifest, content, assets } = await importPackageFromZip(file) - - await installPackage(importedManifest.id, { manifest: importedManifest, content }) - + const { manifest: importedManifest, content, assets } = await executePackageImport(file) toast.success(`Package "${importedManifest.name}" imported successfully!`) - toast.info(`Imported: ${content.schemas.length} schemas, ${content.pages.length} pages, ${content.workflows.length} workflows, ${assets.length} assets`) - + toast.info( + `Imported: ${content.schemas.length} schemas, ${content.pages.length} pages, ${content.workflows.length} workflows, ${assets.length} assets` + ) onOpenChange(false) } catch (error) { console.error('Import error:', error) @@ -175,22 +79,11 @@ export function PackageImportExport({ open, onOpenChange, mode }: PackageImportE } } - const handleFileSelect = (e: React.ChangeEvent) => { - const file = e.target.files?.[0] - if (file) { - if (!file.name.endsWith('.zip')) { - toast.error('Please select a .zip file') - return - } - handleImport(file) - } - } - const handleAddTag = () => { if (tagInput.trim() && !manifest.tags?.includes(tagInput.trim())) { setManifest(prev => ({ ...prev, - tags: [...(prev.tags || []), tagInput.trim()] + tags: [...(prev.tags || []), tagInput.trim()], })) setTagInput('') } @@ -199,396 +92,39 @@ export function PackageImportExport({ open, onOpenChange, mode }: PackageImportE const handleRemoveTag = (tag: string) => { setManifest(prev => ({ ...prev, - tags: (prev.tags || []).filter(t => t !== tag) + tags: (prev.tags || []).filter(t => t !== tag), })) } + const handleFileSelect = createFileSelector(handleImport, message => toast.error(message)) + if (mode === 'import') { return ( - - - -
-
- -
-
- Import Package - Import a package from a ZIP file -
-
-
- -
- - - Select Package File - Choose a .zip file containing a MetaBuilder package - - -
-
fileInputRef.current?.click()} - > - -

Click to select a package file

-

Supports .zip files only

- -
- - {importing && ( -
-
- Importing package... -
- )} -
- - - - - - What's Included in Packages? - - -
-
- - Data schemas -
-
- - Page configurations -
-
- - Workflows -
-
- - Lua scripts -
-
- - Component hierarchies -
-
- - CSS configurations -
-
- - Assets (images, etc.) -
-
- - Seed data -
-
-
-
- -
- -
-

Import Warning

-

Imported packages will be merged with existing data. Make sure to back up your database before importing.

-
-
-
- -
+ ) } return ( - - - -
-
- -
-
- Export Package - Create a shareable package or database snapshot -
-
-
- - -
-
- - -
-
- -
- Custom Package -
- Export selected data as a reusable package -
-
- - - -
-
- -
- Full Snapshot -
- Export entire database as backup -
-
-
- - - -
-
- - setManifest(prev => ({ ...prev, name: e.target.value }))} - /> -
- -
-
- - setManifest(prev => ({ ...prev, version: e.target.value }))} - /> -
- -
- - setManifest(prev => ({ ...prev, author: e.target.value }))} - /> -
-
- -
- -