mirror of
https://github.com/TriliumNext/Trilium.git
synced 2025-12-11 19:44:00 -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 { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||||
import { TypeWidgetProps } from "./type_widget";
|
import { TypeWidgetProps } from "./type_widget";
|
||||||
import { Defaults, jsPlumb, jsPlumbInstance, OverlaySpec } from "jsplumb";
|
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 FNote from "../../entities/fnote";
|
||||||
import { ComponentChildren, RefObject } from "preact";
|
import { ComponentChildren, RefObject } from "preact";
|
||||||
import froca from "../../services/froca";
|
import froca from "../../services/froca";
|
||||||
@ -9,6 +9,10 @@ import NoteLink from "../react/NoteLink";
|
|||||||
import "./RelationMap.css";
|
import "./RelationMap.css";
|
||||||
import { t } from "../../services/i18n";
|
import { t } from "../../services/i18n";
|
||||||
import panzoom, { PanZoomOptions } from "panzoom";
|
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 {
|
interface MapData {
|
||||||
notes: {
|
notes: {
|
||||||
@ -23,6 +27,11 @@ interface MapData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Clipboard {
|
||||||
|
noteId: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
const uniDirectionalOverlays: OverlaySpec[] = [
|
const uniDirectionalOverlays: OverlaySpec[] = [
|
||||||
[
|
[
|
||||||
"Arrow",
|
"Arrow",
|
||||||
@ -36,7 +45,7 @@ const uniDirectionalOverlays: OverlaySpec[] = [
|
|||||||
["Label", { label: "", id: "label", cssClass: "connection-label" }]
|
["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 [ data, setData ] = useState<MapData>();
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const apiRef = useRef<jsPlumbInstance>(null);
|
const apiRef = useRef<jsPlumbInstance>(null);
|
||||||
@ -83,6 +92,19 @@ export default function RelationMap({ note }: TypeWidgetProps) {
|
|||||||
spacedUpdate.scheduleUpdate();
|
spacedUpdate.scheduleUpdate();
|
||||||
}, [ data ]);
|
}, [ 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({
|
usePanZoom({
|
||||||
containerRef,
|
containerRef,
|
||||||
options: {
|
options: {
|
||||||
@ -102,7 +124,7 @@ export default function RelationMap({ note }: TypeWidgetProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="note-detail-relation-map note-detail-printable">
|
<div className="note-detail-relation-map note-detail-printable">
|
||||||
<div className="relation-map-wrapper">
|
<div className="relation-map-wrapper" onClick={clickCallback}>
|
||||||
<JsPlumb
|
<JsPlumb
|
||||||
apiRef={apiRef}
|
apiRef={apiRef}
|
||||||
containerRef={containerRef}
|
containerRef={containerRef}
|
||||||
@ -152,6 +174,47 @@ function usePanZoom({ containerRef, options, transformData, onTransform }: {
|
|||||||
}, [ containerRef, 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 }: {
|
function JsPlumb({ className, props, children, containerRef: externalContainerRef, apiRef }: {
|
||||||
className?: string;
|
className?: string;
|
||||||
props: Omit<Defaults, "container">;
|
props: Omit<Defaults, "container">;
|
||||||
@ -229,3 +292,12 @@ function getZoom(container: HTMLDivElement) {
|
|||||||
|
|
||||||
return parseFloat(matches[1]);
|
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;
|
let containerCounter = 1;
|
||||||
|
|
||||||
interface Clipboard {
|
|
||||||
noteId: string;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RelationType = "uniDirectional" | "biDirectional" | "inverse";
|
export type RelationType = "uniDirectional" | "biDirectional" | "inverse";
|
||||||
|
|
||||||
interface Relation {
|
interface Relation {
|
||||||
@ -106,13 +101,6 @@ interface Relation {
|
|||||||
render: boolean;
|
render: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Deduplicate.
|
|
||||||
interface PostNoteResponse {
|
|
||||||
note: {
|
|
||||||
noteId: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Deduplicate.
|
// TODO: Deduplicate.
|
||||||
interface RelationMapPostResponse {
|
interface RelationMapPostResponse {
|
||||||
relations: Relation[];
|
relations: Relation[];
|
||||||
@ -149,21 +137,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
|||||||
|
|
||||||
this.$relationMapWrapper = this.$widget.find(".relation-map-wrapper");
|
this.$relationMapWrapper = this.$widget.find(".relation-map-wrapper");
|
||||||
this.$relationMapWrapper.on("click", (event) => {
|
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;
|
return true;
|
||||||
});
|
});
|
||||||
@ -249,12 +223,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
|||||||
|
|
||||||
|
|
||||||
async doRefresh(note: FNote) {
|
async doRefresh(note: FNote) {
|
||||||
await this.loadMapData();
|
|
||||||
|
|
||||||
await this.initJsPlumbInstance();
|
await this.initJsPlumbInstance();
|
||||||
|
|
||||||
await this.initPanZoom();
|
|
||||||
|
|
||||||
this.loadNotesAndRelations();
|
this.loadNotesAndRelations();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -558,39 +527,6 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
|||||||
this.loadNotesAndRelations();
|
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">) {
|
relationMapResetPanZoomEvent({ ntxId }: EventData<"relationMapResetPanZoom">) {
|
||||||
if (!this.isNoteContext(ntxId)) {
|
if (!this.isNoteContext(ntxId)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user