2025-12-07 04:59:10 +07:00

338 lines
7.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import HeaderBlock from './HeaderBlock.vue';
import { Network, type Data } from 'vis-network'
import { getGraph, updateNode } from './client';
import type { Graph, GraphEdge, GraphNode } from './models';
const network = ref<HTMLElement>()
const graph = ref<Graph>({
nodes: [],
edges: []
})
const selectedNode = ref<GraphNode>({
id: 0,
label: "",
name: "",
text: "",
applications: [],
links: [],
})
let net = <Network>{}
let data = <Data>{}
const displayEdges = ref(0)
const allEdges = ref(0)
const onApplicationEdges = ref(true)
async function loadGraph() {
graph.value = await getGraph()
allEdges.value = graph.value.edges.length
if (onApplicationEdges.value) {
graph.value.edges = graph.value.edges.filter(function (edge: GraphEdge) { return edge.type !== 'application' })
}
graph.value.edges.map(function (edge: GraphEdge) {
if (edge.type == 'application') {
edge.color = '#cccccc'
}
})
displayEdges.value = graph.value.edges.length
data = {
nodes: graph.value.nodes,
edges: graph.value.edges.sort(function (a: GraphEdge, b: GraphEdge) {
if (a.type == 'application') {
return 1
}
if (b.type == 'application') {
return -1
}
return 0
})
}
net.setData(data)
console.log(graph.value.edges)
}
onMounted(async () => {
if (!network.value) return
const options = {
interaction: {
selectable: true,
},
nodes: {
color: {
border: '#2B7CE9', // Normal border color
background: '#97C2FC', // Normal background color
highlight: { // Selection state
border: '#960000',
background: '#ff9494'
},
hover: { // Hover state
border: '#2B7CE9',
background: '#D2E5FF'
}
}
},
}
net = new Network(network.value, data, options)
net.on("click", function (params) {
if (params.nodes.length > 0) {
selectNode(graph.value.nodes[params.nodes[0]])
} else if (params.edges.length > 0) {
console.log("Clicked edge:", params.edges[0]);
}
});
await loadGraph()
selectNode(graph.value.nodes[0])
})
function selectNode(node: GraphNode) {
console.log("Select node:", node.id)
selectedNode.value = node
const links = graph.value.edges.filter(function (it: GraphEdge) {
return it.from == node.id
}).map(function (it: GraphEdge): GraphNode {
const id = it.to
const linkNode = graph.value.nodes.filter(function (it: GraphNode) { return it.id == id })
return linkNode[0]
})
selectedNode.value.links = links
net.selectNodes([selectedNode.value.id])
}
async function updateSelectedNode() {
console.log("Update node:", selectedNode.value)
await updateNode(selectedNode.value)
await loadGraph()
}
function nodeHeader(node: GraphNode): string {
return "[" + node.label + "] - " + node.name
}
</script>
<template>
<HeaderBlock>
<div>
Редактор сценариев
</div>
</HeaderBlock>
<div ref="network" class="graph-container"></div>
<div class="nodes-container">
<h2>Точки</h2>
<div>Всего точек: {{ graph.nodes.length }}</div>
<div>
Всего связей: {{ allEdges }}, показано: {{ displayEdges }}
<div>
Показать все связи:
<label class="checkbox-green">
<input type="checkbox" v-on:click="onApplicationEdges = !onApplicationEdges, loadGraph()">
<span class="checkbox-green-switch" data-label-on="Вкл" data-label-off="Выкл"></span>
</label>
</div>
</div>
<hr class="hr">
<div v-bind:key="node.id" v-for="node in graph.nodes">
<div :class="[node.id == selectedNode.id ? 'selected-node' : '']" class="node-select-button"
v-on:click="selectNode(node)">
{{ nodeHeader(node) }}
<span v-if="node.applications.length > 0">({{ node.applications.length }})</span>
</div>
</div>
</div>
<div class="edit-node-container">
<h2>Редактирование точки</h2>
<div>
{{ nodeHeader(selectedNode) }}
</div>
<div>
<textarea class="node-text-edit-field" rows="30" v-model="selectedNode.text"></textarea>
</div>
<div>
<h3>Приложения: {{ selectedNode.applications.length }}</h3>
<div v-bind:key="index" v-for="(application, index) in selectedNode.applications">
<textarea class="node-text-edit-field" rows="5" v-model="application.name"></textarea>
</div>
</div>
<div>
<h3>Ссылки: {{ selectedNode.links.length }}</h3>
<div v-bind:key="node.id" v-for="node in selectedNode.links">
<div class="node-select-button" v-on:click="selectNode(node)">
- {{ nodeHeader(node) }}
</div>
</div>
</div>
<div>
<button class="node-edit-save-button" v-on:click="updateSelectedNode()">Сохранить</button>
</div>
</div>
</template>
<style scoped>
.graph-container {
width: 100%;
height: calc(100vh - 50px);
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.nodes-container {
position: fixed;
left: 5px;
top: 55px;
height: calc(100vh - 100px);
padding: 10px 20px;
}
.edit-node-container {
position: fixed;
right: 5px;
top: 55px;
height: calc(100vh - 100px);
padding: 10px 20px;
min-width: 350px;
max-width: 400px;
}
.node-select-button {
color: #373737;
}
.node-select-button:hover {
font-weight: bold;
cursor: pointer;
}
.selected-node {
font-weight: bold;
color: #960000;
cursor: pointer;
}
.node-text-edit-field {
padding: 7px;
margin: 5px 0;
width: 100%;
}
.node-edit-save-button {
padding: 3px 7px;
margin: 5px;
background-color: #ffffff;
border-radius: 7px;
border: 1px solid #373737;
}
.node-edit-save-button:hover {
background-color: #dddddd;
cursor: pointer;
}
.hr {
margin: 10px 0;
}
.checkbox-green {
display: inline-block;
height: 20px;
line-height: 28px;
margin-right: 10px;
position: relative;
vertical-align: middle;
font-size: 14px;
user-select: none;
}
.checkbox-green .checkbox-green-switch {
display: inline-block;
height: 20px;
width: 90px;
box-sizing: border-box;
position: relative;
border-radius: 2px;
background: #848484;
transition: background-color 0.3s cubic-bezier(0, 1, 0.5, 1);
}
.checkbox-green .checkbox-green-switch:before {
content: attr(data-label-on);
display: inline-block;
box-sizing: border-box;
width: 45px;
padding: 0 8px;
position: absolute;
top: 0;
left: 45px;
text-transform: uppercase;
text-align: center;
color: rgba(255, 255, 255, 0.5);
font-size: 10px;
line-height: 20px;
}
.checkbox-green .checkbox-green-switch:after {
content: attr(data-label-off);
display: inline-block;
box-sizing: border-box;
width: 44px;
border-radius: 1px;
position: absolute;
top: 1px;
left: 1px;
z-index: 5;
text-transform: uppercase;
text-align: center;
background: white;
line-height: 18px;
font-size: 10px;
color: #777;
transition: transform 0.3s cubic-bezier(0, 1, 0.5, 1);
}
.checkbox-green input[type="checkbox"] {
display: block;
width: 0;
height: 0;
position: absolute;
z-index: -1;
opacity: 0;
}
.checkbox-green input[type="checkbox"]:checked + .checkbox-green-switch {
background-color: #848484;
}
.checkbox-green input[type="checkbox"]:checked + .checkbox-green-switch:before {
content: attr(data-label-off);
left: 0;
}
.checkbox-green input[type="checkbox"]:checked + .checkbox-green-switch:after {
content: attr(data-label-on);
color: #848484;
transform: translate3d(44px, 0, 0);
}
/* Hover */
.checkbox-green input[type="checkbox"]:not(:disabled) + .checkbox-green-switch:hover {
cursor: pointer;
}
/* Disabled */
.checkbox-green input[type=checkbox]:disabled + .checkbox-green-switch {
opacity: 0.6;
filter: grayscale(50%);
}
/* Focus */
.checkbox-green.focused .checkbox-green-switch:after {
box-shadow: inset 0px 0px 4px #ff5623;
}
</style>