Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the inline source support. #56

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
12 changes: 12 additions & 0 deletions debug/embedded.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<kicanvas-embed>
<kicanvas-source>
(kicad_sch (version 20230121) (generator eeschema) (uuid
5d5ad125-5ef1-42a1-a410-a0c4ab262ca6) (paper "A4")
(title_block (title "KiCanvas inline sources test") (date
"2023-11-10") ) (lib_symbols ) (text "KiCanvas inline
sources test" (at 93.98 104.14 0) (effects (font (size 5 5)
(thickness 1) bold) (justify left bottom)) (uuid
27eb63d7-7111-4c0e-9985-c1ed90138e31) ) (sheet_instances
(path "/" (page "1")) ) )
</kicanvas-source>
</kicanvas-embed>
</main>
</body>

Expand Down
5 changes: 5 additions & 0 deletions docs/docs/embedding.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ This example shows how to use `<kicanvas-source>` along with inline KiCAD data.
- `basic` - zoom, pan, and select are available.
- `full` - complete interactive viewer, including side panels.
- `controlslist` - further customizes the available controls.

- `nooverlay` - don't show the "click or tap to interact" overlay.
- `nofullscreen` - don't show the fullscreen button. ⚠️
- `nodownload` - don't show the download button.
Expand All @@ -136,7 +137,11 @@ This example shows how to use `<kicanvas-source>` along with inline KiCAD data.
- `noinfo` - don't show the document info panel. ⚠️
- `nopreferences` - don't show the user preferences panel. ⚠️
- `nohelp` - don't show the help panel. ⚠️

