OK
This commit is contained in:
93
src/lib/io.ts
Normal file
93
src/lib/io.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import JSZip from 'jszip';
|
||||
import type { Manifest, AssetStore } from './types';
|
||||
import { state } from './state.svelte';
|
||||
|
||||
export async function exportBoard(): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const zip = new JSZip();
|
||||
const assetsFolder = zip.folder('assets');
|
||||
if (!assetsFolder) throw new Error('Failed to create assets folder');
|
||||
|
||||
const exportManifest: Manifest = {
|
||||
version: 1,
|
||||
items: state.manifest.items.map((item) => ({ ...item })),
|
||||
sharedCss: state.manifest.sharedCss,
|
||||
appCss: state.manifest.appCss
|
||||
};
|
||||
|
||||
for (const item of exportManifest.items) {
|
||||
if (item.assetId) {
|
||||
const asset = state.assets[item.assetId];
|
||||
if (asset) {
|
||||
const ext = asset.filename.split('.').pop() || 'bin';
|
||||
const filename = `${item.assetId}.${ext}`;
|
||||
assetsFolder.file(filename, asset.blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zip.file('manifest.json', JSON.stringify(exportManifest, null, 2));
|
||||
|
||||
const blob = await zip.generateAsync({ type: 'blob' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'board.bub';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
return { success: false, error: e instanceof Error ? e.message : 'Export failed' };
|
||||
}
|
||||
}
|
||||
|
||||
export async function importBoard(file: File): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const zip = await JSZip.loadAsync(file);
|
||||
|
||||
const manifestFile = zip.file('manifest.json');
|
||||
if (!manifestFile) throw new Error('Invalid .bub file: missing manifest.json');
|
||||
|
||||
const manifestJson = await manifestFile.async('string');
|
||||
const raw = JSON.parse(manifestJson);
|
||||
|
||||
if (raw.version !== 1) throw new Error(`Unsupported manifest version: ${raw.version}`);
|
||||
|
||||
const manifest: Manifest = {
|
||||
version: 1,
|
||||
items: raw.items,
|
||||
sharedCss: raw.sharedCss ?? '',
|
||||
appCss: raw.appCss ?? '',
|
||||
flags: raw.flags ?? {}
|
||||
};
|
||||
|
||||
const assets: AssetStore = {};
|
||||
const urlReplacements: Map<string, string> = new Map();
|
||||
|
||||
for (const item of manifest.items) {
|
||||
if (item.assetId) {
|
||||
const assetFiles = zip.folder('assets')?.file(new RegExp(`^${item.assetId}\\.`));
|
||||
if (assetFiles && assetFiles.length > 0) {
|
||||
const assetFile = assetFiles[0];
|
||||
const blob = await assetFile.async('blob');
|
||||
const url = URL.createObjectURL(blob);
|
||||
const filename = assetFile.name.split('/').pop() || 'asset';
|
||||
assets[item.assetId] = { blob, url, filename };
|
||||
urlReplacements.set(item.assetId, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of manifest.items) {
|
||||
if (item.assetId && urlReplacements.has(item.assetId)) {
|
||||
const newUrl = urlReplacements.get(item.assetId)!;
|
||||
item.html = item.html.replace(/src="[^"]*"/g, `src="${newUrl}"`);
|
||||
}
|
||||
}
|
||||
|
||||
state.load(manifest, assets);
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
return { success: false, error: e instanceof Error ? e.message : 'Import failed' };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user