Skip to content

Commit

Permalink
added controls in sankey diagram for adding new connections and nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
ishubin committed Feb 5, 2025
1 parent 69696e6 commit 19a743f
Show file tree
Hide file tree
Showing 12 changed files with 306 additions and 32 deletions.
3 changes: 1 addition & 2 deletions assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -2966,10 +2966,9 @@ ul.script-options .script-option.selected {
top: 0;
left: 0;
z-index: 998;
/* overflow: auto; */
}
.context-menu ul {
position: absolute;
position: fixed;
margin: 0;
padding: 0;
list-style: none;
Expand Down
2 changes: 1 addition & 1 deletion assets/templates/diagrams/mind-map.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/templates/diagrams/sankey.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions assets/templates/diagrams/src/control.sch
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ struct Control {
selectedItemId: ""
type: 'button'
options: List()
optionsProvider: null
}
122 changes: 120 additions & 2 deletions assets/templates/diagrams/src/sankey.sch
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,10 @@ func onAreaUpdate(itemId, item, area) {
node.x = (area.x - node.position) / max(1, width)
node.y = (area.y - node.offset) / max(1, height)

if (abs(node.x*width) < 7) {
node.x = 0
}

nodesData = encodeNodes(nodesById)
}
}
Expand Down Expand Up @@ -743,10 +747,38 @@ func onDeleteItem(itemId, item) {
}


func provideOptionsForDstNode(nodeId) {
local options = List('-- Create new node --')

local node = allNodes.find(n => {n.id == nodeId})
if (node) {
allNodes.forEach(anotherNode => {
if (anotherNode.level > node.level) {
options.add(anotherNode.id)
}
})
}
options
}

func provideOptionsForSrcNode(nodeId) {
local options = List('-- Create new node --')

local node = allNodes.find(n => {n.id == nodeId})
if (node) {
allNodes.forEach(anotherNode => {
if (anotherNode.level < node.level) {
options.add(anotherNode.id)
}
})
}
options
}

