82 lines
3.2 KiB
TypeScript
82 lines
3.2 KiB
TypeScript
/**
|
|
* Unified project import dispatcher.
|
|
*
|
|
* Velxio accepts two on-disk project formats:
|
|
*
|
|
* - `.vlx` — Velxio's native single-file JSON (boards + components + wires +
|
|
* file groups, optionally multi-board). Handled by importVlxFile,
|
|
* which writes directly into the simulator + editor stores.
|
|
*
|
|
* - `.zip` — Wokwi-compatible bundle (diagram.json + sketch.ino + headers +
|
|
* libraries.txt). Handled by importFromWokwiZip, which returns a
|
|
* structured payload that callers apply to the stores themselves
|
|
* (so the toolbar can also pop the "install missing libraries"
|
|
* modal afterwards).
|
|
*
|
|
* Both UI entry points (the toolbar "Import" button and the file-explorer
|
|
* "Open project" button) route through this dispatcher so the user sees the
|
|
* same behaviour regardless of which button they click — and so we have ONE
|
|
* place to extend if a third format ever shows up.
|
|
*/
|
|
|
|
import type { Wire } from '../types/wire';
|
|
import { importVlxFile, VlxParseError } from './vlxFile';
|
|
import { importFromWokwiZip, type VelxioComponent } from './wokwiZip';
|
|
|
|
/**
|
|
* Common result shape: `kind` tells callers which path ran, so they can
|
|
* react appropriately (e.g. only the zip path needs the library-install
|
|
* modal — the .vlx format inlines library state).
|
|
*/
|
|
export type ProjectImportResult =
|
|
| { kind: 'vlx' }
|
|
| {
|
|
kind: 'zip';
|
|
boardType: 'arduino-uno' | 'arduino-nano' | 'arduino-mega' | 'raspberry-pi-pico';
|
|
boardPosition: { x: number; y: number };
|
|
components: VelxioComponent[];
|
|
wires: Wire[];
|
|
files: Array<{ name: string; content: string }>;
|
|
libraries: string[];
|
|
};
|
|
|
|
/**
|
|
* Detect format from the filename + MIME type and dispatch to the right
|
|
* loader. Returns the loader's result so callers can chain UI behaviour
|
|
* (e.g. show the install-libraries modal for zips). Throws when the
|
|
* format is unrecognised or the loader fails — callers should catch and
|
|
* surface to the user.
|
|
*
|
|
* Note: `.vlx` loads itself directly into the stores; `.zip` returns a
|
|
* payload that the caller must apply (we keep that asymmetry so the
|
|
* toolbar can still trigger the libraries modal at the end).
|
|
*/
|
|
export async function importProjectFile(file: File): Promise<ProjectImportResult> {
|
|
const lower = file.name.toLowerCase();
|
|
|
|
if (lower.endsWith('.vlx') || (file.type === 'application/json' && !lower.endsWith('.zip'))) {
|
|
try {
|
|
await importVlxFile(file);
|
|
return { kind: 'vlx' };
|
|
} catch (err) {
|
|
// Re-raise with a friendlier prefix. VlxParseError already has a
|
|
// good message; native errors get a clear context label.
|
|
const msg = err instanceof VlxParseError ? err.message : (err as Error).message;
|
|
throw new Error(`Could not load .vlx file:\n\n${msg}`);
|
|
}
|
|
}
|
|
|
|
if (lower.endsWith('.zip')) {
|
|
const result = await importFromWokwiZip(file);
|
|
return { kind: 'zip', ...result };
|
|
}
|
|
|
|
throw new Error(
|
|
`Unsupported project file: ${file.name}.\n` +
|
|
`Velxio accepts .vlx (Velxio projects) and .zip (Wokwi bundles).`,
|
|
);
|
|
}
|
|
|
|
/** File-input `accept` attribute that pairs with importProjectFile. */
|
|
export const PROJECT_FILE_ACCEPT = '.vlx,.zip,application/json,application/zip';
|