mirror of
https://github.com/zadam/trilium.git
synced 2025-11-10 15:25:51 +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 { ReadOnlyCode, EditableCode } from "./type_widgets/code/Code";
|
||||
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,
|
||||
@@ -96,6 +97,7 @@ function getCorrespondingWidget(noteType: ExtendedNoteType | undefined, props: T
|
||||
case "readOnlyCode": return <ReadOnlyCode {...props} />
|
||||
case "editableCode": return <EditableCode {...props} />
|
||||
case "mermaid": return <Mermaid {...props} />
|
||||
case "mindMap": return <MindMap {...props} />
|
||||
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 utils from "../../services/utils.js";
|
||||
import type { MindElixirInstance } from "mind-elixir";
|
||||
import nodeMenu from "@mind-elixir/node-menu";
|
||||
import type FNote from "../../entities/fnote.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 {
|
||||
|
||||
private $content!: JQuery<HTMLElement>;
|
||||
@@ -161,24 +16,6 @@ export default class MindMapWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
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.
|
||||
this.$content.on("click", ".mind-elixir-toolbar.lt", () => {
|
||||
this.spacedUpdate.scheduleUpdate();
|
||||
@@ -213,13 +50,9 @@ export default class MindMapWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
async #initLibrary(direction?: number) {
|
||||
this.MindElixir = (await import("mind-elixir")).default;
|
||||
|
||||
const mind = new this.MindElixir({
|
||||
el: this.$content[0],
|
||||
direction: direction ?? this.MindElixir.LEFT
|
||||
});
|
||||
mind.install(nodeMenu);
|
||||
|
||||
this.mind = mind;
|
||||
mind.init(this.MindElixir.new(NEW_TOPIC_NAME));
|
||||
@@ -230,12 +63,6 @@ export default class MindMapWidget extends TypeWidget {
|
||||
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() {
|
||||
|
||||
Reference in New Issue
Block a user