From d77162ddf1abd48672494272a9068b94956ac296 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Fri, 20 Dec 2024 17:35:22 +0000 Subject: [PATCH] Add history view --- plugin/src/plugin.ts | 35 ++++---- .../apply-remote-changes-locally.ts | 1 + plugin/src/tracing/sync-history.ts | 27 +++--- plugin/src/views/history-view.ts | 89 +++++++++++++++++++ plugin/src/views/status-bar.ts | 6 +- plugin/src/views/sync-view.ts | 40 --------- 6 files changed, 130 insertions(+), 68 deletions(-) create mode 100644 plugin/src/views/history-view.ts delete mode 100644 plugin/src/views/sync-view.ts diff --git a/plugin/src/plugin.ts b/plugin/src/plugin.ts index 260b9e5..535ecd7 100644 --- a/plugin/src/plugin.ts +++ b/plugin/src/plugin.ts @@ -4,7 +4,7 @@ import { Plugin } from "obsidian"; import * as lib from "../../backend/sync_lib/pkg/sync_lib.js"; import * as wasmBin from "../../backend/sync_lib/pkg/sync_lib_bg.wasm"; import { SyncSettingsTab } from "./views/settings-tab"; -import { SyncView } from "./views/sync-view"; +import { HistoryView } from "./views/history-view.js"; import { ObsidianFileEventHandler } from "./events/obisidan-event-handler.js"; import { SyncService } from "./services/sync-service"; @@ -15,6 +15,7 @@ import { applyLocalChangesRemotely } from "./sync-operations/apply-local-changes import { StatusBar } from "./views/status-bar"; import { Logger } from "./tracing/logger.js"; import { SyncHistory } from "./tracing/sync-history.js"; +import { LogsView } from "./views/logs-view.js"; export default class SyncPlugin extends Plugin { private remoteListenerIntervalId: number | null = null; @@ -115,14 +116,22 @@ export default class SyncPlugin extends Plugin { } }); - this.registerView(SyncView.TYPE, (leaf) => new SyncView(leaf)); - - const ribbonIconEl = this.addRibbonIcon( - "dice", - "Sample Plugin", - async (_: MouseEvent) => this.activateView() + this.registerView( + HistoryView.TYPE, + (leaf) => new HistoryView(leaf, this.history) + ); + this.registerView(LogsView.TYPE, (leaf) => new LogsView(leaf)); + + this.addRibbonIcon( + HistoryView.ICON, + "Open VaultLink events", + async (_: MouseEvent) => this.activateView(HistoryView.TYPE) + ); + this.addRibbonIcon( + LogsView.ICON, + "Open VaultLink logs", + async (_: MouseEvent) => this.activateView(LogsView.TYPE) ); - ribbonIconEl.addClass("my-plugin-ribbon-class"); Logger.getInstance().info("Plugin loaded"); } @@ -133,23 +142,19 @@ export default class SyncPlugin extends Plugin { } } - private async activateView(): Promise { + private async activateView(type: string): Promise { const { workspace } = this.app; let leaf: WorkspaceLeaf | null = null; - const leaves = workspace.getLeavesOfType(SyncView.TYPE); + const leaves = workspace.getLeavesOfType(type); if (leaves.length > 0) { - // A leaf with our view already exists, use that [leaf] = leaves; } else { - // Our view could not be found in the workspace, create a new leaf - // In the right sidebar for it leaf = workspace.getRightLeaf(false); - await leaf?.setViewState({ type: SyncView.TYPE, active: true }); + await leaf?.setViewState({ type: type, active: true }); } - // "Reveal" the leaf in case it is in a collapsed sidebar if (leaf) { await workspace.revealLeaf(leaf); } diff --git a/plugin/src/sync-operations/apply-remote-changes-locally.ts b/plugin/src/sync-operations/apply-remote-changes-locally.ts index ddf7ec5..3b8840c 100644 --- a/plugin/src/sync-operations/apply-remote-changes-locally.ts +++ b/plugin/src/sync-operations/apply-remote-changes-locally.ts @@ -31,6 +31,7 @@ export async function applyRemoteChangesLocally({ } isRunning = true; + try { const remote = await syncServer.getAll(database.getLastSeenUpdateId()); diff --git a/plugin/src/tracing/sync-history.ts b/plugin/src/tracing/sync-history.ts index 292d7f6..2c4e451 100644 --- a/plugin/src/tracing/sync-history.ts +++ b/plugin/src/tracing/sync-history.ts @@ -37,15 +37,16 @@ export class SyncHistory { private static readonly MAX_ENTRIES = 1000; private entries: HistoryEntry[] = []; - private readonly requestCountListeners: ((status: HistoryStats) => void)[] = - []; + private readonly syncHistoryUpdateListeners: (( + status: HistoryStats + ) => void)[] = []; private status: HistoryStats = { success: 0, error: 0, }; - public getMessages(): HistoryEntry[] { - return this.entries; + public getEntries(): HistoryEntry[] { + return [...this.entries]; } public reset(): void { @@ -54,15 +55,15 @@ export class SyncHistory { success: 0, error: 0, }; - this.requestCountListeners.forEach((listener) => { + this.syncHistoryUpdateListeners.forEach((listener) => { listener(this.status); }); } - public addSyncHistoryStatsChangeListener( - listener: (status: HistoryStats) => void + public addSyncHistoryUpdateListener( + listener: (stats: HistoryStats) => void ): void { - this.requestCountListeners.push(listener); + this.syncHistoryUpdateListeners.push(listener); listener({ ...this.status }); } @@ -75,15 +76,21 @@ export class SyncHistory { if (entry.status === SyncStatus.SUCCESS) { this.status.success++; - Logger.getInstance().info(`Synced file: ${entry.relativePath}`); + Logger.getInstance().info( + `History entry: ${entry.relativePath} - ${entry.message}` + ); } else if (entry.status === SyncStatus.ERROR) { this.status.error++; Logger.getInstance().error( `Error syncing file: ${entry.relativePath} - ${entry.message}` ); + } else { + Logger.getInstance().debug( + `No-op syncing file: ${entry.relativePath} - ${entry.message}` + ); } - this.requestCountListeners.forEach((listener) => { + this.syncHistoryUpdateListeners.forEach((listener) => { listener(this.status); }); diff --git a/plugin/src/views/history-view.ts b/plugin/src/views/history-view.ts new file mode 100644 index 0000000..ed7ead1 --- /dev/null +++ b/plugin/src/views/history-view.ts @@ -0,0 +1,89 @@ +import type { WorkspaceLeaf } from "obsidian"; +import { ItemView } from "obsidian"; +import type { SyncHistory } from "src/tracing/sync-history"; +import { SyncSource } from "src/tracing/sync-history"; +import { intlFormatDistance } from "date-fns"; + +export class HistoryView extends ItemView { + public static readonly TYPE = "example-view"; + public static readonly ICON = "square-stack"; + private timer: NodeJS.Timer | null = null; + + public constructor( + leaf: WorkspaceLeaf, + private readonly history: SyncHistory + ) { + super(leaf); + this.icon = HistoryView.ICON; + + // eslint-disable-next-line @typescript-eslint/no-misused-promises + history.addSyncHistoryUpdateListener(async () => { + await this.updateView(); + }); + } + + private static formatSource(source: SyncSource | undefined): string { + switch (source) { + case SyncSource.PUSH: + return " ⤴️"; + case SyncSource.PULL: + return " ⤵️"; + default: + return ""; + } + } + + private static formatTime(timestamp: Date): string { + return intlFormatDistance(timestamp, new Date()); + } + + public getViewType(): string { + return HistoryView.TYPE; + } + + public getDisplayText(): string { + return "Example view"; + } + + public async onOpen(): Promise { + await this.updateView(); + // eslint-disable-next-line @typescript-eslint/no-misused-promises + this.timer = setInterval(async () => this.updateView(), 500); + } + + public async onClose(): Promise { + if (this.timer) { + clearInterval(this.timer); + } + } + + private async updateView(): Promise { + const container = this.containerEl.children[1]; + container.empty(); + container.createEl("h4", { text: "VaultLink History" }); + + this.history + .getEntries() + .reverse() + .forEach((entry) => { + const card = container.createDiv({ + cls: ["history-card", entry.status.toLocaleLowerCase()], + }); + const header = card.createDiv({ cls: "history-card-header" }); + header.createEl("h5", { + text: + entry.relativePath + + HistoryView.formatSource(entry.source), + cls: "history-card-title", + }); + header.createSpan({ + text: HistoryView.formatTime(entry.timestamp), + cls: "history-card-timestamp", + }); + card.createEl("p", { + text: entry.message, + cls: "history-card-message", + }); + }); + } +} diff --git a/plugin/src/views/status-bar.ts b/plugin/src/views/status-bar.ts index c1f3517..a4d59d8 100644 --- a/plugin/src/views/status-bar.ts +++ b/plugin/src/views/status-bar.ts @@ -6,9 +6,9 @@ export class StatusBar { public constructor(plugin: Plugin, history: SyncHistory) { this.statusBarItem = plugin.addStatusBarItem(); - history.addSyncHistoryStatsChangeListener((status) => - { this.updateStatus(status); } - ); + history.addSyncHistoryUpdateListener((status) => { + this.updateStatus(status); + }); } private updateStatus({ success, error }: HistoryStats): void { diff --git a/plugin/src/views/sync-view.ts b/plugin/src/views/sync-view.ts deleted file mode 100644 index 5d81c37..0000000 --- a/plugin/src/views/sync-view.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { WorkspaceLeaf } from "obsidian"; -import { ItemView } from "obsidian"; -import { LogLevel, Logger } from "src/tracing/logger"; - -export class SyncView extends ItemView { - public static readonly TYPE = "example-view"; - - public constructor(leaf: WorkspaceLeaf) { - super(leaf); - } - - public getViewType(): string { - return SyncView.TYPE; - } - - public getDisplayText(): string { - return "Example view"; - } - - public async onOpen(): Promise { - const container = this.containerEl.children[1]; - container.empty(); - container.createEl("h4", { text: "Example view" }); - - // eslint-disable-next-line @typescript-eslint/no-misused-promises - setInterval(async () => this.updateView(), 1000); - } - - public async updateView(): Promise { - const container = this.containerEl.children[1]; - container.empty(); - - const messages = Logger.getInstance() - .getMessages(LogLevel.DEBUG) - .map((message) => message.toString()) - .join("\n"); - - container.createEl("pre", { text: messages }); - } -}