diff --git a/src/base/dom/file-picker.ts b/src/base/dom/file-picker.ts new file mode 100644 index 00000000..fb0d2ddf --- /dev/null +++ b/src/base/dom/file-picker.ts @@ -0,0 +1,50 @@ +/* + Copyright (c) 2025 Xiang Yang. + Published under the standard MIT License. + Full text available at: https://opensource.org/licenses/MIT +*/ + +import { LocalFileSystem } from "../../kicanvas/services/vfs"; + +export class FilePicker { + /** + * Open file picker and call callback with the selected files. + */ + static async pick(callback: (vfs: LocalFileSystem) => Promise) { + const files = await FilePicker.open_picker(); + if (files.length > 0) { + await callback(new LocalFileSystem(files)); + } + } + + /** + * Open the file picker + */ + private static open_picker(): Promise { + return new Promise((resolve) => { + // because the Window:showOpenFilePicker() method is experimental + // so we use the traditional technique + const input = document.createElement("input"); + + input.type = "file"; + input.style.display = "none"; + input.multiple = true; + input.accept = ".kicad_pcb,.kicad_pro,.kicad_sch"; + + input.onchange = (event) => { + const files = (event.target as HTMLInputElement).files; + if (files && files.length > 0) { + resolve(Array.from(files)); + } else { + resolve([]); + } + }; + + input.oncancel = () => { + resolve([]); + }; + + input.click(); + }); + } +} diff --git a/src/kicanvas/elements/kicanvas-shell.css b/src/kicanvas/elements/kicanvas-shell.css index 010e52f9..0e3089e1 100644 --- a/src/kicanvas/elements/kicanvas-shell.css +++ b/src/kicanvas/elements/kicanvas-shell.css @@ -84,6 +84,26 @@ section.overlay input::placeholder { color: var(--fg); } +section.overlay .link_button { + color: #81eeff; + background: transparent; + font-size: 1em; + font-weight: normal; + text-decoration: underline; + outline: none; + border: none; + box-sizing: border-box; + transition: color var(--transition-time-medium) ease; +} + +section.overlay .link_button:focus { + border: var(--button-focus-outline); +} + +section.overlay .link_button:hover { + color: var(--fg); +} + section.overlay p.note { color: var(--input-placeholder); font-size: 1em; diff --git a/src/kicanvas/elements/kicanvas-shell.ts b/src/kicanvas/elements/kicanvas-shell.ts index 9c2bc355..36ab9e5a 100644 --- a/src/kicanvas/elements/kicanvas-shell.ts +++ b/src/kicanvas/elements/kicanvas-shell.ts @@ -6,6 +6,7 @@ import { later } from "../../base/async"; import { DropTarget } from "../../base/dom/drag-drop"; +import { FilePicker } from "../../base/dom/file-picker"; import { CSS, attribute, html, query } from "../../base/web-components"; import { KCUIElement, KCUIIconElement } from "../../kc-ui"; import { sprites_url } from "../icons/sprites"; @@ -76,6 +77,9 @@ class KiCanvasShellElement extends KCUIElement { @query(`input[name="link"]`, true) public link_input: HTMLInputElement; + @query(`button[name="open_local"]`, true) + public open_file_button: HTMLButtonElement; + override initialContentCallback() { const url_params = new URLSearchParams(document.location.search); const github_paths = url_params.getAll("github"); @@ -111,6 +115,12 @@ class KiCanvasShellElement extends KCUIElement { location.searchParams.set("github", link); window.history.pushState(null, "", location); }); + + this.open_file_button.addEventListener("click", async (e) => { + FilePicker.pick(async (vfs) => { + await this.setup_project(vfs); + }); + }); } private async setup_project(vfs: VirtualFileSystem) { @@ -166,7 +176,13 @@ class KiCanvasShellElement extends KCUIElement { type="text" placeholder="Paste a GitHub link..." autofocus /> -

or drag & drop your KiCAD files

+

+ or drag & drop your KiCAD files, or +

KiCanvas is { + return this.files.find((f) => f.name == name) !== undefined; + } + + override async get(name: string): Promise { + const file = this.files.find((f) => f.name == name); + if (file) { + return file; + } else { + throw new Error(`File ${name} not found`); + } + } + + override async download(name: string) { + initiate_download(await this.get(name)); + } +}