Skip to content

Commit b9b68cb

Browse files
authored
add: the inline source support (#157)
1 parent d5d10a8 commit b9b68cb

5 files changed

Lines changed: 266 additions & 24 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ www/kicanvas
55
coverage/
66
esbuild-meta.json
77

8+
.pyenv
89
.DS_Store

‎debug/embedded.html‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,50 @@
6262
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
6363
sunt in culpa qui officia deserunt mollit anim id est laborum.
6464
</p>
65+
<kicanvas-embed>
66+
<kicanvas-source>
67+
(kicad_sch (version 20230121) (generator eeschema) (uuid
68+
5d5ad125-5ef1-42a1-a410-a0c4ab262ca6) (paper "A4")
69+
(title_block (title "KiCanvas inline sources test") (date
70+
"2023-11-10") ) (lib_symbols ) (text "KiCanvas inline
71+
sources test" (at 93.98 104.14 0) (effects (font (size 5 5)
72+
(thickness 1) bold) (justify left bottom)) (uuid
73+
27eb63d7-7111-4c0e-9985-c1ed90138e31) ) (sheet_instances
74+
(path "/" (page "1")) ) )
75+
</kicanvas-source>
76+
</kicanvas-embed>
77+
<p>
78+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
79+
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
80+
enim ad minim veniam, quis nostrud exercitation ullamco laboris
81+
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
82+
in reprehenderit in voluptate velit esse cillum dolore eu fugiat
83+
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
84+
sunt in culpa qui officia deserunt mollit anim id est laborum.
85+
</p>
86+
<kicanvas-embed controls="full">
87+
<kicanvas-source name="sch1.kicad_sch">
88+
(kicad_sch (version 20230121) (generator eeschema) (uuid
89+
5d5ad125-5ef1-42a1-a410-a0c4ab262ca6) (paper "A4")
90+
(title_block (title "KiCanvas inline sources test 1") (date
91+
"2023-11-10") ) (lib_symbols ) (text "KiCanvas inline file
92+
SCH1" (at 93.98 104.14 0) (effects (font (size 5 5)
93+
(thickness 1) bold) (justify left bottom)) (uuid
94+
27eb63d7-7111-4c0e-9985-c1ed90138e31) ) (sheet_instances
95+
(path "/" (page "1")) ) )
96+
</kicanvas-source>
97+
<kicanvas-source name="sch2.kicad_sch">
98+
(kicad_sch (version 20230121) (generator eeschema) (uuid
99+
5d5ad125-5ef1-42a1-a410-a0c4ab262ca6) (paper "A4")
100+
(title_block (title "KiCanvas inline sources test 2") (date
101+
"2023-11-11") ) (lib_symbols ) (text "KiCanvas inline file
102+
SCH2" (at 90 100 0) (effects (font (size 5 5) (thickness 1)
103+
bold) (justify left bottom)) (uuid
104+
27eb63d7-7111-4c0e-9985-c1ed90138e31) ) (sheet_instances
105+
(path "/" (page "1")) ) )
106+
</kicanvas-source>
107+
<kicanvas-source src="examples/mcu.kicad_sch"></kicanvas-source>
108+
</kicanvas-embed>
65109
</main>
66110
</body>
67111

‎docs/docs/embedding.md‎

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# <kicanvas-embed\>: The KiCanvas embedded viewer element
22

33
<!-- load kicanvas -->
4+
45
<script type="module" src="/kicanvas/kicanvas.js"></script>
56

67
!!! warning "Work in progress"
@@ -98,22 +99,37 @@ You can switch between the displayed files using the project panel on the right
9899

99100
### Inline source
100101

101-
!!! warning "Not yet implemented"
102-
103-
This functionality hasn't been implemented yet
104-
105102
This example shows how to use `<kicanvas-source>` along with inline KiCAD data. In this case, it's a symbol copied from a schematic and pasted into the HTML source:
106103