- `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.
- `type` - the type of inline source. Available values include `sch` and `pcb`. When the `src` attribute is not empty and the inline source exists, the `src` attribute specified file will be loaded and be determined. Otherwise, the file type will be determined by this attribute. If this attribute is empty, the loader will try determined type by the first few characters.
- `originname` - the origin file name. Due to the KiCad dependence on the file name, when using an inline source specify it is a good choice (e.g. using the extern render for Gitea). The default name is `noname` when using the inline source, or file name in the URL when using the `src` attribute.

- `theme` - sets the color theme to use, valid values are `kicad` and `witchhazel`. ⚠️
- `zoom` - sets the initial view into the document. ⚠️
- `objects` - zooms to show all visible objects (default). ⚠️
Expand Down
6 changes: 2 additions & 4 deletions src/base/dom/drag-drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
Full text available at: https://opensource.org/licenses/MIT
*/

import {
DragAndDropFileSystem,
VirtualFileSystem,
} from "../../kicanvas/services/vfs";
import VirtualFileSystem from "../../kicanvas/services/vfs";
import DragAndDropFileSystem from "../../kicanvas/services/drop-vfs";

export class DropTarget {
constructor(elm: HTMLElement, callback: (fs: VirtualFileSystem) => void) {
Expand Down
1 change: 0 additions & 1 deletion src/kc-ui/focus-overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ export class KCUIFocusOverlay extends KCUIElement {

this.#intersection_observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
console.log(entry);
if (!entry.isIntersecting) {
this.classList.remove("has-focus");
}
Expand Down
78 changes: 69 additions & 9 deletions src/kicanvas/elements/kicanvas-embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ import {
import { KCUIElement } from "../../kc-ui";
import kc_ui_styles from "../../kc-ui/kc-ui.css";
import { Project } from "../project";
import { FetchFileSystem, VirtualFileSystem } from "../services/vfs";
import VirtualFileSystem from "../services/vfs";
import FetchFileSystem, { FetchFileSource } from "../services/fetch-vfs";
import type { KCBoardAppElement } from "./kc-board/app";
import type { KCSchematicAppElement } from "./kc-schematic/app";
import { Logger } from "../../base/log";

const log = new Logger("kicanvas:embedtag");

/**
*
* The `kicanvas-embed` label
*/
class KiCanvasEmbedElement extends KCUIElement {
static override styles = [
Expand Down Expand Up @@ -96,22 +100,72 @@ class KiCanvasEmbedElement extends KCUIElement {
async #setup_events() {}

async #load_src() {
const sources = [];
const sources: FetchFileSource[] = [];

if (this.src) {
sources.push(this.src);
sources.push(new FetchFileSource("uri", this.src));
}

for (const src_elm of this.querySelectorAll<KiCanvasSourceElement>(
"kicanvas-source",
)) {
const sre_eles =
this.querySelectorAll<KiCanvasSourceElement>("kicanvas-source");
for (const src_elm of sre_eles) {
if (src_elm.src) {
sources.push(src_elm.src);
// Append the source uri firstly
sources.push(new FetchFileSource("uri", src_elm.src));
} else if (src_elm.childNodes.length > 0) {
let content = "";

for (const child of src_elm.childNodes) {
if (child.nodeType === Node.TEXT_NODE) {
// Get the content and triming the CR,LF,space.
content += child.nodeValue ?? "";
} else {
log.warn(
"kicanvas-source children type is not vaild and that be skiped.",
);
continue;
}
}

content = content.trimStart();

// Determine the file extension name.
// That make `project.ts` determine the file type is possible.
let file_extname = "";
if (src_elm.type) {
if (src_elm.type === "sch") {
file_extname = "kicad_sch";
} else if (src_elm.type === "pcb") {
file_extname = "kicad_pcb";
} else {
log.warn('Invaild value of attribute "type"');
continue;
}
} else {
// "type" attribute is null, Try to determined the file type.
// sch: (kicad_sch ....
// pcb: (kicad_pcb ....
if (content.startsWith("(kicad_sch")) {
file_extname = "kicad_sch";
} else if (content.startsWith("(kicad_pcb")) {
file_extname = "kicad_pcb";
} else {
log.warn('Cannot determine the file "type"');
continue;
}
}
const filename = src_elm.originname ?? `noname.${file_extname}`;
log.info(`Determined the inline source as "${filename}"`);
// append to the sources
sources.push(new FetchFileSource("content", content, filename));
} else {
// That means this element is empty.
log.warn("kicanvas-source is empty.");
}
}

if (sources.length == 0) {
console.warn("No valid sources specified");
log.warn("No valid sources specified");
return;
}

Expand Down Expand Up @@ -180,6 +234,12 @@ class KiCanvasSourceElement extends CustomElement {

@attribute({ type: String })
src: string | null;

@attribute({ type: String })
type: string | null;

@attribute({ type: String })
originname: string | null;
}

window.customElements.define("kicanvas-source", KiCanvasSourceElement);
Expand Down
7 changes: 5 additions & 2 deletions src/kicanvas/elements/kicanvas-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { sprites_url } from "../icons/sprites";
import { Project } from "../project";
import { GitHub } from "../services/github";
import { GitHubFileSystem } from "../services/github-vfs";
import { FetchFileSystem, type VirtualFileSystem } from "../services/vfs";
import FetchFileSystem, { FetchFileSource } from "../services/fetch-vfs";
import type VirtualFileSystem from "../services/vfs";
import { KCBoardAppElement } from "./kc-board/app";
import { KCSchematicAppElement } from "./kc-schematic/app";

Expand Down Expand Up @@ -82,7 +83,9 @@ class KiCanvasShellElement extends KCUIElement {

later(async () => {
if (this.src) {
const vfs = new FetchFileSystem([this.src]);
const vfs = new FetchFileSystem([
new FetchFileSource("uri", this.src),
]);
await this.setup_project(vfs);
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/kicanvas/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type {
SchematicSheet,
SchematicSheetInstance,
} from "../kicad/schematic";
import type { VirtualFileSystem } from "./services/vfs";
import type VirtualFileSystem from "./services/vfs";

const log = new Logger("kicanvas:project");

Expand Down
89 changes: 89 additions & 0 deletions src/kicanvas/services/drop-vfs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
Copyright (c) 2023 XiangYyang
*/

import { initiate_download } from "../../base/dom/download";
import VirtualFileSystem from "./vfs";

/**
* Virtual file system for HTML drag and drop (DataTransfer)
*/
export default class DragAndDropFileSystem extends VirtualFileSystem {
constructor(private items: FileSystemFileEntry[]) {
super();
}

static async fromDataTransfer(dt: DataTransfer) {
let items: FileSystemEntry[] = [];

// Pluck items out as webkit entries (either FileSystemFileEntry or
// FileSystemDirectoryEntry)
for (let i = 0; i < dt.items.length; i++) {
const item = dt.items[i]?.webkitGetAsEntry();
if (item) {
items.push(item);
}
}

// If it's just one directory then open it and set all of our items
// to its contents.
if (items.length == 1 && items[0]?.isDirectory) {
const reader = (
items[0] as FileSystemDirectoryEntry
).createReader();

items = [];

await new Promise((resolve, reject) => {
reader.readEntries((entries) => {
for (const entry of entries) {
if (!entry.isFile) {
continue;
}
items.push(entry);
}
resolve(true);
}, reject);
});
}

return new DragAndDropFileSystem(items as FileSystemFileEntry[]);
}

public override *list() {
for (const entry of this.items) {
yield entry.name;
}
}

public override async has(name: string): Promise<boolean> {
for (const entry of this.items) {
if (entry.name == name) {
return true;
}
}
return false;
}

public override async get(name: string): Promise<File> {
let file_entry: FileSystemFileEntry | null = null;
for (const entry of this.items) {
if (entry.name == name) {
file_entry = entry;
break;
}
}

if (file_entry == null) {
throw new Error(`File ${name} not found!`);
}

return await new Promise((resolve, reject) => {
file_entry!.file(resolve, reject);
});
}

public async download(name: string) {
initiate_download(await this.get(name));
}
}
Loading