mirror of
https://github.com/zadam/trilium.git
synced 2025-11-10 23:35:50 +01:00
chore(react/type_widget): get mindmap to render
This commit is contained in:
@@ -17,6 +17,7 @@ import File from "./type_widgets/File";
|
|||||||
import Image from "./type_widgets/Image";
|
import Image from "./type_widgets/Image";
|
||||||
import { ReadOnlyCode, EditableCode } from "./type_widgets/code/Code";
|
import { ReadOnlyCode, EditableCode } from "./type_widgets/code/Code";
|
||||||
import Mermaid from "./type_widgets/Mermaid";
|
import Mermaid from "./type_widgets/Mermaid";
|
||||||
|
import MindMap from "./type_widgets/MindMap";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one,
|
* A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one,
|
||||||
@@ -96,6 +97,7 @@ function getCorrespondingWidget(noteType: ExtendedNoteType | undefined, props: T
|
|||||||
case "readOnlyCode": return <ReadOnlyCode {...props} />
|
case "readOnlyCode": return <ReadOnlyCode {...props} />
|
||||||
case "editableCode": return <EditableCode {...props} />
|
case "editableCode": return <EditableCode {...props} />
|
||||||
case "mermaid": return <Mermaid {...props} />
|
case "mermaid": return <Mermaid {...props} />
|
||||||
|
case "mindMap": return <MindMap {...props} />
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
124
apps/client/src/widgets/type_widgets/MindMap.css
Normal file
124
apps/client/src/widgets/type_widgets/MindMap.css
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
.note-detail-mind-map {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-detail-mind-map .mind-map-container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 80px;
|
||||||
|
overflow: auto;
|
||||||
|
background: var(--panel-bgcolor);
|
||||||
|
color: var(--main-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 1px 2px #0003;
|
||||||
|
width: 240px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 15px 15px;
|
||||||
|
transition: .3s all
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu.close {
|
||||||
|
height: 29px;
|
||||||
|
width: 46px;
|
||||||
|
overflow: hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .button-container {
|
||||||
|
padding: 3px 0;
|
||||||
|
direction: rtl
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu #nm-tag {
|
||||||
|
margin-top: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .nm-fontsize-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-bottom: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .nm-fontsize-container div {
|
||||||
|
height: 36px;
|
||||||
|
width: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 1px 2px #0003;
|
||||||
|
background-color: #fff;
|
||||||
|
color: tomato;
|
||||||
|
border-radius: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .nm-fontcolor-container {
|
||||||
|
margin-bottom: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu input,
|
||||||
|
.map-container .node-menu textarea {
|
||||||
|
background: var(--input-background-color);
|
||||||
|
border: 1px solid var(--panel-border-color);
|
||||||
|
border-radius: var(--bs-border-radius);
|
||||||
|
color: var(--main-color);
|
||||||
|
padding: 5px;
|
||||||
|
margin: 10px 0;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu textarea {
|
||||||
|
resize: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .split6 {
|
||||||
|
display: inline-block;
|
||||||
|
width: 16.66%;
|
||||||
|
margin-bottom: 5px
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .palette {
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 21px;
|
||||||
|
height: 21px;
|
||||||
|
border: 1px solid #edf1f2;
|
||||||
|
margin: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .nmenu-selected,
|
||||||
|
.map-container .node-menu .palette:hover {
|
||||||
|
box-shadow: tomato 0 0 0 2px;
|
||||||
|
background-color: #c7e9fa
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .size-selected {
|
||||||
|
background-color: tomato !important;
|
||||||
|
border-color: tomato;
|
||||||
|
fill: #fff;
|
||||||
|
color: #fff
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .size-selected svg {
|
||||||
|
color: #fff
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .bof {
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .bof span {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 5px
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container .node-menu .bof .selected {
|
||||||
|
background-color: tomato;
|
||||||
|
color: #fff
|
||||||
|
}
|
||||||
77
apps/client/src/widgets/type_widgets/MindMap.tsx
Normal file
77
apps/client/src/widgets/type_widgets/MindMap.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { useCallback, useEffect, useRef } from "preact/hooks";
|
||||||
|
import { TypeWidgetProps } from "./type_widget";
|
||||||
|
import { MindElixirData, default as VanillaMindElixir } from "mind-elixir";
|
||||||
|
import { HTMLAttributes } from "preact";
|
||||||
|
// allow node-menu plugin css to be bundled by webpack
|
||||||
|
import nodeMenu from "@mind-elixir/node-menu";
|
||||||
|
import "mind-elixir/style";
|
||||||
|
import "@mind-elixir/node-menu/dist/style.css";
|
||||||
|
import "./MindMap.css";
|
||||||
|
|
||||||
|
const NEW_TOPIC_NAME = "";
|
||||||
|
|
||||||
|
interface MindmapModel extends MindElixirData {
|
||||||
|
direction: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MindElixirProps {
|
||||||
|
direction: number;
|
||||||
|
containerProps?: Omit<HTMLAttributes<HTMLDivElement>, "ref">;
|
||||||
|
content: MindElixirData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MindMap({ }: TypeWidgetProps) {
|
||||||
|
const content = VanillaMindElixir.new(NEW_TOPIC_NAME);
|
||||||
|
|
||||||
|
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
|
/*
|
||||||
|
* Some global shortcuts interfere with the default shortcuts of the mind map,
|
||||||
|
* as defined here: https://mind-elixir.com/docs/guides/shortcuts
|
||||||
|
*/
|
||||||
|
if (e.key === "F1") {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zoom controls
|
||||||
|
const isCtrl = e.ctrlKey && !e.altKey && !e.metaKey;
|
||||||
|
if (isCtrl && (e.key == "-" || e.key == "=" || e.key == "0")) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="note-detail-mind-map note-detail-printable">
|
||||||
|
<MindElixir
|
||||||
|
content={content}
|
||||||
|
containerProps={{
|
||||||
|
className: "mind-map-container",
|
||||||
|
onKeyDown
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function MindElixir({ content, containerProps, direction }: MindElixirProps) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
|
const mind = new VanillaMindElixir({
|
||||||
|
el: containerRef.current,
|
||||||
|
direction
|
||||||
|
});
|
||||||
|
|
||||||
|
mind.install(nodeMenu);
|
||||||
|
mind.init(content);
|
||||||
|
|
||||||
|
return () => mind.destroy();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} {...containerProps}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,154 +1,9 @@
|
|||||||
import TypeWidget from "./type_widget.js";
|
import TypeWidget from "./type_widget.js";
|
||||||
import utils from "../../services/utils.js";
|
import utils from "../../services/utils.js";
|
||||||
import type { MindElixirInstance } from "mind-elixir";
|
import type { MindElixirInstance } from "mind-elixir";
|
||||||
import nodeMenu from "@mind-elixir/node-menu";
|
|
||||||
import type FNote from "../../entities/fnote.js";
|
import type FNote from "../../entities/fnote.js";
|
||||||
import type { EventData } from "../../components/app_context.js";
|
import type { EventData } from "../../components/app_context.js";
|
||||||
|
|
||||||
// allow node-menu plugin css to be bundled by webpack
|
|
||||||
import "mind-elixir/style";
|
|
||||||
import "@mind-elixir/node-menu/dist/style.css";
|
|
||||||
|
|
||||||
const NEW_TOPIC_NAME = "";
|
|
||||||
|
|
||||||
const TPL = /*html*/`
|
|
||||||
<div class="note-detail-mind-map note-detail-printable">
|
|
||||||
<div class="mind-map-container">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.note-detail-mind-map {
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-mind-map .mind-map-container {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: 60px;
|
|
||||||
right: 20px;
|
|
||||||
bottom: 80px;
|
|
||||||
overflow: auto;
|
|
||||||
background: var(--panel-bgcolor);
|
|
||||||
color: var(--main-color);
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 0 1px 2px #0003;
|
|
||||||
width: 240px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 0 15px 15px;
|
|
||||||
transition: .3s all
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu.close {
|
|
||||||
height: 29px;
|
|
||||||
width: 46px;
|
|
||||||
overflow: hidden
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .button-container {
|
|
||||||
padding: 3px 0;
|
|
||||||
direction: rtl
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu #nm-tag {
|
|
||||||
margin-top: 20px
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .nm-fontsize-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
margin-bottom: 20px
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .nm-fontsize-container div {
|
|
||||||
height: 36px;
|
|
||||||
width: 36px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
box-shadow: 0 1px 2px #0003;
|
|
||||||
background-color: #fff;
|
|
||||||
color: tomato;
|
|
||||||
border-radius: 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .nm-fontcolor-container {
|
|
||||||
margin-bottom: 10px
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu input,
|
|
||||||
.map-container .node-menu textarea {
|
|
||||||
background: var(--input-background-color);
|
|
||||||
border: 1px solid var(--panel-border-color);
|
|
||||||
border-radius: var(--bs-border-radius);
|
|
||||||
color: var(--main-color);
|
|
||||||
padding: 5px;
|
|
||||||
margin: 10px 0;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu textarea {
|
|
||||||
resize: none
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .split6 {
|
|
||||||
display: inline-block;
|
|
||||||
width: 16.66%;
|
|
||||||
margin-bottom: 5px
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .palette {
|
|
||||||
border-radius: 100%;
|
|
||||||
width: 21px;
|
|
||||||
height: 21px;
|
|
||||||
border: 1px solid #edf1f2;
|
|
||||||
margin: auto
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .nmenu-selected,
|
|
||||||
.map-container .node-menu .palette:hover {
|
|
||||||
box-shadow: tomato 0 0 0 2px;
|
|
||||||
background-color: #c7e9fa
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .size-selected {
|
|
||||||
background-color: tomato !important;
|
|
||||||
border-color: tomato;
|
|
||||||
fill: #fff;
|
|
||||||
color: #fff
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .size-selected svg {
|
|
||||||
color: #fff
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .bof {
|
|
||||||
text-align: center
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .bof span {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 14px;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 2px 5px
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-container .node-menu .bof .selected {
|
|
||||||
background-color: tomato;
|
|
||||||
color: #fff
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface MindmapModel {
|
|
||||||
direction: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class MindMapWidget extends TypeWidget {
|
export default class MindMapWidget extends TypeWidget {
|
||||||
|
|
||||||
private $content!: JQuery<HTMLElement>;
|
private $content!: JQuery<HTMLElement>;
|
||||||
@@ -161,24 +16,6 @@ export default class MindMapWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
|
||||||
this.$content = this.$widget.find(".mind-map-container");
|
|
||||||
this.$content.on("keydown", (e) => {
|
|
||||||
/*
|
|
||||||
* Some global shortcuts interfere with the default shortcuts of the mind map,
|
|
||||||
* as defined here: https://mind-elixir.com/docs/guides/shortcuts
|
|
||||||
*/
|
|
||||||
if (e.key === "F1") {
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zoom controls
|
|
||||||
const isCtrl = e.ctrlKey && !e.altKey && !e.metaKey;
|
|
||||||
if (isCtrl && (e.key == "-" || e.key == "=" || e.key == "0")) {
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save the mind map if the user changes the layout direction.
|
// Save the mind map if the user changes the layout direction.
|
||||||
this.$content.on("click", ".mind-elixir-toolbar.lt", () => {
|
this.$content.on("click", ".mind-elixir-toolbar.lt", () => {
|
||||||
this.spacedUpdate.scheduleUpdate();
|
this.spacedUpdate.scheduleUpdate();
|
||||||
@@ -213,13 +50,9 @@ export default class MindMapWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #initLibrary(direction?: number) {
|
async #initLibrary(direction?: number) {
|
||||||
this.MindElixir = (await import("mind-elixir")).default;
|
|
||||||
|
|
||||||
const mind = new this.MindElixir({
|
const mind = new this.MindElixir({
|
||||||
el: this.$content[0],
|
|
||||||
direction: direction ?? this.MindElixir.LEFT
|
direction: direction ?? this.MindElixir.LEFT
|
||||||
});
|
});
|
||||||
mind.install(nodeMenu);
|
|
||||||
|
|
||||||
this.mind = mind;
|
this.mind = mind;
|
||||||
mind.init(this.MindElixir.new(NEW_TOPIC_NAME));
|
mind.init(this.MindElixir.new(NEW_TOPIC_NAME));
|
||||||
@@ -230,12 +63,6 @@ export default class MindMapWidget extends TypeWidget {
|
|||||||
this.spacedUpdate.scheduleUpdate();
|
this.spacedUpdate.scheduleUpdate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the note is displayed directly after a refresh, the scroll ends up at (0,0), making it difficult for the user to see.
|
|
||||||
// Adding an arbitrary wait until the element is attached to the DOM seems to do the trick for now.
|
|
||||||
setTimeout(() => {
|
|
||||||
mind.toCenter();
|
|
||||||
}, 200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData() {
|
async getData() {
|
||||||
|
|||||||
Reference in New Issue
Block a user