107104
```html
108105
<kicanvas-embed>
109-
<kicanvas-source type="schematic">
110-
(lib_symbols (symbol "power:+12V" (power) (pin_names (offset 0)) (in_bom
111-
yes) (on_board yes) (property "Reference" "#PWR" (at 0 -3.81 0) (effects
112-
(font (size 1.27 1.27)) hide) ) ...
106+
<!-- Use it directly -->
107+
<kicanvas-source>
108+
(kicad_sch (version 20230121) (generator eeschema) (uuid
109+
5d5ad125-5ef1-42a1-a410-a0c4ab262ca6) (paper "A4") (title_block (title
110+
"KiCanvas inline sources") (date "2023-11-11") ) (lib_symbols ) (text
111+
"Hello World !!!" (at 90 100 0) (effects (font (size 5 5) (thickness 1)
112+
bold) (justify left bottom)) (uuid 27eb63d7-7111-4c0e-9985-c1ed90138e31)
113+
) (sheet_instances (path "/" (page "1")) ) )
113114
</kicanvas-source>
115+
116+
<!-- Or mixed with `src` tag -->
117+
<kicanvas-source src="/examples/simple.kicad_sch"></kicanvas-source>
114118
</kicanvas-embed>
115119
```
116120

121+
<kicanvas-embed controls="full">
122+
<kicanvas-source src="/examples/simple.kicad_sch"></kicanvas-source>
123+
<kicanvas-source name="inline.kicad_sch">
124+
(kicad_sch (version 20230121) (generator eeschema) (uuid
125+
5d5ad125-5ef1-42a1-a410-a0c4ab262ca6) (paper "A4") (title_block (title
126+
"KiCanvas inline sources") (date "2023-11-11") ) (lib_symbols ) (text
127+
"Inline source file" (at 90 100 0) (effects (font (size 5 5) (thickness 1)
128+
bold) (justify left bottom)) (uuid 27eb63d7-7111-4c0e-9985-c1ed90138e31)
129+
) (sheet_instances (path "/" (page "1")) ) )
130+
</kicanvas-source>
131+
</kicanvas-embed>
132+
117133
## Attributes
118134

119135
!!! warning "Not yet implemented"
@@ -137,6 +153,8 @@ This example shows how to use `<kicanvas-source>` along with inline KiCAD data.
137153
- `nopreferences` - don't show the user preferences panel. ⚠️
138154
- `nohelp` - don't show the help panel. ⚠️
139155
- `src` - the URL of the document to embed. If you want to show multiple documents within a single viewer, you can use multiple child `<kicanvas-source>` elements.
156+
- `type` - when providing the file source inline, this explicitly sets the file type. If not specified, KiCanvas will attempt to determine the type automatically. If specified, it should be one of `schematic`, `board`, `project`, or `worksheet`.
157+
- `name` - when providing the file source inline, this explicitly sets the file name. This is typically only necessary when there are multiple files within a project, as KiCAD uses the file name to link schematic sheets, drawing sheets, and PCBs together. If unspecified, KiCanvas will generate a file name like `inline_0.kicad_sch`.
140158
- `theme` - sets the color theme to use, valid values are `kicad` and `witchhazel`. ⚠️
141159
- `zoom` - sets the initial view into the document. ⚠️
142160
- `objects` - zooms to show all visible objects (default). ⚠️
@@ -150,11 +168,11 @@ This example shows how to use `<kicanvas-source>` along with inline KiCAD data.
150168

151169
This functionality hasn't been implemented yet
152170

153-
| Event Name | Fired When |
154-
| ---------------------------- | ------------------------------------------------------------------------------------------------- |
155-
| ⚠️ `kicanvas:click` | The user clicks or taps within the embedded document |
156-
| ⚠️ `kicanvas:documentchange` | The currently displayed document is changed, either through user interaction or programmatically. |
157-
| ⚠️ `kicanvas:error` | An error occurs while loading source files |
158-
| ⚠️ `kicanvas:load` | All sources files have been successfully loaded |
159-
| ⚠️ `kicanvas:loadstart` | KiCanvas begins loading source files |
160-
| ⚠️ `kicanvas:select` | The user selects (or deselects) an object within the document |
171+
| Event Name | Fired When |
172+
| --------------------------- | ------------------------------------------------------------------------------------------------- |
173+
| ⚠️`kicanvas:click` | The user clicks or taps within the embedded document |
174+
| ⚠️`kicanvas:documentchange` | The currently displayed document is changed, either through user interaction or programmatically. |
175+
| ⚠️`kicanvas:error` | An error occurs while loading source files |
176+
| ⚠️`kicanvas:load` | All sources files have been successfully loaded |
177+
| ⚠️`kicanvas:loadstart` | KiCanvas begins loading source files |
178+
| ⚠️`kicanvas:select` | The user selects (or deselects) an object within the document |

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

