From f0fd2b0909239687953ccf6bd43bfc68898dbc21 Mon Sep 17 00:00:00 2001 From: Trey Wallis <40307803+trey-wallis@users.noreply.github.com> Date: Fri, 12 Apr 2024 17:07:59 -0600 Subject: [PATCH] feat: add basic handlers for file events --- src/event/event-manager.ts | 50 ++++++++++++++++++++++++++++++++++++ src/event/types.ts | 3 +++ src/main.ts | 34 ++++++++++++++++++++++++- src/react/index.tsx | 52 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/event/event-manager.ts create mode 100644 src/event/types.ts diff --git a/src/event/event-manager.ts b/src/event/event-manager.ts new file mode 100644 index 0000000..3c2ffce --- /dev/null +++ b/src/event/event-manager.ts @@ -0,0 +1,50 @@ +import { PluginEvent, EventCallback } from "./types"; + +export default class EventManager { + private static instance: EventManager; + private eventListeners: Record; + + private constructor() { + this.eventListeners = {} as Record; + } + + // Ensures only one instance is created + public static getInstance(): EventManager { + if (!EventManager.instance) { + EventManager.instance = new EventManager(); + } + return EventManager.instance; + } + + // Method to add an event listener + public on(eventName: PluginEvent, callback: EventCallback): void { + if (!this.eventListeners[eventName]) { + this.eventListeners[eventName] = []; + } + this.eventListeners[eventName].push(callback); + } + + // Method to remove an event listener + public off( + eventName: PluginEvent, + callbackToRemove: EventCallback + ): void { + if (!this.eventListeners[eventName]) { + return; + } + this.eventListeners[eventName] = this.eventListeners[eventName].filter( + (callback) => callback !== callbackToRemove + ); + } + + // Method to trigger all callbacks associated with an event + public emit(eventName: PluginEvent, ...data: unknown[]): void { + //console.log("[EventManager] emiting event:", eventName); + if (!this.eventListeners[eventName]) { + return; + } + this.eventListeners[eventName].forEach((callback) => { + callback(...data); + }); + } +} diff --git a/src/event/types.ts b/src/event/types.ts new file mode 100644 index 0000000..3daa0da --- /dev/null +++ b/src/event/types.ts @@ -0,0 +1,3 @@ +export type PluginEvent = "rename-file" | "create-file" | "delete-file" | "modify-file"; + +export type EventCallback = (...data: unknown[]) => void; diff --git a/src/main.ts b/src/main.ts index 4320dd7..618f840 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { Plugin, } from 'obsidian'; +import { Plugin, TFile, } from 'obsidian'; import VaultExplorerView from './obsidian/vault-explorer-view'; import VaultExplorerSettingsTab from './obsidian/vault-explorer-settings-tab'; @@ -6,6 +6,7 @@ import VaultExplorerSettingsTab from './obsidian/vault-explorer-settings-tab'; import { VaultExplorerPluginSettings } from './types'; import { VAULT_EXPLORER_VIEW } from './constants'; import _ from 'lodash'; +import EventManager from './event/event-manager'; const DEFAULT_SETTINGS: VaultExplorerPluginSettings = { @@ -53,10 +54,41 @@ export default class VaultExplorerPlugin extends Plugin { } }); + this.registerEvents(); this.addSettingTab(new VaultExplorerSettingsTab(this.app, this)); } + private registerEvents() { + //Callback if the file is renamed or moved + //This callback is already debounced by Obsidian + this.registerEvent(this.app.vault.on("rename", (file: TFile, oldPath: string) => { + if (file.extension !== "md") return; + EventManager.getInstance().emit("rename-file", oldPath, file.path); + })); + + //Callback if a file is deleted + //This callback is already debounced by Obsidian + this.registerEvent(this.app.vault.on("delete", (file: TFile) => { + if (file.extension !== "md") return; + EventManager.getInstance().emit("delete-file", file.path); + })); + + //Callback if a file is created + //This callback is already debounced by Obsidian + this.registerEvent(this.app.vault.on("create", (file: TFile) => { + if (file.extension !== "md") return; + EventManager.getInstance().emit("create-file", file.path); + })); + + //Callback if a file is modified + //This callback is already debounced by Obsidian + this.registerEvent(this.app.vault.on("modify", (file: TFile) => { + if (file.extension !== "md") return; + EventManager.getInstance().emit("modify-file", file.path); + })); + } + onunload() { } diff --git a/src/react/index.tsx b/src/react/index.tsx index 6055c56..3365494 100644 --- a/src/react/index.tsx +++ b/src/react/index.tsx @@ -8,6 +8,7 @@ import { useAppMount } from "./app-mount-provider"; import Checkbox from "./checkbox"; import Flex from "./flex"; import Stack from "./stack"; +import EventManager from "src/event/event-manager"; export default function ReactView() { const [folderPath, setFolderPath] = React.useState(""); @@ -27,6 +28,57 @@ export default function ReactView() { setOnlyCreatedToday(settings.filters.onlyCreatedToday); }, []); + const [, setRefreshTime] = React.useState(0); + + //TODO optimize + React.useEffect(() => { + const handleRenameFile = (oldPath: string, newPath: string) => { + setRefreshTime(Date.now()); + }; + + EventManager.getInstance().on("rename-file", handleRenameFile); + return () => { + EventManager.getInstance().off("rename-file", handleRenameFile); + }; + }, []); + + //TODO optimize + React.useEffect(() => { + const handleCreateFile = (oldPath: string, newPath: string) => { + setRefreshTime(Date.now()); + }; + + EventManager.getInstance().on("create-file", handleCreateFile); + return () => { + EventManager.getInstance().off("create-file", handleCreateFile); + }; + }, []); + + //TODO optimize + React.useEffect(() => { + const handleDeleteFile = (oldPath: string, newPath: string) => { + setRefreshTime(Date.now()); + }; + + EventManager.getInstance().on("delete-file", handleDeleteFile); + return () => { + EventManager.getInstance().off("delete-file", handleDeleteFile); + }; + }, []); + + //TODO optimize + //TODO should we use handle frontmatter change? + React.useEffect(() => { + const handleModifyFile = (oldPath: string, newPath: string) => { + setRefreshTime(Date.now()); + }; + + EventManager.getInstance().on("modify-file", handleModifyFile); + return () => { + EventManager.getInstance().off("modify-file", handleModifyFile); + }; + }, []); + React.useEffect(() => { onSettingsChange({ ...settings,