func generateNodeControls(nodes) {
local controls = List()
nodes.forEach(node => {
if (node.srcNodes.size > 0 && node.dstNodes.size == 0) {
if (node.level == levels.size - 1) {
controls.add(Control(
"+",
Map(
Expand All @@ -761,13 +793,94 @@ func generateNodeControls(nodes) {
'n-' + node.id,
'button'
))
} else {
local control = Control(
"+",
Map(
'nodeId', node.id
),
"addDstConnection(control.data.nodeId, option)",
node.position + node.x * width + node.width + 20,
node.offset + node.y * height + node.height / 2 - 10,
20, 20,
'+',
'TL',
'n-' + node.id,
'choice'
)

control.optionsProvider = `provideOptionsForDstNode(control.data.nodeId)`
controls.add(control)
}

if (node.level > 0) {
local control = Control(
"+",
Map(
'nodeId', node.id
),
"addSrcConnection(control.data.nodeId, option)",
node.position + node.x * width - 20,
node.offset + node.y * height + node.height / 2 - 10,
20, 20,
'+',
'TR',
'n-' + node.id,
'choice'
)

control.optionsProvider = `provideOptionsForSrcNode(control.data.nodeId)`
controls.add(control)
}
})
controls
}

func addDstConnection(nodeId, option) {
if (option == '-- Create new node --') {
addDstNodeForNode(nodeId)
} else {
local srcNode = allNodes.find(node => {node.id == nodeId})
local dstNode = allNodes.find(node => {node.id == option})
if (srcNode && dstNode) {
local value = srcNode.value
if (srcNode.value - srcNode.outValue > 0) {
value = srcNode.value - srcNode.outValue
}
codeLines.add(CodeLine(`${srcNode.id} [${value}] ${dstNode.id}`, null))
diagramCode = encodeDiagram()
}
}
}

func addSrcConnection(nodeId, option) {
if (option == '-- Create new node --') {
addSrcNodeForNode(nodeId)
} else {
local srcNode = allNodes.find(node => {node.id == option})
local dstNode = allNodes.find(node => {node.id == nodeId})
if (srcNode && dstNode) {
local value = srcNode.value
if (srcNode.value - srcNode.outValue > 0) {
value = srcNode.value - srcNode.outValue
}
codeLines.add(CodeLine(`${srcNode.id} [${value}] ${dstNode.id}`, null))
diagramCode = encodeDiagram()
}
}
}



func addDstNodeForNode(nodeId) {
addNewNodeForNode(nodeId, true)
}

func addSrcNodeForNode(nodeId) {
addNewNodeForNode(nodeId, false)
}

func addNewNodeForNode(nodeId, isDst) {
local allNodeIds = Set()
local selectedNode = null

Expand All @@ -786,9 +899,14 @@ func addDstNodeForNode(nodeId) {
if (!allNodeIds.has(newId)) {
foundUniqueId = true
}
idx += 1
}
if (foundUniqueId) {
codeLines.add(CodeLine(`${selectedNode.id} [${selectedNode.value}] ${newId}`, null))
if (isDst) {
codeLines.add(CodeLine(`${selectedNode.id} [${selectedNode.value}] ${newId}`, null))
} else {
codeLines.add(CodeLine(`${newId} [${selectedNode.value}] ${selectedNode.id}`, null))
}
diagramCode = encodeDiagram()
if (selectedNode.level == levels.size - 1) {
width += max(1, width - nodeWidth) / max(1, (levels.size - 1))
Expand Down
23 changes: 22 additions & 1 deletion src/ui/components/SchemeEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
:zoom="schemeContainer.screenTransform.scale"
:boundaryBoxColor="schemeContainer.scheme.style.boundaryBoxColor"
:controlPointsColor="schemeContainer.scheme.style.controlPointsColor"
@choice-control-clicked="onEditBoxChoiceControlClicked"
@custom-control-clicked="onEditBoxCustomControlClicked"
@template-rebuild-requested="onEditBoxTemplateRebuildRequested"
@template-properties-updated-requested="onEditBoxTemplatePropertiesUpdateRequested"
Expand Down Expand Up @@ -618,7 +619,7 @@ import shortid from 'shortid';
import utils from '../utils.js';
import {dragAndDropBuilder} from '../dragndrop.js';
import myMath from '../myMath';
import { Keys, registerKeyPressHandler, deregisterKeyPressHandler } from '../events';
import { Keys, registerKeyPressHandler, deregisterKeyPressHandler, mouseCoordsFromEvent } from '../events';
import DrawingControlsPanel from './DrawingControlsPanel.vue';

import {applyStyleFromAnotherItem, defaultItem, defaultTextSlotProps } from '../scheme/Item';
Expand Down Expand Up @@ -3334,6 +3335,26 @@ export default {
this.schemeContainer.updateEditBox();
},

mouseCoordsFromEvent(event) {
const p = mouseCoordsFromEvent(event);
if (!p) {
return this.mouseCoordsFromPageCoords(0, 0);
}
return this.mouseCoordsFromPageCoords(p.x, p.y);
},

onEditBoxChoiceControlClicked({options, editBoxId, event, callback}) {
const p = this.mouseCoordsFromEvent(event);
this.$emit('context-menu-requested', p.x, p.y, options.map(option => {
return {
name: option,
clicked: () => {
callback(option);
}
}
}));
},

onWindowResize() {
const minExpectedGap = 20;
if (this.sidePanelRightWidth + minExpectedGap > window.innerWidth / 2) {
Expand Down
57 changes: 47 additions & 10 deletions src/ui/components/editor/ContextMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,26 @@
file, You can obtain one at https://mozilla.org/MPL/2.0/. -->
<template>
<div class="context-menu" oncontextmenu="return false;" :style="{'max-height': `${maxHeight}px`}">
<ul ref="rootContextMenu" :style="{'top': `${y}px`, 'left': `${x}px`}">
<li v-for="(option, optionIndex) in options" @click="onOptionSelected(option)">
<ul ref="rootContextMenu" :style="{'top': `${y}px`, 'left': `${x}px`, 'max-height': `${maxHeight}px`, 'overflow': 'auto'}">
<li v-for="(option, optionIndex) in options"
@click="onOptionSelected(option)"
@mouseover="onOptionMouseOver(option, $event)"
class="context-menu-option"
>
<span class="context-menu-suboptions-icon">
<i v-if="option.subOptions" class="fas fa-caret-right"/>
</span>

<i v-if="option.iconClass" :class="option.iconClass"/>
<span class="context-menu-option-name">{{option.name}}</span>
</li>
</ul>


<ul v-if="option.subOptions" :style="subOptionsStyle(optionIndex, option.subOptions)">
<li v-for="subOption in option.subOptions" @click="onOptionSelected(subOption)">
<i v-if="subOption.iconClass" :class="subOption.iconClass"/>
<span class="context-menu-option-name">{{subOption.name}}</span>
</li>
</ul>
<ul v-if="subOptionMenu.shown" ref="subOptionMenu"
:style="{'top': `${subOptionMenu.y}px`, 'left': `${subOptionMenu.x}px`, 'max-height': `${maxHeight}px`, 'overflow': 'auto'}">
<li v-for="subOption in subOptionMenu.options" @click="onOptionSelected(subOption)">
<i v-if="subOption.iconClass" :class="subOption.iconClass"/>
<span class="context-menu-option-name">{{subOption.name}}</span>
</li>
</ul>
</div>
Expand Down Expand Up @@ -65,11 +69,44 @@ export default {
menuWidth: 100,
menuHeight: 100,
maxHeight: window.innerHeight - 30,
mountTime: new Date().getTime()
mountTime: new Date().getTime(),

subOptionMenu: {
shown: false,
x: 0,
y: 0,
options: []
}
}
},

methods: {
onOptionMouseOver(option, event) {
if (option.subOptions) {
const li = event.target.closest('li.context-menu-option');
if (!li) {
return;
}
const liRect = li.getBoundingClientRect();
this.subOptionMenu.x = liRect.right;
this.subOptionMenu.y = liRect.top;
this.subOptionMenu.options = option.subOptions;
this.subOptionMenu.shown = true;
this.$nextTick(() => {
if (!this.$refs.subOptionMenu) {
return;
}
const rect = this.$refs.subOptionMenu.getBoundingClientRect();
if (rect.bottom > window.innerHeight) {
this.subOptionMenu.y = window.innerHeight - rect.height;
}
if (rect.right > window.innerWidth) {
this.subOptionMenu.x = window.innerWidth - rect.width;
}
});
}
},

onDocumentClick(event) {
if (!utils.domHasParentNode(event.target, domElement => domElement.classList.contains('context-menu'))) {
if (new Date().getTime() - this.mountTime > 500) {
Expand Down
50 changes: 45 additions & 5 deletions src/ui/components/editor/EditBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
</g>

<g v-for="(control,idx) in templateControls">
<template v-if="control.type === 'button'" >
<template v-if="control.type === 'button' || control.type === 'choice'">
<rect
class="item-control-point"
:x="control.x - control.xc * control.width/safeZoom"
Expand All @@ -175,7 +175,7 @@
fill="rgba(0,0,0,0)"
:rx="10/safeZoom"
data-type="edit-box-template-control"
@click="onTemplateControlClick(idx)"
@click="onTemplateControlClick(idx, $event)"
/>
</template>
<template v-else-if="control.type === 'textfield'">
Expand Down Expand Up @@ -588,7 +588,7 @@ export default {
customControls: [],
templateControls: [],
colorControlToggled: false,
draggingFileOver: false
draggingFileOver: false,
};
},

Expand Down Expand Up @@ -776,10 +776,50 @@ export default {
this.$emit('template-properties-updated-requested');
},

onTemplateControlClick(idx) {
expandTemplateControlChoiceOptions(control, event) {
const item = this.editBox.templateItemRoot;
let options = control.options;
if (control.optionsProvider) {
options = control.optionsProvider(item);
}
this.$emit('choice-control-clicked', {
options: options,
editBoxId: this.editBox.id,
event,

callback: (selectedOption) => {
const originArgs = utils.clone(item.args.templateArgs);
const updatedArgs = control.click(item, selectedOption);
item.area.w = updatedArgs.width;
item.area.h = updatedArgs.height;

const diff = jsonDiff(originArgs, updatedArgs);
if (diff.changes.length > 0) {
EditorEventBus.schemeChangeCommitted.$emit(this.editorId);
}

if (item.args && item.args.templateArgs) {
for (let key in item.args.templateArgs) {
if (updatedArgs.hasOwnProperty(key)) {
item.args.templateArgs[key] = updatedArgs[key];
}
}
}
this.$emit('template-rebuild-requested', this.editBox.templateItemRoot.id, this.template, item.args.templateArgs);
this.$emit('template-properties-updated-requested');
},
});
},

onTemplateControlClick(idx, event) {
const control = this.templateControls[idx];
if (control.type === 'choice') {
this.expandTemplateControlChoiceOptions(control, event);
return;
}
const item = this.editBox.templateItemRoot;
const originArgs = utils.clone(item.args.templateArgs);
const updatedArgs = this.templateControls[idx].click(item);
const updatedArgs = control.click(item);
item.area.w = updatedArgs.width;
item.area.h = updatedArgs.height;

Expand Down
Loading

0 comments on commit 19a743f

Please sign in to comment.