Lines changed: 139 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import { later } from "../../base/async";
8+
import { Logger } from "../../base/log";
89
import {
910
CSS,
1011
CustomElement,
@@ -15,12 +16,19 @@ import {
1516
import { KCUIElement } from "../../kc-ui";
1617
import kc_ui_styles from "../../kc-ui/kc-ui.css";
1718
import { Project } from "../project";
18-
import { FetchFileSystem, VirtualFileSystem } from "../services/vfs";
19+
import {
20+
FetchFileSystem,
21+
LocalFileSystem,
22+
MergedFileSystem,
23+
VirtualFileSystem,
24+
} from "../services/vfs";
1925
import type { KCBoardAppElement } from "./kc-board/app";
2026
import type { KCSchematicAppElement } from "./kc-schematic/app";
2127

28+
const log = new Logger("kicanvas:embedtag");
29+
2230
/**
23-
*
31+
* kicanvas-embed tag
2432
*/
2533
class KiCanvasEmbedElement extends KCUIElement {
2634
static override styles = [
@@ -99,26 +107,55 @@ class KiCanvasEmbedElement extends KCUIElement {
99107
async #setup_events() {}
100108

101109
async #load_src() {
102-
const sources = [];
110+
const url_src = [];
111+
const inline_file = [];
103112

104113
if (this.src) {
105-
sources.push(this.src);
114+
url_src.push(this.src);
106115
}
107116

117+
let inline_count = 0;
118+
108119
for (const src_elm of this.querySelectorAll<KiCanvasSourceElement>(
109120
"kicanvas-source",
110121
)) {
111122
if (src_elm.src) {
112-
sources.push(src_elm.src);
123+
// URL
124+
url_src.push(src_elm.src);
125+
} else if (src_elm.is_inline_source()) {
126+
// inline source
127+
const default_name = `inline_${inline_count}`;
128+
inline_count += 1;
129+
130+
const file = src_elm.load_inline_source(default_name);
131+
132+
if (file) {
133+
log.info(
134+
`Determined inline source ${file.name}, ${file.size} bytes`,
135+
);
136+
137+
inline_file.push(file);
138+
}
113139
}
114140
}
115141

116-
if (sources.length == 0) {
117-
console.warn("No valid sources specified");
142+
// maybe we have a better way to merge two VFS
143+
// we need load file from File blobs and URLs
144+
145+
const url_vfs =
146+
url_src.length === 0
147+
? null
148+
: new FetchFileSystem(url_src, this.custom_resolver);
149+
150+
const inline_vfs =
151+
inline_file.length === 0 ? null : new LocalFileSystem(inline_file);
152+
153+
if (url_vfs === null && inline_vfs === null) {
154+
log.warn("No valid sources specified");
118155
return;
119156
}
120157

121-
const vfs = new FetchFileSystem(sources, this.custom_resolver);
158+
const vfs = new MergedFileSystem([url_vfs, inline_vfs]);
122159
await this.#setup_project(vfs);
123160
}
124161

@@ -173,6 +210,13 @@ class KiCanvasEmbedElement extends KCUIElement {
173210

174211
window.customElements.define("kicanvas-embed", KiCanvasEmbedElement);
175212

213+
enum KiCanvasSourceType {
214+
Schematic = "schematic",
215+
Board = "board",
216+
Project = "project",
217+
Worksheet = "worksheet",
218+
}
219+
176220
class KiCanvasSourceElement extends CustomElement {
177221
constructor() {
178222
super();
@@ -181,8 +225,95 @@ class KiCanvasSourceElement extends CustomElement {
181225
this.style.display = "none";
182226
}
183227

228+
is_inline_source(): boolean {
229+
return this.src === null && this.childNodes.length > 0;
230+
}
231+
232+
load_inline_source(default_name: string | null = null): File | undefined {
233+
let content = "";
234+
235+
for (const child of this.childNodes) {
236+
if (child.nodeType === Node.TEXT_NODE) {
237+
// Get the content and triming the CR,LF,space.
238+
content += child.nodeValue ?? "";
239+
} else {
240+
log.warn(
241+
`kicanvas-source children ${child.nodeType} are invaild.`,
242+
);
243+
}
244+
}
245+
246+
content = content.trimStart();
247+
248+
// determine the file name
249+
let file_name;
250+
if (this.name) {
251+
file_name = this.name;
252+
} else {
253+
const typ =
254+
this.type ?? KiCanvasSourceElement.determine_file_type(content);
255+
256+
if (typ === undefined) {
257+
log.warn(
258+
`Unknown file type, content: ${content.slice(0, 64)}...`,
259+
);
260+
return undefined;
261+
}
262+
263+
const ext = KiCanvasSourceElement.get_file_ext(typ);
264+
265+
file_name = (default_name ?? "noname") + ext;
266+
}
267+
268+
if (content.length === 0) {
269+
log.warn(`kicanvas-source content ${file_name} is empty.`);
270+
return undefined;
271+
}
272+
273+
const file_blob = new Blob([content], { type: "text/plain" });
274+
const file = new File([file_blob], file_name);
275+
276+
return file;
277+
}
278+
279+
private static determine_file_type(
280+
content: string,
281+
): KiCanvasSourceType | undefined {
282+
if (content.startsWith("(kicad_sch")) {
283+
return KiCanvasSourceType.Schematic;
284+
} else if (content.startsWith("(kicad_pcb")) {
285+
return KiCanvasSourceType.Board;
286+
} else if (content.startsWith("(kicad_wks")) {
287+
return KiCanvasSourceType.Worksheet;
288+
} else if (content.startsWith("{")) {
289+
// project? maybe we need try parsing the json
290+
return KiCanvasSourceType.Project;
291+
}
292+
293+
return undefined;
294+
}
295+
296+
private static get_file_ext(typ: KiCanvasSourceType): string {
297+
switch (typ) {
298+
case KiCanvasSourceType.Schematic:
299+
return ".kicad_sch";
300+
case KiCanvasSourceType.Board:
301+
return ".kicad_pcb";
302+
case KiCanvasSourceType.Worksheet:
303+
return ".kicad_wks";
304+
case KiCanvasSourceType.Project:
305+
return ".kicad_prj";
306+
}
307+
}
308+
184309
@attribute({ type: String })
185310
src: string | null;
311+
312+
@attribute({ type: String })
313+
type: KiCanvasSourceType | null;
314+
315+
@attribute({ type: String })
316+
name: string | null;
186317
}
187318

188319
window.customElements.define("kicanvas-source", KiCanvasSourceElement);

‎src/kicanvas/services/vfs.ts‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,51 @@ export class LocalFileSystem extends VirtualFileSystem {
229229
initiate_download(await this.get(name));
230230
}
231231
}
232+
233+
/**
234+
* Merge two virtual file systems into one
235+
*/
236+
export class MergedFileSystem extends VirtualFileSystem {
237+
private fs_list: VirtualFileSystem[];
238+
239+
constructor(fs: (VirtualFileSystem | null)[]) {
240+
super();
241+
this.fs_list = fs.filter((f) => f !== null);
242+
}
243+
244+
override *list() {
245+
for (const fs of this.fs_list) {
246+
yield* fs.list();
247+
}
248+
}
249+
250+
override async has(name: string): Promise<boolean> {
251+
for (const fs of this.fs_list) {
252+
if (await fs.has(name)) {
253+
return true;
254+
}
255+
}
256+
257+
return false;
258+
}
259+
260+
override async get(name: string): Promise<File> {
261+
for (const fs of this.fs_list) {
262+
if (await fs.has(name)) {
263+
return await fs.get(name);
264+
}
265+
}
266+
267+
throw new Error(`File ${name} not found`);
268+
}
269+
270+
override async download(name: string) {
271+
for (const fs of this.fs_list) {
272+
if (await fs.has(name)) {
273+
return await fs.download(name);
274+
}
275+
}
276+
277+
throw new Error(`File ${name} not found`);
278+
}
279+
}

0 commit comments

Comments
 (0)