Navigator

This commit is contained in:
KuroSago 2025-05-12 16:47:33 +08:00
parent 533d2e5cac
commit 6d3bf694c1
10 changed files with 316 additions and 61 deletions

View File

@ -18,7 +18,6 @@
"katex": "^0.16.9", "katex": "^0.16.9",
"simple-mind-map-plugin-themes": "^1.0.0", "simple-mind-map-plugin-themes": "^1.0.0",
"@toast-ui/editor": "^3.1.5", "@toast-ui/editor": "^3.1.5",
"axios": "^1.7.9",
"codemirror": "^5.65.16" "codemirror": "^5.65.16"
}, },
"main": "src/index.ts", "main": "src/index.ts",

View File

@ -1,10 +0,0 @@
import tailwindConfig from './tailwind.config';
export default {
plugins: {
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
autoprefixer: {},
tailwindcss: { config: tailwindConfig },
'tailwindcss/nesting': {},
},
};

View File

@ -0,0 +1,299 @@
<template>
<div
v-if="showMiniMap"
class="navigatorBox"
:class="{ isDark: isDark }"
ref="navigatorBoxRef"
:style="{ width: width + 'px' }"
@mousedown="onMousedown"
@mousemove="onMousemove"
>
<div
class="svgBox"
ref="svgBoxRef"
:style="{
transform: `scale(${svgBoxScale})`,
left: svgBoxLeft + 'px',
top: svgBoxTop + 'px',
}"
>
<img :src="mindMapImg" @mousedown.prevent />
</div>
<div
class="windowBox"
:style="viewBoxStyle"
:class="{ withTransition: withTransition }"
@mousedown.stop="onViewBoxMousedown"
@mousemove="onViewBoxMousemove"
></div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed, nextTick, watch } from "vue";
import { useMindMapStore } from "../index";
import { storeToRefs } from "pinia";
const { getMindMapInstance } = useMindMapStore();
const { isDark, showMiniMap } = storeToRefs(useMindMapStore());
const timer = ref(null);
const boxWidth = ref(0);
const boxHeight = ref(0);
const svgBoxScale = ref(1);
const svgBoxLeft = ref(0);
const svgBoxTop = ref(0);
const viewBoxStyle = ref({
left: "0px", //
top: "0px",
bottom: "0px",
right: "0px",
});
const mindMapImg = ref("");
const width = ref(0);
const setSizeTimer = ref(null);
const withTransition = ref(true);
const navigatorBoxRef = ref(null);
const svgBoxRef = ref(null);
function init() {
if (!navigatorBoxRef.value) return;
const rect = navigatorBoxRef.value.getBoundingClientRect();
boxWidth.value = rect.width;
boxHeight.value = rect.height;
}
function drawMiniMap() {
const currentMindMap = getMindMapInstance();
if (
!currentMindMap ||
!currentMindMap.miniMap ||
!boxWidth.value ||
!boxHeight.value
)
return;
try {
const calculationResult = currentMindMap.miniMap.calculationMiniMap(
boxWidth.value,
boxHeight.value
);
if (!calculationResult) return;
const {
getImgUrl,
viewBoxStyle: newViewBoxStyle,
miniMapBoxScale: newMiniMapBoxScale,
miniMapBoxLeft: newMiniMapBoxLeft,
miniMapBoxTop: newMiniMapBoxTop,
} = calculationResult;
if (getImgUrl && typeof getImgUrl === "function") {
getImgUrl((img) => {
mindMapImg.value = img;
});
}
if (newViewBoxStyle) viewBoxStyle.value = newViewBoxStyle;
if (newMiniMapBoxScale !== undefined)
svgBoxScale.value = newMiniMapBoxScale;
if (newMiniMapBoxLeft !== undefined) svgBoxLeft.value = newMiniMapBoxLeft;
if (newMiniMapBoxTop !== undefined) svgBoxTop.value = newMiniMapBoxTop;
} catch (error) {
console.error("Error drawing minimap:", error);
}
}
function toggle_mini_map(show) {
showMiniMap.value = show; // store
// nextTick watch
}
watch(
() => showMiniMap.value,
(isShown) => {
if (isShown) {
nextTick(() => {
navigatorBoxRef.value && init();
svgBoxRef.value && drawMiniMap();
});
}
},
{ immediate: true }
);
function data_change() {
if (!showMiniMap.value) {
// 使 store
return;
}
clearTimeout(timer.value);
timer.value = setTimeout(() => {
drawMiniMap();
}, 500);
}
function setSize() {
clearTimeout(setSizeTimer.value);
setSizeTimer.value = setTimeout(() => {
width.value = Math.min(window.innerWidth - 80, 370);
nextTick(() => {
if (showMiniMap.value && navigatorBoxRef.value) {
init();
drawMiniMap();
}
});
}, 300);
}
function onMousedown(e) {
const currentMindMap = getMindMapInstance();
if (
currentMindMap &&
currentMindMap.miniMap &&
typeof currentMindMap.miniMap.onMousedown === "function"
) {
currentMindMap.miniMap.onMousedown(e);
}
}
function onMousemove(e) {
const currentMindMap = getMindMapInstance();
if (
currentMindMap &&
currentMindMap.miniMap &&
typeof currentMindMap.miniMap.onMousemove === "function"
) {
currentMindMap.miniMap.onMousemove(e);
}
}
function onMouseup(e) {
if (!withTransition.value) {
withTransition.value = true;
}
const currentMindMap = getMindMapInstance();
if (
currentMindMap &&
currentMindMap.miniMap &&
typeof currentMindMap.miniMap.onMouseup === "function"
) {
currentMindMap.miniMap.onMouseup(e);
}
}
function onViewBoxMousedown(e) {
const currentMindMap = getMindMapInstance();
if (
currentMindMap &&
currentMindMap.miniMap &&
typeof currentMindMap.miniMap.onViewBoxMousedown === "function"
) {
currentMindMap.miniMap.onViewBoxMousedown(e);
}
}
function onViewBoxMousemove(e) {
const currentMindMap = getMindMapInstance();
if (
currentMindMap &&
currentMindMap.miniMap &&
typeof currentMindMap.miniMap.onViewBoxMousemove === "function"
) {
currentMindMap.miniMap.onViewBoxMousemove(e);
}
}
function onViewBoxPositionChange({ left, right, top, bottom }) {
withTransition.value = false;
viewBoxStyle.value.left = left;
viewBoxStyle.value.right = right;
viewBoxStyle.value.top = top;
viewBoxStyle.value.bottom = bottom;
}
onMounted(() => {
setSize();
window.addEventListener("resize", setSize);
window.addEventListener("mouseup", onMouseup);
// 线 ()
// eventBus.on('toggle_mini_map', toggle_mini_map);
// eventBus.on('data_change', data_change);
// eventBus.on('view_data_change', data_change); // Vue2 view_data_change data_change
// eventBus.on('node_tree_render_end', data_change);
// watch(getMindMapInstance(), (newInstance) => {
// if (newInstance && typeof newInstance.on === 'function') {
// newInstance.on(
// 'mini_map_view_box_position_change',
// onViewBoxPositionChange
// );
// }
// }, { immediate: true });
// toggle_mini_map(true)
//
});
onUnmounted(() => {
window.removeEventListener("resize", setSize);
window.removeEventListener("mouseup", onMouseup);
// 线 ()
// eventBus.off('toggle_mini_map', toggle_mini_map);
// eventBus.off('data_change', data_change);
// eventBus.off('view_data_change', data_change);
// eventBus.off('node_tree_render_end', data_change);
const currentMindMap = getMindMapInstance();
if (currentMindMap && typeof currentMindMap.off === "function") {
currentMindMap.off(
"mini_map_view_box_position_change",
onViewBoxPositionChange
);
}
clearTimeout(timer.value);
clearTimeout(setSizeTimer.value);
});
</script>
<style lang="scss" scoped>
.navigatorBox {
position: absolute;
height: 220px;
background-color: #fff;
bottom: 24px;
right: 24px;
box-shadow: 0 0 16px #989898;
border-radius: 4px;
border: 1px solid #eee;
cursor: pointer;
user-select: none;
&.isDark {
background-color: #262a2e;
}
.svgBox {
position: absolute;
left: 0;
top: 0;
transform-origin: left top;
}
.windowBox {
position: absolute;
border: 2px solid rgb(238, 69, 69);
background-color: rgba(238, 69, 69, 0.2);
&.withTransition {
transition: all 0.3s;
}
}
}
</style>

