242 lines
6.6 KiB
TypeScript
242 lines
6.6 KiB
TypeScript
import { ItemView, WorkspaceLeaf, setIcon } from "obsidian";
|
|
import { OPENCODE_VIEW_TYPE } from "./types";
|
|
import { OPENCODE_ICON_NAME } from "./icons";
|
|
import type OpenCodePlugin from "./main";
|
|
import { ProcessState } from "./ProcessManager";
|
|
|
|
export class OpenCodeView extends ItemView {
|
|
plugin: OpenCodePlugin;
|
|
private iframeEl: HTMLIFrameElement | null = null;
|
|
private currentState: ProcessState = "stopped";
|
|
private unsubscribeStateChange: (() => void) | null = null;
|
|
|
|
constructor(leaf: WorkspaceLeaf, plugin: OpenCodePlugin) {
|
|
super(leaf);
|
|
this.plugin = plugin;
|
|
}
|
|
|
|
getViewType(): string {
|
|
return OPENCODE_VIEW_TYPE;
|
|
}
|
|
|
|
getDisplayText(): string {
|
|
return "OpenCode";
|
|
}
|
|
|
|
getIcon(): string {
|
|
return OPENCODE_ICON_NAME;
|
|
}
|
|
|
|
async onOpen(): Promise<void> {
|
|
this.contentEl.empty();
|
|
this.contentEl.addClass("opencode-container");
|
|
|
|
// Subscribe to state changes
|
|
this.unsubscribeStateChange = this.plugin.onProcessStateChange((state) => {
|
|
this.currentState = state;
|
|
this.updateView();
|
|
});
|
|
|
|
// Initial render
|
|
this.currentState = this.plugin.getProcessState();
|
|
this.updateView();
|
|
|
|
// Start server if not running (lazy start) - don't await to avoid blocking view open
|
|
if (this.currentState === "stopped") {
|
|
this.plugin.startServer();
|
|
}
|
|
}
|
|
|
|
async onClose(): Promise<void> {
|
|
// Unsubscribe from state changes to prevent memory leak
|
|
if (this.unsubscribeStateChange) {
|
|
this.unsubscribeStateChange();
|
|
this.unsubscribeStateChange = null;
|
|
}
|
|
|
|
// Clean up iframe
|
|
if (this.iframeEl) {
|
|
this.iframeEl.src = "about:blank";
|
|
this.iframeEl = null;
|
|
}
|
|
}
|
|
|
|
private updateView(): void {
|
|
switch (this.currentState) {
|
|
case "stopped":
|
|
this.renderStoppedState();
|
|
break;
|
|
case "starting":
|
|
this.renderStartingState();
|
|
break;
|
|
case "running":
|
|
this.renderRunningState();
|
|
break;
|
|
case "error":
|
|
this.renderErrorState();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private renderStoppedState(): void {
|
|
this.contentEl.empty();
|
|
|
|
const statusContainer = this.contentEl.createDiv({
|
|
cls: "opencode-status-container",
|
|
});
|
|
|
|
const iconEl = statusContainer.createDiv({ cls: "opencode-status-icon" });
|
|
setIcon(iconEl, "power-off");
|
|
|
|
statusContainer.createEl("h3", { text: "OpenCode is stopped" });
|
|
statusContainer.createEl("p", {
|
|
text: "Click the button below to start the OpenCode server.",
|
|
cls: "opencode-status-message",
|
|
});
|
|
|
|
const startButton = statusContainer.createEl("button", {
|
|
text: "Start OpenCode",
|
|
cls: "mod-cta",
|
|
});
|
|
startButton.addEventListener("click", () => {
|
|
this.plugin.startServer();
|
|
});
|
|
}
|
|
|
|
private renderStartingState(): void {
|
|
this.contentEl.empty();
|
|
|
|
const statusContainer = this.contentEl.createDiv({
|
|
cls: "opencode-status-container",
|
|
});
|
|
|
|
const loadingEl = statusContainer.createDiv({ cls: "opencode-loading" });
|
|
loadingEl.createDiv({ cls: "opencode-spinner" });
|
|
|
|
statusContainer.createEl("h3", { text: "Starting OpenCode..." });
|
|
statusContainer.createEl("p", {
|
|
text: "Please wait while the server starts up.",
|
|
cls: "opencode-status-message",
|
|
});
|
|
}
|
|
|
|
private renderRunningState(): void {
|
|
this.contentEl.empty();
|
|
|
|
// Create header with controls
|
|
const headerEl = this.contentEl.createDiv({ cls: "opencode-header" });
|
|
|
|
const titleSection = headerEl.createDiv({ cls: "opencode-header-title" });
|
|
const iconEl = titleSection.createSpan();
|
|
setIcon(iconEl, OPENCODE_ICON_NAME);
|
|
titleSection.createSpan({ text: "OpenCode" });
|
|
|
|
const actionsEl = headerEl.createDiv({ cls: "opencode-header-actions" });
|
|
|
|
// Reload button
|
|
const reloadButton = actionsEl.createEl("button", {
|
|
attr: { "aria-label": "Reload" },
|
|
});
|
|
setIcon(reloadButton, "refresh-cw");
|
|
reloadButton.addEventListener("click", () => {
|
|
this.reloadIframe();
|
|
});
|
|
|
|
// Open in browser button
|
|
const externalButton = actionsEl.createEl("button", {
|
|
attr: { "aria-label": "Open in browser" },
|
|
});
|
|
setIcon(externalButton, "external-link");
|
|
externalButton.addEventListener("click", () => {
|
|
window.open(this.plugin.getServerUrl(), "_blank");
|
|
});
|
|
|
|
// Stop button
|
|
const stopButton = actionsEl.createEl("button", {
|
|
attr: { "aria-label": "Stop server" },
|
|
});
|
|
setIcon(stopButton, "square");
|
|
stopButton.addEventListener("click", () => {
|
|
this.plugin.stopServer();
|
|
});
|
|
|
|
// Create iframe container
|
|
const iframeContainer = this.contentEl.createDiv({
|
|
cls: "opencode-iframe-container",
|
|
});
|
|
|
|
this.iframeEl = iframeContainer.createEl("iframe", {
|
|
cls: "opencode-iframe",
|
|
attr: {
|
|
src: this.plugin.getServerUrl(),
|
|
frameborder: "0",
|
|
allow: "clipboard-read; clipboard-write",
|
|
},
|
|
});
|
|
|
|
// Handle iframe load errors
|
|
this.iframeEl.addEventListener("error", () => {
|
|
console.error("Failed to load OpenCode iframe");
|
|
});
|
|
}
|
|
|
|
private renderErrorState(): void {
|
|
this.contentEl.empty();
|
|
|
|
const statusContainer = this.contentEl.createDiv({
|
|
cls: "opencode-status-container opencode-error",
|
|
});
|
|
|
|
const iconEl = statusContainer.createDiv({ cls: "opencode-status-icon" });
|
|
setIcon(iconEl, "alert-circle");
|
|
|
|
statusContainer.createEl("h3", { text: "Failed to start OpenCode" });
|
|
|
|
// Display specific error message if available
|
|
const errorMessage = this.plugin.getLastError();
|
|
if (errorMessage) {
|
|
statusContainer.createEl("p", {
|
|
text: errorMessage,
|
|
cls: "opencode-status-message opencode-error-message",
|
|
});
|
|
} else {
|
|
statusContainer.createEl("p", {
|
|
text: "There was an error starting the OpenCode server.",
|
|
cls: "opencode-status-message",
|
|
});
|
|
}
|
|
|
|
const buttonContainer = statusContainer.createDiv({
|
|
cls: "opencode-button-group",
|
|
});
|
|
|
|
const retryButton = buttonContainer.createEl("button", {
|
|
text: "Retry",
|
|
cls: "mod-cta",
|
|
});
|
|
retryButton.addEventListener("click", () => {
|
|
this.plugin.startServer();
|
|
});
|
|
|
|
const settingsButton = buttonContainer.createEl("button", {
|
|
text: "Open Settings",
|
|
});
|
|
settingsButton.addEventListener("click", () => {
|
|
(this.app as any).setting.open();
|
|
(this.app as any).setting.openTabById("obsidian-opencode");
|
|
});
|
|
}
|
|
|
|
private reloadIframe(): void {
|
|
if (this.iframeEl) {
|
|
const src = this.iframeEl.src;
|
|
this.iframeEl.src = "about:blank";
|
|
setTimeout(() => {
|
|
if (this.iframeEl) {
|
|
this.iframeEl.src = src;
|
|
}
|
|
}, 100);
|
|
}
|
|
}
|
|
}
|