From 42e2fed4e59008bdec1c3b3b116733ae2c217d7f Mon Sep 17 00:00:00 2001 From: Hananoshika Yomaru Date: Mon, 16 Oct 2023 20:25:09 -0700 Subject: [PATCH] some new features --- README.md | 36 +- bun.lockb | Bin 102626 -> 103026 bytes package.json | 3 +- src/typings/types-obsidian.d.ts | 298 +++++++++ src/util/State.ts | 3 +- src/views/graph/ForceGraph.ts | 151 ++++- src/views/graph/Graph3dView.ts | 2 +- src/views/leafView.ts | 598 ++++++++++++++++++ src/views/settings/GraphSettingsView.ts | 28 +- .../settings/categories/AddNodeGroupButton.ts | 6 +- .../settings/categories/FilterSettingsView.ts | 117 ++-- .../settings/categories/GroupSettingItem.ts | 1 + .../settings/categories/addNodeGroups.ts | 1 + 13 files changed, 1139 insertions(+), 105 deletions(-) create mode 100644 src/typings/types-obsidian.d.ts create mode 100644 src/views/leafView.ts diff --git a/README.md b/README.md index bda7f56..cfdecd9 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,41 @@ ## Obsidian 3D Graph (Yomaru) > This is a fork from the original https://github.com/AlexW00/obsidian-3d-graph -> -> ⚠️ This plugin is still under early development. Please open github issue if you have problems. +> +> ⚠️ This plugin is still under early development. Please open github issue if you have problems. A 3D Graph for Obsidian! -## Installation +see the demo: https://www.youtube.com/watch?v=HnqXH6z4WrY -### through BRAT +## Installation -### Manual installation +### through BRAT -## Showcase: +1. install the BRAT plugin +2. go to the plugin option, add beta plugin, copy and paste the link of this repo +3. the plugin will automatically appear in the list of installed community plugins, enabled this plugin -https://www.youtube.com/watch?v=HnqXH6z4WrY +### Manual installation +1. cd to `.obsidian/plugins` +2. git clone this repo +3. `cd obsidian-3d-graph && bun install && bun run build` +4. there you go 🎉 +## Development + +1. cd to `.obsidian/plugins` +2. git clone this repo +3. `cd obsidian-3d-graph && bun install && bun run dev` +4. there you go 🎉 + +for release, just run `bun release` + +## Note + +## Say thank you + +If you are enjoying this plugin then please support my work and enthusiasm by buying me a coffee on https://www.buymeacoffee.com/yomaru. + +Buy Me A Coffee diff --git a/bun.lockb b/bun.lockb index a32d9363cd8caf6ab7c96313bad6aceb9efab7b8..d8ed427914c6ba4a8c26bcfa620ff2939ef8428f 100755 GIT binary patch delta 18723 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%0D} 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 string; + openGlobalSearch: (query: string) => void; + }; +} + +interface OpenViewState { + eState?: EphemeralState; + state?: { mode: string }; + active?: boolean; +} + +declare class QuickSwitcherModal extends SuggestModal { + getSuggestions(query: string): TFile[] | Promise; + + renderSuggestion(value: TFile, el: HTMLElement): unknown; + + onChooseSuggestion(item: TFile, evt: MouseEvent | KeyboardEvent): unknown; +} + +interface InternalPlugin { + disable(): void; + + enable(): void; + + enabled: boolean; + _loaded: boolean; + instance: { name: string; id: string }; +} + +interface GraphPlugin extends InternalPlugin { + views: { localgraph: (leaf: WorkspaceLeaf) => GraphView }; +} + +interface GraphView extends View { + engine: typeof Object; + renderer: { worker: { terminate(): void } }; +} + +interface QuickSwitcherPlugin extends InternalPlugin { + instance: { + name: string; + id: string; + QuickSwitcherModal: typeof QuickSwitcherModal; + }; +} + +declare global { + const i18next: { + t(id: string): string; + }; + + interface Window { + activeWindow: Window; + activeDocument: Document; + } +} + +declare module "obsidian" { + interface App { + commands: { + listCommands(): Command[]; + findCommand(id: string): Command; + removeCommand(id: string): void; + executeCommandById(id: string): void; + commands: Record; + }; + internalPlugins: { + plugins: InternalPlugins; + getPluginById(id: T): InternalPlugins[T]; + }; + plugins: { + manifests: Record; + plugins: Record & { + ["recent-files-obsidian"]: Plugin & { + shouldAddFile(file: TFile): boolean; + }; + }; + getPlugin(id: string): Plugin; + getPlugin(id: "calendar"): CalendarPlugin; + }; + dom: { appContainerEl: HTMLElement }; + viewRegistry: ViewRegistry; + + openWithDefaultApp(path: string): void; + } + + interface ViewRegistry { + typeByExtension: Record; // file extensions to view types + viewByType: Record View>; // file extensions to view types + } + + interface CalendarPlugin { + view: View; + } + + interface WorkspaceParent { + insertChild(index: number, child: WorkspaceItem, resize?: boolean): void; + + replaceChild(index: number, child: WorkspaceItem, resize?: boolean): void; + + removeChild(leaf: WorkspaceLeaf, resize?: boolean): void; + + containerEl: HTMLElement; + children: any; + } + + interface MarkdownEditView { + editorEl: HTMLElement; + } + + class MarkdownPreviewRendererStatic extends MarkdownPreviewRenderer { + static registerDomEvents( + el: HTMLElement, + handlerInstance: unknown, + cb: (el: HTMLElement) => unknown + ): void; + } + + interface WorkspaceLeaf { + openLinkText(linkText: string, path: string, state?: unknown): Promise; + + updateHeader(): void; + + containerEl: HTMLDivElement; + working: boolean; + parentSplit: WorkspaceParent; + activeTime: number; + } + + interface Workspace { + recordHistory(leaf: WorkspaceLeaf, pushHistory: boolean): void; + + iterateLeaves( + callback: (item: WorkspaceLeaf) => boolean | void, + item: WorkspaceItem | WorkspaceItem[] + ): boolean; + + iterateLeaves( + item: WorkspaceItem | WorkspaceItem[], + callback: (item: WorkspaceLeaf) => boolean | void + ): boolean; + + getDropLocation(event: MouseEvent): { + target: WorkspaceItem; + sidedock: boolean; + }; + + recursiveGetTarget(event: MouseEvent, parent: WorkspaceParent): WorkspaceItem; + + recordMostRecentOpenedFile(file: TFile): void; + + onDragLeaf(event: MouseEvent, leaf: WorkspaceLeaf): void; + + onLayoutChange(): void; // tell Obsidian leaves have been added/removed/etc. + activeLeafEvents(): void; + + floatingSplit: any; + + pushUndoHistory(leaf: WorkspaceLeaf, id: string, e: any): void; + } + + interface Editor { + getClickableTokenAt(pos: EditorPosition): { + text: string; + type: string; + start: EditorPosition; + end: EditorPosition; + }; + } + + interface WorkspaceItem { + side?: "left" | "right"; + } + + interface View { + iconEl: HTMLElement; + file: TFile; + + setMode(mode: MarkdownSubView): Promise; + + followLinkUnderCursor(newLeaf: boolean): void; + + modes: Record; + + getMode(): string; + + headerEl: HTMLElement; + contentEl: HTMLElement; + } + + interface SearchView extends View { + onKeyArrowRightInFocus(e: KeyboardEvent): void; + + onKeyArrowLeftInFocus(e: KeyboardEvent): void; + + onKeyArrowUpInFocus(e: KeyboardEvent): void; + + onKeyArrowDownInFocus(e: KeyboardEvent): void; + + onKeyEnterInFocus(e: KeyboardEvent): void; + + onKeyShowMoreBefore(e: KeyboardEvent): void; + + onKeyShowMoreAfter(e: KeyboardEvent): void; + + dom: any; + searchComponent: SearchComponent; + headerDom: any; + } + + interface EmptyView extends View { + actionListEl: HTMLElement; + emptyTitleEl: HTMLElement; + } + + interface FileManager { + createNewMarkdownFile(folder: TFolder, fileName: string): Promise; + } + + enum PopoverState { + Showing, + Shown, + Hiding, + Hidden, + } + + interface Menu { + items: MenuItem[]; + dom: HTMLElement; + hideCallback: () => unknown; + } + + interface MenuItem { + iconEl: HTMLElement; + dom: HTMLElement; + } + + interface EphemeralState { + focus?: boolean; + subpath?: string; + line?: number; + startLoc?: Loc; + endLoc?: Loc; + scroll?: number; + } + + interface HoverParent { + type?: string; + } + + interface WorkspaceContainer { + type: "window" | "split" | "tab"; + } + + interface HoverPopover { + parent: EmbeddedViewParent | null; + targetEl: HTMLElement; + hoverEl: HTMLElement; + + position(pos?: MousePos): void; + + hide(): void; + + show(): void; + + shouldShowSelf(): boolean; + + timer: number; + waitTime: number; + + shouldShow(): boolean; + + transition(): void; + } + + interface MousePos { + x: number; + y: number; + } + + interface Plugin { + registerGlobalCommand(command: Command): void; + } +} diff --git a/src/util/State.ts b/src/util/State.ts index f74e675..c5844fb 100644 --- a/src/util/State.ts +++ b/src/util/State.ts @@ -76,7 +76,8 @@ export class State { }, this as Record) as S; if (typeof subStateValue === "object") { // check if is like generic type S - if (subStateValue instanceof type) { + // @ts-ignore + if (subStateValue instanceof type || Boolean(subStateValue?.__getTarget)) { // @ts-ignore return new State(subStateValue.__getTarget); } else { diff --git a/src/views/graph/ForceGraph.ts b/src/views/graph/ForceGraph.ts index 735beeb..3f42b74 100644 --- a/src/views/graph/ForceGraph.ts +++ b/src/views/graph/ForceGraph.ts @@ -11,11 +11,15 @@ import { eventBus } from "@/util/EventBus"; import { GraphSettings } from "@/settings/GraphSettings"; import { DagOrientation } from "@/settings/categories/DisplaySettings"; import * as d3 from "d3"; +import * as THREE from "three"; const LINK_PARTICLE_MULTIPLIER = 2; const LINK_ARROW_WIDTH_MULTIPLIER = 5; const PARTICLE_FREQUECY = 4; +const FOCAL_FROM_CAMERA = 400; +const DISTANCE_FROM_FOCAL = 300; + // Adapted from https://github.com/vasturiano/3d-force-graph/blob/master/example/highlight/index.html // D3.js 3D Force Graph @@ -36,13 +40,15 @@ export class ForceGraph { private readonly isLocalGraph: boolean; private graph: Graph; private readonly plugin: Graph3dPlugin; + private myCube: THREE.Mesh; + private nodeLabelEl: HTMLDivElement; constructor(plugin: Graph3dPlugin, rootHtmlElement: HTMLElement, isLocalGraph: boolean) { this.rootHtmlElement = rootHtmlElement; this.isLocalGraph = isLocalGraph; this.plugin = plugin; - console.log("ForceGraph constructor", rootHtmlElement); + // console.log("ForceGraph constructor", rootHtmlElement); this.createGraph(); this.initListeners(); @@ -67,15 +73,68 @@ export class ForceGraph { } private createGraph() { + const xDir = new THREE.Vector3(1, 0, 0); + const yDir = new THREE.Vector3(0, 1, 0); + const zDir = new THREE.Vector3(0, 0, 1); + + const myCube = new THREE.Mesh( + new THREE.BoxGeometry(30, 30, 30), + new THREE.MeshBasicMaterial({ color: 0xff0000 }) + ); + this.myCube = myCube; + myCube.position.set(0, 0, -300); + myCube.visible = false; + + xDir.normalize(); + yDir.normalize(); + zDir.normalize(); + + const origin = new THREE.Vector3(0, 0, 0); + const length = 100; + + const xArrow = new THREE.ArrowHelper(xDir, origin, length, 0xff0000); + const yArrow = new THREE.ArrowHelper(yDir, origin, length, 0x00ff00); + const zArrow = new THREE.ArrowHelper(zDir, origin, length, 0x0000ff); + this.createInstance(); this.createNodes(); this.createLinks(); - // this.updateForce(settings.display.dagOrientation, settings.display.nodeRepulsion); + this.instance.scene().add(xArrow).add(yArrow).add(zArrow).add(myCube); + const camera = this.instance.camera() as THREE.PerspectiveCamera; + const renderer = this.instance.renderer(); + function onZoom(event: WheelEvent) { + // const delta = event.deltaY; + // const zoomSpeed = 0.1; + const distanceToCenter = camera.position.distanceTo(origin); + // console.log(camera.position, distanceToCenter); + camera.updateProjectionMatrix(); + xArrow.setLength(distanceToCenter / 10); + yArrow.setLength(distanceToCenter / 10); + zArrow.setLength(distanceToCenter / 10); + } + + renderer.domElement.addEventListener("wheel", onZoom); + + this.instance.scene().onBeforeRender = (renderer, scene, camera, geometry, material, group) => { + const cwd = new THREE.Vector3(); + camera.getWorldDirection(cwd); + cwd.multiplyScalar(FOCAL_FROM_CAMERA); + cwd.add(camera.position); + myCube.position.set(cwd.x, cwd.y, cwd.z); + myCube.setRotationFromQuaternion(camera.quaternion); + }; } private createInstance() { const [width, height] = [this.rootHtmlElement.innerWidth, this.rootHtmlElement.innerHeight]; const divEl = document.createElement("div"); + const nodeLabelEl = divEl.createDiv({ + cls: "node-label", + text: "test", + }); + nodeLabelEl.style.opacity = "0"; + this.nodeLabelEl = nodeLabelEl; + // set the divEl to have z-index 0 divEl.style.zIndex = "0"; const settings = this.plugin.getSettings(); @@ -88,9 +147,7 @@ export class ForceGraph { ], })(this.rootHtmlElement) .graphData(this.getGraphData()) - // .nodeLabel( - // (node: Node) => `
${node.name}
` - // ) + // .nodeLabel((node: Node) => `
${node.name}
`) // @ts-ignore we need to return null or empty string because by default it will access the name of node, see https://github.com/vasturiano/3d-force-graph#node-styling .nodeLabel((node: Node) => null) .nodeRelSize(settings.display.nodeSize) @@ -106,28 +163,35 @@ export class ForceGraph { // .d3Force("y", d3.forceY().strength(0.1)); } - private createNodes = () => { + private getNodeLabelText = (node: Node) => { const settings = this.plugin.getSettings(); + const fullPath = node.path; + const fileNameWithExtension = node.name; + const fullPathWithoutExtension = fullPath.substring(0, fullPath.lastIndexOf(".")); + const fileNameWithoutExtension = fileNameWithExtension.substring( + 0, + fileNameWithExtension.lastIndexOf(".") + ); + const text = !settings.display.showExtension + ? settings.display.showFullPath + ? fullPathWithoutExtension + : fileNameWithoutExtension + : settings.display.showFullPath + ? fullPath + : fileNameWithExtension; + return text; + }; + + private createNodes = () => { this.instance .nodeColor((node: Node) => this.getNodeColor(node)) // .nodeVisibility(this.doShowNode) .onNodeHover(this.onNodeHover) .nodeThreeObject((node: Node) => { const nodeEl = document.createElement("div"); - const fullPath = node.path; - const fileNameWithExtension = node.name; - const fullPathWithoutExtension = fullPath.substring(0, fullPath.lastIndexOf(".")); - const fileNameWithoutExtension = fileNameWithExtension.substring( - 0, - fileNameWithExtension.lastIndexOf(".") - ); - nodeEl.textContent = !settings.display.showExtension - ? settings.display.showFullPath - ? fullPathWithoutExtension - : fileNameWithoutExtension - : settings.display.showFullPath - ? fullPath - : fileNameWithExtension; + + const text = this.getNodeLabelText(node); + nodeEl.textContent = text; // @ts-ignore nodeEl.style.color = node.color; nodeEl.className = "node-label"; @@ -137,7 +201,34 @@ export class ForceGraph { nodeEl.style.borderRadius = "4px"; nodeEl.style.backgroundColor = rgba(0, 0, 0, 0.5); nodeEl.style.userSelect = "none"; - return new CSS2DObject(nodeEl); + + // const sprite = new SpriteText(text); + // sprite.color = "#8898aa"; + // sprite.textHeight = 12; + // sprite.position.y = 8; + // sprite.material.depthWrite = false; + // sprite.visible + + // return sprite; + + const cssObject = new CSS2DObject(nodeEl); + cssObject.onAfterRender = (renderer, scene, camera) => { + // get the position of the node + // @ts-ignore + const obj = node.__threeObj as THREE.Object3D | undefined; + const nodePosition = obj?.position as THREE.Vector3; + // then get the distance between the node and this.myCube , console.log it + const distance = nodePosition.distanceTo(this.myCube.position); + // change the opacity of the nodeEl base on the distance + // the higher the distance, the lower the opacity + // when the distance is 300, the opacity is 0 + // console.log(distance); + const normalizedDistance = Math.min(distance, DISTANCE_FROM_FOCAL) / DISTANCE_FROM_FOCAL; + const easedValue = 0.5 - 0.5 * Math.cos(normalizedDistance * Math.PI); + nodeEl.style.opacity = `${1 - easedValue}`; + }; + + return cssObject; }) .nodeThreeObjectExtend(true); }; @@ -150,7 +241,6 @@ export class ForceGraph { ? settings.display.linkThickness * 1.5 : settings.display.linkThickness ) - // .linkVisibility(this.doShowLink) .linkDirectionalParticles((link: Link) => this.isHighlightedLink(link) ? PARTICLE_FREQUECY : 0 ) @@ -205,9 +295,9 @@ export class ForceGraph { const settings = this.plugin.getSettings(); if (this.isLocalGraph && this.plugin.openFileState.value) { this.graph = this.plugin.globalGraph.clone().getLocalGraph(this.plugin.openFileState.value); - console.log(this.graph); + // console.log(this.graph); } else { - console.log(settings.filters.searchResult); + // console.log(settings.filters.searchResult); this.graph = this.plugin.globalGraph.filter((node) => { return ( (settings.filters.searchResult.length === 0 || @@ -224,7 +314,7 @@ export class ForceGraph { }; private refreshGraphData = () => { - console.log("refresh graph data"); + // console.log("refresh graph data"); this.instance.graphData(this.getGraphData()); }; @@ -288,6 +378,17 @@ export class ForceGraph { private onNodeHover = (node: Node | null) => { if ((!node && !this.highlightedNodes.size) || (node && this.hoveredNode === node)) return; + // set node label text + if (node) { + const text = this.getNodeLabelText(node); + this.nodeLabelEl.textContent = text; + // @ts-ignore + this.nodeLabelEl.style.color = node.color; + this.nodeLabelEl.style.opacity = "1"; + } else { + this.nodeLabelEl.style.opacity = "0"; + } + this.clearHighlights(); if (node) { diff --git a/src/views/graph/Graph3dView.ts b/src/views/graph/Graph3dView.ts index cdd6c8c..92593b0 100644 --- a/src/views/graph/Graph3dView.ts +++ b/src/views/graph/Graph3dView.ts @@ -29,7 +29,7 @@ export class Graph3dView extends ItemView { const settings = new GraphSettingsView( this.plugin.settingsState, this.plugin.theme, - this.plugin.app + this.plugin ); viewContent.appendChild(settings); } else { diff --git a/src/views/leafView.ts b/src/views/leafView.ts new file mode 100644 index 0000000..2550f63 --- /dev/null +++ b/src/views/leafView.ts @@ -0,0 +1,598 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// Original code from https://github.com/nothingislost/obsidian-hover-editor/blob/9ec3449be9ab3433dc46c4c3acfde1da72ff0261/src/popover.ts +// You can use this file as a basic leaf view create method in anywhere +// Please rememeber if you want to use this file, you should patch the types-obsidian.d.ts file +// And also monkey around the Obsidian original method. +import { + Component, + EphemeralState, + HoverPopover, + MarkdownEditView, + parseLinktext, + Plugin, + PopoverState, + requireApiVersion, + resolveSubpath, + TFile, + View, + Workspace, + WorkspaceLeaf, + WorkspaceSplit, + WorkspaceTabs, +} from "obsidian"; +import type { OpenViewState } from "@/typings/types-obsidian"; + +const PREFIX = "graph-3d"; + +export interface EmbeddedViewParent { + hoverPopover: EmbeddedView | null; + containerEl?: HTMLElement; + view?: View; + dom?: HTMLElement; +} +const popovers = new WeakMap(); +type ConstructableWorkspaceSplit = new ( + ws: Workspace, + dir: "horizontal" | "vertical" +) => WorkspaceSplit; + +export function isEmebeddedLeaf(leaf: WorkspaceLeaf) { + // Work around missing enhance.js API by checking match condition instead of looking up parent + return leaf.containerEl.matches(`.${PREFIX}-block.${PREFIX}-leaf-view .workspace-leaf`); +} + +function genId(size: number): string { + const chars = []; + for (let n = 0; n < size; n++) chars.push(((16 * Math.random()) | 0).toString(16)); + return chars.join(""); +} + +function nosuper(base: new (...args: unknown[]) => T): new () => T { + const derived = function () { + return Object.setPrototypeOf(new Component(), new.target.prototype); + }; + derived.prototype = base.prototype; + return Object.setPrototypeOf(derived, base); +} + +export const spawnLeafView = ( + plugin: Plugin, + initiatingEl?: HTMLElement, + leaf?: WorkspaceLeaf, + onShowCallback?: () => unknown, + props?: { + hoverElClasses?: string[]; + containerElClasses?: string[]; + } +): [WorkspaceLeaf, EmbeddedView] => { + // When Obsidian doesn't set any leaf active, use leaf instead. + let parent = app.workspace.activeLeaf as unknown as EmbeddedViewParent; + if (!parent) parent = leaf as unknown as EmbeddedViewParent; + + if (!initiatingEl) initiatingEl = parent?.containerEl; + + const hoverPopover = new EmbeddedView( + parent, + initiatingEl!, + plugin, + undefined, + onShowCallback, + props + ); + return [hoverPopover.attachLeaf(), hoverPopover]; +}; + +export class EmbeddedView extends nosuper(HoverPopover) { + onTarget: boolean; + setActive: (event: MouseEvent) => void; + + lockedOut: boolean; + abortController? = this.addChild(new Component()); + detaching = false; + opening = false; + + rootSplit: WorkspaceSplit = new (WorkspaceSplit as ConstructableWorkspaceSplit)( + window.app.workspace, + "vertical" + ); + + isPinned = true; + + titleEl: HTMLElement; + containerEl: HTMLElement; + + // It is currently not useful. + // leafInHoverEl: WorkspaceLeaf; + + oldPopover = this.parent?.hoverPopover; + document: Document = this.targetEl?.ownerDocument ?? window.activeDocument ?? window.document; + + id = genId(8); + // @ts-ignore + bounce?: NodeJS.Timeout; + boundOnZoomOut: () => void; + + originalPath: string; // these are kept to avoid adopting targets w/a different link + originalLinkText: string; + static activePopover?: EmbeddedView; + + static activeWindows() { + const windows: Window[] = [window]; + const { floatingSplit } = app.workspace; + if (floatingSplit) { + for (const split of floatingSplit.children) { + if (split.win) windows.push(split.win); + } + } + return windows; + } + + static containerForDocument(doc: Document) { + if (doc !== document && app.workspace.floatingSplit) + for (const container of app.workspace.floatingSplit.children) { + if (container.doc === doc) return container; + } + return app.workspace.rootSplit; + } + + static activePopovers() { + return this.activeWindows().flatMap(this.popoversForWindow); + } + + static popoversForWindow(win?: Window) { + return ( + Array.prototype.slice.call( + win?.document?.body.querySelectorAll(`.${PREFIX}-leaf-view`) ?? [] + ) as HTMLElement[] + ) + .map((el) => popovers.get(el)!) + .filter((he) => he); + } + + static forLeaf(leaf: WorkspaceLeaf | undefined) { + // leaf can be null such as when right clicking on an internal link + const el = leaf && document.body.matchParent.call(leaf.containerEl, `.${PREFIX}-leaf-view`); // work around matchParent race condition + return el ? popovers.get(el) : undefined; + } + + static iteratePopoverLeaves(ws: Workspace, cb: (leaf: WorkspaceLeaf) => boolean | void) { + for (const popover of this.activePopovers()) { + if (popover.rootSplit && ws.iterateLeaves(cb, popover.rootSplit)) return true; + } + return false; + } + + hoverEl: HTMLElement = this.document.defaultView!.createDiv({ + cls: `${PREFIX}-block ${PREFIX}-leaf-view`, + attr: { id: `${PREFIX}-` + this.id }, + }); + + constructor( + parent: EmbeddedViewParent, + public targetEl: HTMLElement, + public plugin: Plugin, + waitTime?: number, + public onShowCallback?: () => unknown, + props?: { + hoverElClasses?: string[]; + containerElClasses?: string[]; + } + ) { + // + super(); + + if (waitTime === undefined) { + waitTime = 300; + } + this.onTarget = true; + + this.parent = parent; + this.waitTime = waitTime; + this.state = PopoverState.Showing; + + this.abortController!.load(); + this.show(); + this.onShow(); + + this.setActive = this._setActive.bind(this); + // if (hoverEl) { + // hoverEl.addEventListener("mousedown", this.setActive); + // } + // custom logic begin + popovers.set(this.hoverEl, this); + if (props?.hoverElClasses) { + this.hoverEl.addClass(...props.hoverElClasses); + } + this.containerEl = this.hoverEl.createDiv({ cls: props?.containerElClasses }); + this.buildWindowControls(); + this.setInitialDimensions(); + } + + _setActive(evt: MouseEvent) { + evt.preventDefault(); + evt.stopPropagation(); + // @ts-ignore + this.plugin.app.workspace.setActiveLeaf(this.leaves()[0], { + focus: true, + }); + } + + getDefaultMode() { + // return this.parent?.view?.getMode ? this.parent.view.getMode() : "source"; + return "source"; + } + + updateLeaves() { + if (this.onTarget && this.targetEl && !this.document.contains(this.targetEl)) { + this.onTarget = false; + this.transition(); + } + let leafCount = 0; + this.plugin.app.workspace.iterateLeaves((leaf) => { + leafCount++; + }, this.rootSplit); + + if (leafCount === 0) { + this.hide(); // close if we have no leaves + } + this.hoverEl.setAttribute("data-leaf-count", leafCount.toString()); + } + + leaves() { + const leaves: WorkspaceLeaf[] = []; + this.plugin.app.workspace.iterateLeaves((leaf) => { + leaves.push(leaf); + }, this.rootSplit); + return leaves; + } + + setInitialDimensions() { + this.hoverEl.style.height = "auto"; + this.hoverEl.style.width = "100%"; + } + + transition() { + if (this.shouldShow()) { + if (this.state === PopoverState.Hiding) { + this.state = PopoverState.Shown; + clearTimeout(this.timer); + } + } else { + if (this.state === PopoverState.Showing) { + this.hide(); + } else { + if (this.state === PopoverState.Shown) { + this.state = PopoverState.Hiding; + this.timer = window.setTimeout(() => { + if (this.shouldShow()) { + this.transition(); + } else { + this.hide(); + } + }, this.waitTime); + } + } + } + } + + buildWindowControls() { + this.titleEl = this.document.defaultView!.createDiv("popover-titlebar"); + this.titleEl.createDiv("popover-title"); + + this.containerEl.prepend(this.titleEl); + } + + attachLeaf(): WorkspaceLeaf { + this.rootSplit.getRoot = () => + this.plugin.app.workspace[this.document === document ? "rootSplit" : "floatingSplit"]!; + this.rootSplit.getContainer = () => EmbeddedView.containerForDocument(this.document); + + this.titleEl.insertAdjacentElement("afterend", this.rootSplit.containerEl); + const leaf = this.plugin.app.workspace.createLeafInParent(this.rootSplit, 0); + + this.updateLeaves(); + return leaf; + } + + onload(): void { + super.onload(); + this.registerEvent(this.plugin.app.workspace.on("layout-change", this.updateLeaves, this)); + this.registerEvent( + app.workspace.on("layout-change", () => { + // Ensure that top-level items in a popover are not tabbed + // @ts-ignore + this.rootSplit.children.forEach((item: any, index: any) => { + if (item instanceof WorkspaceTabs) { + this.rootSplit.replaceChild(index, item.children[0]); + } + }); + }) + ); + } + + onShow() { + // Once we've been open for closeDelay, use the closeDelay as a hiding timeout + const closeDelay = 600; + setTimeout(() => (this.waitTime = closeDelay), closeDelay); + + this.oldPopover?.hide(); + this.oldPopover = null; + + this.hoverEl.toggleClass("is-new", true); + + this.document.body.addEventListener( + "click", + () => { + this.hoverEl.toggleClass("is-new", false); + }, + { once: true, capture: true } + ); + + if (this.parent) { + this.parent.hoverPopover = this; + } + + // Remove original view header; + const viewHeaderEl = this.hoverEl.querySelector(".view-header"); + viewHeaderEl?.remove(); + + const sizer = this.hoverEl.querySelector(".workspace-leaf"); + if (sizer) this.hoverEl.appendChild(sizer); + + // Remove original inline tilte; + const inlineTitle = this.hoverEl.querySelector(".inline-title"); + if (inlineTitle) inlineTitle.remove(); + + this.onShowCallback?.(); + this.onShowCallback = undefined; // only call it once + } + + detect(el: HTMLElement) { + // TODO: may not be needed? the mouseover/out handers handle most detection use cases + const { targetEl } = this; + + if (targetEl) { + this.onTarget = el === targetEl || targetEl.contains(el); + } + } + + shouldShow() { + return this.shouldShowSelf() || this.shouldShowChild(); + } + + shouldShowChild(): boolean { + return EmbeddedView.activePopovers().some((popover) => { + if (popover !== this && popover.targetEl && this.hoverEl.contains(popover.targetEl)) { + return popover.shouldShow(); + } + return false; + }); + } + + shouldShowSelf() { + // Don't let obsidian show() us if we've already started closing + // return !this.detaching && (this.onTarget || this.onHover); + return ( + !this.detaching && + !!( + this.onTarget || + this.state == PopoverState.Shown || + this.document.querySelector( + `body>.modal-container, body > #he${this.id} ~ .menu, body > #he${this.id} ~ .suggestion-container` + ) + ) + ); + } + + show() { + // native obsidian logic start + // if (!this.targetEl || this.document.body.contains(this.targetEl)) { + this.state = PopoverState.Shown; + this.timer = 0; + + this.targetEl.appendChild(this.hoverEl); + this.onShow(); + app.workspace.onLayoutChange(); + + // initializingHoverPopovers.remove(this); + // activeHoverPopovers.push(this); + // initializePopoverChecker(); + this.load(); + // } + // native obsidian logic end + + // if this is an image view, set the dimensions to the natural dimensions of the image + // an interactjs reflow will be triggered to constrain the image to the viewport if it's + // too large + if (this.hoverEl.dataset.imgHeight && this.hoverEl.dataset.imgWidth) { + this.hoverEl.style.height = + parseFloat(this.hoverEl.dataset.imgHeight) + this.titleEl.offsetHeight + "px"; + this.hoverEl.style.width = parseFloat(this.hoverEl.dataset.imgWidth) + "px"; + } + } + + onHide() { + this.oldPopover = null; + if (this.parent?.hoverPopover === this) { + this.parent.hoverPopover = null; + } + } + + hide() { + this.onTarget = false; + this.detaching = true; + // Once we reach this point, we're committed to closing + + // in case we didn't ever call show() + + // A timer might be pending to call show() for the first time, make sure + // it doesn't bring us back up after we close + if (this.timer) { + clearTimeout(this.timer); + this.timer = 0; + } + + // Hide our HTML element immediately, even if our leaves might not be + // detachable yet. This makes things more responsive and improves the + // odds of not showing an empty popup that's just going to disappear + // momentarily. + this.hoverEl.hide(); + + // If a file load is in progress, we need to wait until it's finished before + // detaching leaves. Because we set .detaching, The in-progress openFile() + // will call us again when it finishes. + if (this.opening) return; + + // Leave this code here to observe the state of the leaves + const leaves = this.leaves(); + if (leaves.length) { + // Detach all leaves before we unload the popover and remove it from the DOM. + // Each leaf.detach() will trigger layout-changed and the updateLeaves() + // method will then call hide() again when the last one is gone. + // leaves[0].detach(); + // leaves[0].detach(); + this.targetEl.empty(); + } else { + this.parent = null; + this.abortController?.unload(); + this.abortController = undefined; + return this.nativeHide(); + } + } + + nativeHide() { + const { hoverEl, targetEl } = this; + this.state = PopoverState.Hidden; + hoverEl.detach(); + + if (targetEl) { + const parent = targetEl.matchParent(`.${PREFIX}-leaf-view`); + if (parent) popovers.get(parent)?.transition(); + } + + this.onHide(); + this.unload(); + } + + resolveLink(linkText: string, sourcePath: string): TFile | null { + const link = parseLinktext(linkText); + const tFile = link + ? this.plugin.app.metadataCache.getFirstLinkpathDest(link.path, sourcePath) + : null; + return tFile; + } + + async openLink( + linkText: string, + sourcePath: string, + eState?: EphemeralState, + createInLeaf?: WorkspaceLeaf + ) { + let file = this.resolveLink(linkText, sourcePath); + const link = parseLinktext(linkText); + if (!file && createInLeaf) { + const folder = this.plugin.app.fileManager.getNewFileParent(sourcePath); + file = await this.plugin.app.fileManager.createNewMarkdownFile(folder, link.path); + } + + if (!file) { + // this.displayCreateFileAction(linkText, sourcePath, eState); + return; + } + const { viewRegistry } = this.plugin.app; + const viewType = viewRegistry.typeByExtension[file.extension]; + if (!viewType || !viewRegistry.viewByType[viewType]) { + // this.displayOpenFileAction(file); + return; + } + + eState = Object.assign(this.buildEphemeralState(file, link), eState); + const parentMode = this.getDefaultMode(); + const state = this.buildState(parentMode, eState); + const leaf = await this.openFile(file, state, createInLeaf); + const leafViewType = leaf?.view?.getViewType(); + // console.log(leaf); + if (leafViewType === "image") { + // TODO: temporary workaround to prevent image popover from disappearing immediately when using live preview + if ( + this.parent?.hasOwnProperty("editorEl") && + (this.parent as unknown as MarkdownEditView).editorEl!.hasClass("is-live-preview") + ) { + this.waitTime = 3000; + } + const img = leaf!.view.contentEl.querySelector("img")!; + this.hoverEl.dataset.imgHeight = String(img.naturalHeight); + this.hoverEl.dataset.imgWidth = String(img.naturalWidth); + this.hoverEl.dataset.imgRatio = String(img.naturalWidth / img.naturalHeight); + } else if (leafViewType === "pdf") { + this.hoverEl.style.height = "800px"; + this.hoverEl.style.width = "600px"; + } + if (state.state?.mode === "source") { + this.whenShown(() => { + // Not sure why this is needed, but without it we get issue #186 + if (requireApiVersion("1.0")) (leaf?.view as any)?.editMode?.reinit?.(); + leaf?.view?.setEphemeralState(state.eState); + }); + } + } + + whenShown(callback: () => any) { + // invoke callback once the popover is visible + if (this.detaching) return; + const existingCallback = this.onShowCallback; + this.onShowCallback = () => { + if (this.detaching) return; + callback(); + if (typeof existingCallback === "function") existingCallback(); + }; + if (this.state === PopoverState.Shown) { + this.onShowCallback(); + this.onShowCallback = undefined; + } + } + + async openFile(file: TFile, openState?: OpenViewState, useLeaf?: WorkspaceLeaf) { + if (this.detaching) return; + const leaf = useLeaf ?? this.attachLeaf(); + this.opening = true; + + try { + await leaf.openFile(file, openState); + } catch (e) { + console.error(e); + } finally { + this.opening = false; + if (this.detaching) this.hide(); + } + this.plugin.app.workspace.setActiveLeaf(leaf); + + return leaf; + } + + buildState(parentMode: string, eState?: EphemeralState) { + return { + active: false, // Don't let Obsidian force focus if we have autofocus off + state: { mode: "source" }, // Don't set any state for the view, because this leaf is stayed on another view. + eState: eState, + }; + } + + buildEphemeralState( + file: TFile, + link?: { + path: string; + subpath: string; + } + ) { + const cache = this.plugin.app.metadataCache.getFileCache(file); + const subpath = cache ? resolveSubpath(cache, link?.subpath || "") : undefined; + const eState: EphemeralState = { subpath: link?.subpath }; + if (subpath) { + eState.line = subpath.start.line; + eState.startLoc = subpath.start; + eState.endLoc = subpath.end || undefined; + } + return eState; + } +} diff --git a/src/views/settings/GraphSettingsView.ts b/src/views/settings/GraphSettingsView.ts index 2b9b05c..6ef6fbb 100644 --- a/src/views/settings/GraphSettingsView.ts +++ b/src/views/settings/GraphSettingsView.ts @@ -3,26 +3,28 @@ import { DisplaySettingsView } from "@/views/settings/categories/DisplaySettings import { FilterSettings } from "@/settings/categories/FilterSettings"; import { GroupSettings } from "@/settings/categories/GroupSettings"; import { DisplaySettings } from "@/settings/categories/DisplaySettings"; -import { App, ExtraButtonComponent } from "obsidian"; +import { ExtraButtonComponent, WorkspaceLeaf } from "obsidian"; import { State, StateChange } from "@/util/State"; import { GroupSettingsView } from "@/views/settings/categories/GroupSettingsView"; import { FilterSettingsView } from "@/views/settings/categories/FilterSettingsView"; import { GraphSettings } from "@/settings/GraphSettings"; import { ObsidianTheme } from "@/util/ObsidianTheme"; import { eventBus } from "@/util/EventBus"; +import Graph3dPlugin from "@/main"; export class GraphSettingsView extends HTMLDivElement { - private app: App; private settingsButton: ExtraButtonComponent; private graphControls: HTMLDivElement; private readonly settingsState: State; private readonly theme: ObsidianTheme; + private readonly plugin: Graph3dPlugin; + searchLeaf: WorkspaceLeaf; - constructor(settingsState: State, theme: ObsidianTheme, app: App) { + constructor(settingsState: State, theme: ObsidianTheme, plugin: Graph3dPlugin) { super(); this.settingsState = settingsState; this.theme = theme; - this.app = app; + this.plugin = plugin; } private isCollapsedState = new State(true); @@ -40,23 +42,25 @@ export class GraphSettingsView extends HTMLDivElement { this.graphControls = this.createDiv({ cls: "graph-controls" }); this.appendGraphControlsItems(this.graphControls.createDiv({ cls: "control-buttons" })); - this.appendSetting( + this.appendSettingGroup( this.settingsState.createSubState("value.filters", FilterSettings), "Filters", - (...args) => FilterSettingsView(...args, this.app) + (...args) => FilterSettingsView(...args, this.plugin) ); - this.appendSetting( + this.appendSettingGroup( this.settingsState.createSubState("value.groups", GroupSettings), "Groups", (...args) => GroupSettingsView(...args, this.theme) ); - this.appendSetting( + this.appendSettingGroup( this.settingsState.createSubState("value.display", DisplaySettings), "Display", DisplaySettingsView ); this.initListeners(); this.toggleCollapsed(this.isCollapsedState.value); + + // init search view } private initListeners() { @@ -111,14 +115,18 @@ export class GraphSettingsView extends HTMLDivElement { } private appendMinimizeButton(containerEl: HTMLElement) { + // on close new ExtraButtonComponent(containerEl) .setIcon("x") .setTooltip("Close") - .onClick(() => (this.isCollapsedState.value = true)); + .onClick(() => { + this.isCollapsedState.value = true; + console.log("graph setting is closed"); + }); } // utility function to append a setting - private appendSetting( + private appendSettingGroup( setting: S, title: string, view: (setting: S, containerEl: HTMLElement) => void diff --git a/src/views/settings/categories/AddNodeGroupButton.ts b/src/views/settings/categories/AddNodeGroupButton.ts index 8421f27..84db330 100644 --- a/src/views/settings/categories/AddNodeGroupButton.ts +++ b/src/views/settings/categories/AddNodeGroupButton.ts @@ -5,6 +5,10 @@ import { ObsidianTheme } from "@/util/ObsidianTheme"; import { State } from "@/util/State"; import { GroupSettingsView } from "@/views/settings/categories/GroupSettingsView"; +const getRandomColor = () => { + return "#" + Math.floor(Math.random() * 16777215).toString(16); +}; + export const addNodeGroupButton = ( groupSettings: State, containerEl: HTMLElement, @@ -20,7 +24,7 @@ export const addNodeGroupButton = ( .setClass("mod-cta") .setButtonText("Add Group") .onClick(() => { - groupSettings.value.groups.push(new NodeGroup("", theme.textMuted)); + groupSettings.value.groups.push(new NodeGroup("", getRandomColor())); containerEl.empty(); GroupSettingsView(groupSettings, containerEl, theme); }); diff --git a/src/views/settings/categories/FilterSettingsView.ts b/src/views/settings/categories/FilterSettingsView.ts index 823cf31..2748c26 100644 --- a/src/views/settings/categories/FilterSettingsView.ts +++ b/src/views/settings/categories/FilterSettingsView.ts @@ -1,69 +1,68 @@ -import { App, Notice, Setting, Plugin } from "obsidian"; +import { SearchView, Setting, TFile } from "obsidian"; import { FilterSettings } from "@/settings/categories/FilterSettings"; import { State } from "@/util/State"; +import { spawnLeafView } from "@/views/leafView"; +import Graph3dPlugin from "@/main"; -interface GlobalSearchPlugin extends Plugin { - instance: { - getGlobalSearchQuery: () => string; - openGlobalSearch: (query: string) => void; - }; -} +const DomLookUpTime = 300; -export const FilterSettingsView = ( +export const FilterSettingsView = async ( filterSettings: State, containerEl: HTMLElement, - app: App + plugin: Graph3dPlugin ) => { - const instance = (app.internalPlugins.plugins["global-search"] as GlobalSearchPlugin).instance; - // add search setting - new Setting(containerEl) - .setClass("mod-search-setting") - .addSearch((search) => { - // initialize search with current search query - search.inputEl.value = filterSettings.value.searchQuery; - filterSettings.onChange((change) => { - if (change.currentPath === "searchQuery") { - search.inputEl.value = change.newValue as string; - } - }); - search.setPlaceholder("Read only"); - search.inputEl.onclick = (e) => { - new Notice("This search is delegated to the global search plugin"); - const currentSearch = instance.getGlobalSearchQuery(); - instance.openGlobalSearch(currentSearch); - }; - search.clearButtonEl.onclick = (e) => { - filterSettings.value.searchQuery = ""; - filterSettings.value.searchResult = []; - }; - search.inputEl.readOnly = true; - return search; - }) - .addButton((button) => { - button.setButtonText("Search").onClick(async (e) => { - const currentSearch = instance.getGlobalSearchQuery(); - instance.openGlobalSearch(currentSearch); - const searchLeaf = app.workspace.getLeavesOfType("search")[0]; - if (searchLeaf) { - filterSettings.value.searchQuery = instance.getGlobalSearchQuery(); - const search = await searchLeaf.open(searchLeaf.view); - const rawSearchResult = await new Promise((resolve) => - setTimeout(() => { - // @ts-ignore - resolve(search.dom.resultDomLookup); - }, 300) - ); - // @ts-ignore - const files = Array.from(rawSearchResult.keys()); - console.log(files); - // @ts-ignore - filterSettings.value.searchResult = files.map((f) => f.path as string); - console.log(filterSettings.value.searchResult); - } else { - new Notice("Please open your search leaf first"); - } - }); - }); + const searchEl = containerEl.createDiv({ + // cls : + }); + const [searchLeaf] = spawnLeafView(plugin, searchEl); + + await searchLeaf.setViewState({ + type: "search", + }); + + // add searchEl to containerEl + containerEl.appendChild(searchEl); + + const element = searchLeaf.containerEl.querySelector( + ".workspace-leaf-content[data-type='search']" + ) as HTMLDivElement; + // element.style.removeProperty("overflow"); + const searchRowEl = element.querySelector(".search-row") as HTMLDivElement; + searchRowEl.style.margin = "0px"; + console.log(element); + // move the element to the containerEl + containerEl.appendChild(searchRowEl); + const inputEl = searchRowEl.getElementsByTagName("input")[0]!; + const settingIconEl = searchRowEl.querySelector( + ".clickable-icon[aria-label='Search settings']" + ) as HTMLDivElement; + const matchCaseIconEl = searchRowEl.querySelector( + "div.search-input-container > div[aria-label='Match case'] " + ) as HTMLDivElement; + const clearButtonEl = searchRowEl.querySelector(".search-input-clear-button") as HTMLDivElement; + settingIconEl?.remove(); + matchCaseIconEl?.remove(); + console.log(settingIconEl); + inputEl.value = filterSettings.value.searchQuery; + inputEl.onkeydown = async (e) => { + const currentView = searchLeaf.view as SearchView; + const rawSearchResult = await new Promise((resolve) => + setTimeout(() => { + // @ts-ignore + resolve(currentView.dom.resultDomLookup); + }, DomLookUpTime) + ); + // @ts-ignore + const files = Array.from(rawSearchResult.keys()) as TFile[]; + + filterSettings.value.searchResult = files.map((f) => f.path); + filterSettings.value.searchQuery = inputEl.value; + }; + + clearButtonEl.onclick = async () => { + filterSettings.value.searchQuery = ""; + filterSettings.value.searchResult = []; + }; // add show attachments setting new Setting(containerEl).setName("Show Attachments").addToggle((toggle) => { diff --git a/src/views/settings/categories/GroupSettingItem.ts b/src/views/settings/categories/GroupSettingItem.ts index 1bbf7d3..0c3648e 100644 --- a/src/views/settings/categories/GroupSettingItem.ts +++ b/src/views/settings/categories/GroupSettingItem.ts @@ -18,6 +18,7 @@ export const AddNodeGroupItem = ( }); addColorPicker(groupEl, group.value.color, (value) => { + console.log("color changed", value); group.value.color = value; }); diff --git a/src/views/settings/categories/addNodeGroups.ts b/src/views/settings/categories/addNodeGroups.ts index 3b2bb37..b79ec60 100644 --- a/src/views/settings/categories/addNodeGroups.ts +++ b/src/views/settings/categories/addNodeGroups.ts @@ -9,6 +9,7 @@ export const addNodeGroups = (groupSettings: State, containerEl: cls: "graph-color-groups-container", }); groupSettings.value.groups.forEach((group, index) => { + console.log(groupSettings.value.groups[0]); const groupState = groupSettings.createSubState(`value.groups.${index}`, NodeGroup); AddNodeGroupItem(groupState, nodeGroupContainerEl, () => { groupSettings.value.groups.splice(index, 1);