View File

@ -7,11 +7,13 @@ import ExportPDF from 'simple-mind-map/src/plugins/ExportPDF'
import ExportXMind from 'simple-mind-map/src/plugins/ExportXMind' import ExportXMind from 'simple-mind-map/src/plugins/ExportXMind'
import Export from 'simple-mind-map/src/plugins/Export' import Export from 'simple-mind-map/src/plugins/Export'
import MindMapLayoutPro from 'simple-mind-map/src/plugins/MindMapLayoutPro' import MindMapLayoutPro from 'simple-mind-map/src/plugins/MindMapLayoutPro'
import MiniMap from 'simple-mind-map/src/plugins/MiniMap'
// import RichText from 'simple-mind-map/src/plugins/RichText' // import RichText from 'simple-mind-map/src/plugins/RichText'
export function usePlugins(MindMapConstructor: typeof MindMap) { export function usePlugins(MindMapConstructor: typeof MindMap) {
// 注册插件 // 注册插件
MindMapConstructor.usePlugin(MiniMap);
MindMapConstructor.usePlugin(ExportPDF); MindMapConstructor.usePlugin(ExportPDF);
MindMapConstructor.usePlugin(ExportXMind); MindMapConstructor.usePlugin(ExportXMind);
MindMapConstructor.usePlugin(Export); MindMapConstructor.usePlugin(Export);

View File

@ -39,6 +39,12 @@ function defineStoreImplementation() {
const mindMapData = ref(null); const mindMapData = ref(null);
// 是否小地图
const showMiniMap = ref(true);
// 是否深色
const isDark = ref(false);
// 激活状态节点 // 激活状态节点
const activeNodes = shallowRef<MindMapNode[]>([]); const activeNodes = shallowRef<MindMapNode[]>([]);
@ -136,6 +142,8 @@ function defineStoreImplementation() {
mindMapData, mindMapData,
activeNodes, activeNodes,
currentLayout, currentLayout,
showMiniMap,
isDark,
getMindMapInstance, getMindMapInstance,
importFile, importFile,

View File

@ -35,4 +35,8 @@ declare module 'simple-mind-map/src/plugins/RichText' {
import RichText from 'simple-mind-map/types/src/plugins/RichText'; import RichText from 'simple-mind-map/types/src/plugins/RichText';
export default RichText; export default RichText;
} }
declare module 'simple-mind-map/src/plugins/MiniMap' {
import MiniMap from 'simple-mind-map/types/src/plugins/MiniMap';
export default MiniMap;
}

View File

@ -2,6 +2,8 @@
<div class="relative w-full h-full border"> <div class="relative w-full h-full border">
<!-- 操作拦 --> <!-- 操作拦 -->
<ToolBar /> <ToolBar />
<!-- 导航器 -->
<Navigator />
<div <div
class="w-full h-full mindMapContainer" class="w-full h-full mindMapContainer"
id="mindMapContainer" id="mindMapContainer"
@ -13,6 +15,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import ToolBar from "../components/ToolBar.vue"; import ToolBar from "../components/ToolBar.vue";
import Navigator from "../components/Navigator.vue";
import { useMindMapStore } from "../store/index"; import { useMindMapStore } from "../store/index";
import type MindMapNode from "simple-mind-map/types/src/core/render/node/MindMapNode"; import type MindMapNode from "simple-mind-map/types/src/core/render/node/MindMapNode";

View File

@ -1,50 +0,0 @@
import type { Config } from 'tailwindcss'
export default {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
border: "rgb(var(--border) / <alpha-value>)",
input: "rgb(var(--input) / <alpha-value>)",
ring: "rgb(var(--ring) / <alpha-value>)",
background: "rgb(var(--background) / <alpha-value>)",
foreground: "rgb(var(--foreground) / <alpha-value>)",
primary: {
DEFAULT: "rgb(var(--primary) / <alpha-value>)",
foreground: "rgb(var(--primary-foreground) / <alpha-value>)",
hover: "rgb(var(--primary-hover) / <alpha-value>)",
active: "rgb(var(--primary-active) / <alpha-value>)",
},
secondary: {
DEFAULT: "rgb(var(--secondary) / <alpha-value>)",
foreground: "rgb(var(--secondary-foreground) / <alpha-value>)",
},
destructive: {
DEFAULT: "rgb(var(--destructive) / <alpha-value>)",
foreground: "rgb(var(--destructive-foreground) / <alpha-value>)",
},
muted: {
DEFAULT: "rgb(var(--muted) / <alpha-value>)",
foreground: "rgb(var(--muted-foreground) / <alpha-value>)",
},
accent: {
DEFAULT: "rgb(var(--accent) / <alpha-value>)",
foreground: "rgb(var(--accent-foreground) / <alpha-value>)",
},
popover: {
DEFAULT: "rgb(var(--popover) / <alpha-value>)",
foreground: "rgb(var(--popover-foreground) / <alpha-value>)",
},
card: {
DEFAULT: "rgb(var(--card) / <alpha-value>)",
foreground: "rgb(var(--card-foreground) / <alpha-value>)",
},
},
},
},
plugins: [],
} satisfies Config

View File