feat(*): svelte 5 panel view sharing the gateway ui, obsidian theme, preview and tests
This commit is contained in:
1 parent
a1bf4db905
commit
04400c5076
33 files changed
+3378
-1803
No files matched your search
+136
@@ -0,0 +1,136 @@
|
||||
import type { ViewStateResult, WorkspaceLeaf } from "obsidian";
|
||||
import { ItemView, TFile } from "obsidian";
|
||||
import { mount, unmount } from "svelte";
|
||||
import type BeaverPlugin from "./main";
|
||||
import { PanelState } from "./state.svelte";
|
||||
import App from "./ui/app.svelte";
|
||||
|
||||
export const VIEW_TYPE_PANEL = "beaver-panel";
|
||||
|
||||
interface SavedState {
|
||||
follow?: unknown;
|
||||
selected?: unknown;
|
||||
}
|
||||
|
||||
// One panel leaf: a sidedock column, a tab, or the phone screen. Follows
|
||||
// the active note's ``conversation_id`` unless pinned, and remembers both
|
||||
// across restarts through the workspace layout.
|
||||
export class PanelView extends ItemView {
|
||||
readonly state = new PanelState();
|
||||
private readonly plugin: BeaverPlugin;
|
||||
private root: Record<string, unknown> | null = null;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: BeaverPlugin) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
this.navigation = false;
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return VIEW_TYPE_PANEL;
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return "Beaver";
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return "bot";
|
||||
}
|
||||
|
||||
getState(): Record<string, unknown> {
|
||||
return { follow: this.state.follow, selected: this.state.selected };
|
||||
}
|
||||
|
||||
async setState(state: unknown, result: ViewStateResult): Promise<void> {
|
||||
const saved = (state ?? {}) as SavedState;
|
||||
if (typeof saved.follow === "boolean") {
|
||||
this.state.follow = saved.follow;
|
||||
}
|
||||
if (typeof saved.selected === "string" || saved.selected === null) {
|
||||
this.state.selected = saved.selected;
|
||||
}
|
||||
await super.setState(state, result);
|
||||
}
|
||||
|
||||
onOpen(): Promise<void> {
|
||||
this.contentEl.empty();
|
||||
this.contentEl.style.padding = "0";
|
||||
this.contentEl.style.overflow = "hidden";
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("file-open", (file) => {
|
||||
// ``null`` means the active leaf is not a file (this panel got
|
||||
// focus, say): keep following the last note rather than drop it.
|
||||
if (file) {
|
||||
this.follow(file);
|
||||
}
|
||||
})
|
||||
);
|
||||
this.registerEvent(
|
||||
this.app.metadataCache.on("changed", (file) => {
|
||||
// The note we follow just got its conversation_id - typically
|
||||
// right after the first send. Re-resolve.
|
||||
if (file.path === this.state.sourcePath) {
|
||||
this.follow(file);
|
||||
}
|
||||
})
|
||||
);
|
||||
this.follow(this.app.workspace.getActiveFile());
|
||||
this.plugin.views.add(this);
|
||||
this.mountApp();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async onClose(): Promise<void> {
|
||||
this.plugin.views.delete(this);
|
||||
await this.unmountApp();
|
||||
this.contentEl.empty();
|
||||
}
|
||||
|
||||
// The active note changed: remember it, and switch to its conversation
|
||||
// when following and it has one.
|
||||
follow(file: TFile | null): void {
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
this.state.sourcePath = file.path;
|
||||
if (!this.state.follow) {
|
||||
return;
|
||||
}
|
||||
const id = this.plugin.conversationIdOf(file);
|
||||
if (id) {
|
||||
this.state.selected = id;
|
||||
}
|
||||
}
|
||||
|
||||
show(id: string): void {
|
||||
this.state.selected = id;
|
||||
}
|
||||
|
||||
// Settings changed: the client is built from them, so rebuild the tree.
|
||||
async remount(): Promise<void> {
|
||||
await this.unmountApp();
|
||||
this.mountApp();
|
||||
}
|
||||
|
||||
private mountApp(): void {
|
||||
this.root = mount(App, {
|
||||
props: {
|
||||
app: this.app,
|
||||
client: this.plugin.apiClient(),
|
||||
component: this,
|
||||
onOpenSettings: () => this.plugin.openSettings(),
|
||||
state: this.state,
|
||||
vaultSubpath: () => this.plugin.settings.vaultSubpath,
|
||||
},
|
||||
target: this.contentEl,
|
||||
});
|
||||
}
|
||||
|
||||
private async unmountApp(): Promise<void> {
|
||||
if (this.root) {
|
||||
await unmount(this.root);
|
||||
this.root = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user