Skip to content

Commit 446f21e

Browse files
authored
add: open files from local disk (#148)
1 parent 941c96e commit 446f21e

4 files changed

Lines changed: 119 additions & 1 deletion

File tree

‎src/base/dom/file-picker.ts‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
Copyright (c) 2025 Xiang Yang.
3+
Published under the standard MIT License.
4+
Full text available at: https://opensource.org/licenses/MIT
5+
*/
6+
7+
import { LocalFileSystem } from "../../kicanvas/services/vfs";
8+
9+
export class FilePicker {
10+
/**
11+
* Open file picker and call callback with the selected files.
12+
*/
13+
static async pick(callback: (vfs: LocalFileSystem) => Promise<void>) {
14+
const files = await FilePicker.open_picker();
15+
if (files.length > 0) {
16+
await callback(new LocalFileSystem(files));
17+
}
18+
}
19+
20+
/**
21+
* Open the file picker
22+
*/
23+
private static open_picker(): Promise<File[]> {
24+
return new Promise((resolve) => {
25+
// because the Window:showOpenFilePicker() method is experimental
26+
// so we use the traditional technique
27+
const input = document.createElement("input");
28+
29+
input.type = "file";
30+
input.style.display = "none";
31+
input.multiple = true;
32+
input.accept = ".kicad_pcb,.kicad_pro,.kicad_sch";
33+
34+
input.onchange = (event) => {
35+
const files = (event.target as HTMLInputElement).files;
36+
if (files && files.length > 0) {
37+
resolve(Array.from(files));
38+
} else {
39+
resolve([]);
40+
}
41+
};
42+
43+
input.oncancel = () => {
44+
resolve([]);
45+
};
46+
47+
input.click();
48+
});
49+
}
50+
}

‎src/kicanvas/elements/kicanvas-shell.css‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,26 @@ section.overlay input::placeholder {
8484
color: var(--fg);
8585
}
8686

87+
section.overlay .link_button {
88+
color: #81eeff;
89+
background: transparent;
90+
font-size: 1em;
91+
font-weight: normal;
92+
text-decoration: underline;
93+
outline: none;
94+
border: none;
95+
box-sizing: border-box;
96+
transition: color var(--transition-time-medium) ease;
97+
}
98+
99+
section.overlay .link_button:focus {
100+
border: var(--button-focus-outline);
101+
}
102+
103+
section.overlay .link_button:hover {
104+
color: var(--fg);
105+
}
106+
87107
section.overlay p.note {
88108
color: var(--input-placeholder);
89109
font-size: 1em;

‎src/kicanvas/elements/kicanvas-shell.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import { later } from "../../base/async";
88
import { DropTarget } from "../../base/dom/drag-drop";
9+
import { FilePicker } from "../../base/dom/file-picker";
910
import { CSS, attribute, html, query } from "../../base/web-components";
1011
import { KCUIElement, KCUIIconElement } from "../../kc-ui";
1112
import { sprites_url } from "../icons/sprites";
@@ -76,6 +77,9 @@ class KiCanvasShellElement extends KCUIElement {
7677
@query(`input[name="link"]`, true)
7778
public link_input: HTMLInputElement;
7879

80+
@query(`button[name="open_local"]`, true)
81+
public open_file_button: HTMLButtonElement;
82+
7983
override initialContentCallback() {
8084
const url_params = new URLSearchParams(document.location.search);
8185
const github_paths = url_params.getAll("github");
@@ -111,6 +115,12 @@ class KiCanvasShellElement extends KCUIElement {
111115
location.searchParams.set("github", link);
112116
window.history.pushState(null, "", location);
113117
});
118+
119+
this.open_file_button.addEventListener("click", async (e) => {
120+
FilePicker.pick(async (vfs) => {
121+
await this.setup_project(vfs);
122+
});
123+
});
114124
}
115125

116126
private async setup_project(vfs: VirtualFileSystem) {
@@ -166,7 +176,13 @@ class KiCanvasShellElement extends KCUIElement {
166176
type="text"
167177
placeholder="Paste a GitHub link..."
168178
autofocus />
169-
<p>or drag & drop your KiCAD files</p>
179+
<p>
180+
or drag & drop your KiCAD files, or<button
181+
name="open_local"
182+
class="link_button">
183+
open from local
184+
</button>
185+
</p>
170186
<p class="note">
171187
KiCanvas is
172188
<a

‎src/kicanvas/services/vfs.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,35 @@ export class DragAndDropFileSystem extends VirtualFileSystem {
197197
initiate_download(await this.get(name));
198198
}
199199
}
200+
201+
/**
202+
* Virtual file system for local files
203+
*/
204+
export class LocalFileSystem extends VirtualFileSystem {
205+
constructor(private files: File[]) {
206+
super();
207+
}
208+
209+
override *list() {
210+
for (const entry of this.files) {
211+
yield entry.name;
212+
}
213+
}
214+
215+
override async has(name: string): Promise<boolean> {
216+
return this.files.find((f) => f.name == name) !== undefined;
217+
}
218+
219+
override async get(name: string): Promise<File> {
220+
const file = this.files.find((f) => f.name == name);
221+
if (file) {
222+
return file;
223+
} else {
224+
throw new Error(`File ${name} not found`);
225+
}
226+
}
227+
228+
override async download(name: string) {
229+
initiate_download(await this.get(name));
230+
}
231+
}

0 commit comments

Comments
 (0)