mirror of
https://github.com/TriliumNext/Trilium.git
synced 2025-12-10 03:53:37 -06:00
chore(type_widgets): bring back relation map note creation
This commit is contained in:
parent
3d08f686cf
commit
2cd3e3f9c8
@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
import { TypeWidgetProps } from "./type_widget";
|
||||
import { Defaults, jsPlumb, jsPlumbInstance, OverlaySpec } from "jsplumb";
|
||||
import { useEditorSpacedUpdate, useNoteBlob } from "../react/hooks";
|
||||
import { useEditorSpacedUpdate, useNoteBlob, useTriliumEvent } from "../react/hooks";
|
||||
import FNote from "../../entities/fnote";
|
||||
import { ComponentChildren, RefObject } from "preact";
|
||||
import froca from "../../services/froca";
|
||||
@ -9,6 +9,10 @@ import NoteLink from "../react/NoteLink";
|
||||
import "./RelationMap.css";
|
||||
import { t } from "../../services/i18n";
|
||||
import panzoom, { PanZoomOptions } from "panzoom";
|
||||
import dialog from "../../services/dialog";
|
||||
import server from "../../services/server";
|
||||
import toast from "../../services/toast";
|
||||
import { CreateChildrenResponse } from "@triliumnext/commons";
|
||||
|
||||
interface MapData {
|
||||
notes: {
|
||||
@ -23,6 +27,11 @@ interface MapData {
|
||||
}
|
||||
}
|
||||
|
||||
interface Clipboard {
|
||||
noteId: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const uniDirectionalOverlays: OverlaySpec[] = [
|
||||
[
|
||||
"Arrow",
|
||||
@ -36,7 +45,7 @@ const uniDirectionalOverlays: OverlaySpec[] = [
|
||||
["Label", { label: "", id: "label", cssClass: "connection-label" }]
|
||||
];
|
||||
|
||||
export default function RelationMap({ note }: TypeWidgetProps) {
|
||||
export default function RelationMap({ note, ntxId }: TypeWidgetProps) {
|
||||
const [ data, setData ] = useState<MapData>();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const apiRef = useRef<jsPlumbInstance>(null);
|
||||
@ -83,12 +92,25 @@ export default function RelationMap({ note }: TypeWidgetProps) {
|
||||
spacedUpdate.scheduleUpdate();
|
||||
}, [ data ]);
|
||||
|
||||
const onNewItem = useCallback((newNote: MapData["notes"][number]) => {
|
||||
if (!data) return;
|
||||
data.notes.push(newNote);
|
||||
setData({ ...data });
|
||||
spacedUpdate.scheduleUpdate();
|
||||
}, [ data, spacedUpdate ]);
|
||||
const clickCallback = useNoteCreation({
|
||||
containerRef,
|
||||
note,
|
||||
ntxId,
|
||||
onCreate: onNewItem
|
||||
});
|
||||
|
||||
usePanZoom({
|
||||
containerRef,
|
||||
options: {
|
||||
maxZoom: 2,
|
||||
minZoom: 0.3,
|
||||
smoothScroll: false,
|
||||
smoothScroll: false,
|
||||
//@ts-expect-error Upstream incorrectly mentions no arguments.
|
||||
filterKey: function (e: KeyboardEvent) {
|
||||
// if ALT is pressed, then panzoom should bubble the event up
|
||||
@ -102,7 +124,7 @@ export default function RelationMap({ note }: TypeWidgetProps) {
|
||||
|
||||
return (
|
||||
<div className="note-detail-relation-map note-detail-printable">
|
||||
<div className="relation-map-wrapper">
|
||||
<div className="relation-map-wrapper" onClick={clickCallback}>
|
||||
<JsPlumb
|
||||
apiRef={apiRef}
|
||||
containerRef={containerRef}
|
||||
@ -152,6 +174,47 @@ function usePanZoom({ containerRef, options, transformData, onTransform }: {
|
||||
}, [ containerRef, onTransform ]);
|
||||
}
|
||||
|
||||
function useNoteCreation({ ntxId, note, containerRef, onCreate }: {
|
||||
ntxId: string | null | undefined;
|
||||
note: FNote;
|
||||
containerRef: RefObject<HTMLDivElement>;
|
||||
onCreate: (newNote: MapData["notes"][number]) => void;
|
||||
}) {
|
||||
const clipboardRef = useRef<Clipboard>(null);
|
||||
useTriliumEvent("relationMapCreateChildNote", async ({ ntxId: eventNtxId }) => {
|
||||
if (eventNtxId !== ntxId) return;
|
||||
const title = await dialog.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") });
|
||||
if (!title?.trim()) return;
|
||||
|
||||
const { note: createdNote } = await server.post<CreateChildrenResponse>(`notes/${note.noteId}/children?target=into`, {
|
||||
title,
|
||||
content: "",
|
||||
type: "text"
|
||||
});
|
||||
|
||||
toast.showMessage(t("relation_map.click_on_canvas_to_place_new_note"));
|
||||
clipboardRef.current = {
|
||||
noteId: createdNote.noteId,
|
||||
title
|
||||
};
|
||||
});
|
||||
const onClickHandler = useCallback((e: MouseEvent) => {
|
||||
const clipboard = clipboardRef.current;
|
||||
if (clipboard && containerRef.current) {
|
||||
const zoom = getZoom(containerRef.current);
|
||||
let { x, y } = getMousePosition(e, containerRef.current, zoom);
|
||||
|
||||
// modifying position so that the cursor is on the top-center of the box
|
||||
x -= 80;
|
||||
y -= 15;
|
||||
|
||||
onCreate({ noteId: clipboard.noteId, x, y });
|
||||
clipboardRef.current = null;
|
||||
}
|
||||
}, [ onCreate ]);
|
||||
return onClickHandler;
|
||||
}
|
||||
|
||||
function JsPlumb({ className, props, children, containerRef: externalContainerRef, apiRef }: {
|
||||
className?: string;
|
||||
props: Omit<Defaults, "container">;
|
||||
@ -229,3 +292,12 @@ function getZoom(container: HTMLDivElement) {
|
||||
|
||||
return parseFloat(matches[1]);
|
||||
}
|
||||
|
||||
function getMousePosition(evt: MouseEvent, container: HTMLDivElement, zoom: number) {
|
||||
const rect = container.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
x: ((evt.clientX ?? 0) - rect.left) / zoom,
|
||||
y: ((evt.clientY ?? 0) - rect.top) / zoom
|
||||
};
|
||||
}
|
||||
|
||||
@ -90,11 +90,6 @@ const linkOverlays = [
|
||||
|
||||
let containerCounter = 1;
|
||||
|
||||
interface Clipboard {
|
||||
noteId: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export type RelationType = "uniDirectional" | "biDirectional" | "inverse";
|
||||
|
||||
interface Relation {
|
||||
@ -106,13 +101,6 @@ interface Relation {
|
||||
render: boolean;
|
||||
}
|
||||
|
||||
// TODO: Deduplicate.
|
||||
interface PostNoteResponse {
|
||||
note: {
|
||||
noteId: string;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Deduplicate.
|
||||
interface RelationMapPostResponse {
|
||||
relations: Relation[];
|
||||
@ -149,21 +137,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
||||
|
||||
this.$relationMapWrapper = this.$widget.find(".relation-map-wrapper");
|
||||
this.$relationMapWrapper.on("click", (event) => {
|
||||
if (this.clipboard && this.mapData) {
|
||||
let { x, y } = this.getMousePosition(event);
|
||||
|
||||
// modifying position so that the cursor is on the top-center of the box
|
||||
x -= 80;
|
||||
y -= 15;
|
||||
|
||||
this.createNoteBox(this.clipboard.noteId, this.clipboard.title, x, y);
|
||||
|
||||
this.mapData.notes.push({ noteId: this.clipboard.noteId, x, y });
|
||||
|
||||
this.saveData();
|
||||
|
||||
this.clipboard = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
@ -249,12 +223,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
||||
|
||||
|
||||
async doRefresh(note: FNote) {
|
||||
await this.loadMapData();
|
||||
|
||||
await this.initJsPlumbInstance();
|
||||
|
||||
await this.initPanZoom();
|
||||
|
||||
this.loadNotesAndRelations();
|
||||
}
|
||||
|
||||
@ -558,39 +527,6 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
||||
this.loadNotesAndRelations();
|
||||
}
|
||||
|
||||
getMousePosition(evt: JQuery.ClickEvent | JQuery.DropEvent) {
|
||||
const rect = this.$relationMapContainer[0].getBoundingClientRect();
|
||||
|
||||
const zoom = this.getZoom();
|
||||
|
||||
return {
|
||||
x: ((evt.clientX ?? 0) - rect.left) / zoom,
|
||||
y: ((evt.clientY ?? 0) - rect.top) / zoom
|
||||
};
|
||||
}
|
||||
|
||||
async relationMapCreateChildNoteEvent({ ntxId }: EventData<"relationMapCreateChildNote">) {
|
||||
if (!this.isNoteContext(ntxId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = await dialogService.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") });
|
||||
|
||||
if (!title?.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { note } = await server.post<PostNoteResponse>(`notes/${this.noteId}/children?target=into`, {
|
||||
title,
|
||||
content: "",
|
||||
type: "text"
|
||||
});
|
||||
|
||||
toastService.showMessage(t("relation_map.click_on_canvas_to_place_new_note"));
|
||||
|
||||
this.clipboard = { noteId: note.noteId, title };
|
||||
}
|
||||
|
||||
relationMapResetPanZoomEvent({ ntxId }: EventData<"relationMapResetPanZoom">) {
|
||||
if (!this.isNoteContext(ntxId)) {
|
||||
return;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user