Skip to content

Commit

Permalink
Add options for node menu (#4354)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Chang <mwdchang@gmail.com>
  • Loading branch information
asylves1 and mwdchang authored Aug 6, 2024
1 parent 0fe1110 commit 25d4855
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,43 @@
aria-controls="overlay_menu"
severity="secondary"
/>
<Menu ref="menu" id="overlay_menu" :model="menuItems" :popup="true" />
<Menu
ref="menu"
id="overlay_menu"
:model="menuItems"
:popup="true"
@focus="emit('menu-focus')"
@blur="emit('menu-blur')"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ref, onMounted } from 'vue';
import Menu from 'primevue/menu';
import Button from 'primevue/button';
import { OperatorMenuItem } from '@/services/workflow';
const isMenuShowing = ref<boolean>(false);
const props = defineProps<{
nodeMenu: OperatorMenuItem[];
}>();
const emit = defineEmits(['menu-focus', 'menu-blur', 'menu-selection']);
const isMenuShowing = ref<boolean>(false);
const menu = ref();
const menuItems = ref([
{
// label: "Options",
items: [
{
label: 'Configure Model',
command() {}
},
{
label: 'Stratify Model',
command() {}
},
{
label: 'Edit Model',
command() {}
const menuItems = ref();
onMounted(() => {
const options: Array<{}> = [];
props.nodeMenu.forEach((node) =>
options.push({
label: node.displayName,
command() {
emit('menu-selection', node.type);
}
]
}
]);
})
);
menuItems.value = [{ items: options }];
});
function showMenu(event) {
menu.value.show(event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,17 @@
/>
</main>
<tera-operator-menu
v-if="isMenuFocused"
v-if="isMenuFocused && menuOptions.length"
:nodeMenu="menuOptions"
:style="{
top: '-30px',
left: `${operatorPosition.width + 20}px`
}"
@mouseenter="showOutputButton(PortType.Output)"
@mouseleave="hideOutputButton"
@focus="() => {}"
@blur="() => {}"
@menu-focus="showOutputButton(PortType.Output)"
@menu-blur="hideOutputButton"
@menu-selection="(operatorType) => onSelection(operatorType)"
@focus="showOutputButton(PortType.Output)"
@blur="hideOutputButton"
/>
</template>

Expand All @@ -68,15 +70,18 @@ import TeraOperatorInputs from '@/components/operator/tera-operator-inputs.vue';
import TeraOperatorOutputs from '@/components/operator/tera-operator-outputs.vue';
import TeraOperatorAnnotation from '@/components/operator/tera-operator-annotation.vue';
import TeraOperatorMenu from '@/components/operator/tera-operator-menu.vue';
import { OperatorMenuItem } from '@/services/workflow';
const props = defineProps<{
node: WorkflowNode<any>;
nodeMenu: Map<string, OperatorMenuItem[]>;
}>();
const emit = defineEmits([
'port-selected',
'port-mouseover',
'port-mouseleave',
'menu-selection',
'remove-operator',
'remove-edges',
'resize',
Expand All @@ -96,6 +101,7 @@ const annotationRef = ref<typeof TeraOperatorAnnotation | null>(null);
const isMenuFocused = ref<boolean>(false);
const menuButtonTimeoutId = ref<NodeJS.Timeout | null>(null);
const menuOptions = ref<OperatorMenuItem[] | []>([]);
let resizeObserver: ResizeObserver | null = null;
Expand Down Expand Up @@ -143,11 +149,16 @@ function mouseleavePort() {
emit('port-mouseleave');
}
function onSelection(operatorType: string) {
emit('menu-selection', operatorType);
}
function resizeHandler() {
emit('resize', props.node);
}
onMounted(() => {
menuOptions.value = props.nodeMenu.get(props.node.operationType) ?? [];
if (operator.value) {
resizeObserver = new ResizeObserver(resizeHandler);
resizeObserver.observe(operator.value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
<tera-operator
ref="teraOperatorRefs"
:node="node"
:nodeMenu="nodeMenu"
@resize="resizeHandler"
@port-selected="(port: WorkflowPort, direction: WorkflowDirection) => createNewEdge(node, port, direction)"
@port-mouseover="onPortMouseover"
Expand All @@ -76,6 +77,7 @@
@duplicate-branch="duplicateBranch(node.id)"
@remove-edges="removeEdges"
@update-state="(event: any) => updateWorkflowNodeState(node, event)"
@menu-selection="(operatorType) => onMenuSelection(operatorType, node)"
>
<template #body>
<component
Expand Down Expand Up @@ -174,7 +176,7 @@ import InputText from 'primevue/inputtext';
import Menu from 'primevue/menu';
import ContextMenu from 'primevue/contextmenu';
import * as workflowService from '@/services/workflow';
import { OperatorImport, OperatorNodeSize } from '@/services/workflow';
import { OperatorImport, OperatorNodeSize, getNodeMenu } from '@/services/workflow';
import * as d3 from 'd3';
import { AssetType, EventType } from '@/types/Types';
import { useDragEvent } from '@/services/drag-drop';
Expand Down Expand Up @@ -237,6 +239,8 @@ const props = defineProps<{
assetId: string;
}>();
const nodeMenu = ref(getNodeMenu(registry.operationMap));
const route = useRoute();
const router = useRouter();
Expand Down Expand Up @@ -439,6 +443,21 @@ const addOperatorToWorkflow: Function =
workflowDirty = true;
};
function onMenuSelection(operatorType: string, menuNode: WorkflowNode<any>) {
const name = operatorType;
const operation = registry.getOperation(operatorType);
const node = registry.getNode(operatorType);
const drilldown = registry.getDrilldown(operatorType);
const width = workflowService.getOperatorNodeSize(OperatorNodeSize.medium).width;
newNodePosition.x = menuNode.x + 80 + width;
newNodePosition.y = menuNode.y;
if (name && operation && node && drilldown) {
addOperatorToWorkflow({ name, operation, node, drilldown })();
}
}
// Menu categories and list items are in order of appearance for separators to work
const contextMenuItems: MenuItem[] = [
{
Expand Down
60 changes: 59 additions & 1 deletion packages/client/hmi-client/src/services/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { EventEmitter } from '@/utils/emitter';
import type { Position } from '@/types/common';
import type {
Operation,
OperationData,
Size,
Workflow,
WorkflowEdge,
Expand Down Expand Up @@ -43,7 +44,7 @@ export enum OperatorNodeSize {
xlarge
}

function getOperatorNodeSize(size: OperatorNodeSize): Size {
export function getOperatorNodeSize(size: OperatorNodeSize): Size {
switch (size) {
case OperatorNodeSize.small:
return { width: 140, height: 220 };
Expand Down Expand Up @@ -634,3 +635,60 @@ export const branchWorkflow = (wf: Workflow, nodeId: string) => {
wf.nodes.push(...copyNodes);
wf.edges.push(...copyEdges);
};

export interface OperatorMenuItem {
type: string;
displayName: string;
}

function assetToOperation(operationMap: Map<string, Operation>) {
const result = new Map<string, OperatorMenuItem[]>();
operationMap.forEach((operation, key) => {
const inputList: Array<OperationData> = operation.inputs ?? [];
inputList.forEach((input) => {
input.type.split('|').forEach((subType) => {
if (!result.has(subType)) {
result.set(subType, []);
} else {
result.get(subType)?.push({
type: key,
displayName: operation.displayName
});
}
});
});
});
return result;
}

function operationToAsset(operationMap: Map<string, Operation>) {
const result = new Map<string, string[]>();

operationMap.forEach((operation, key) => {
result.set(key, []);

const outputList: OperationData[] = operation.outputs ?? [];
outputList.forEach((output) => {
output.type.split('|').forEach((subType) => {
result.get(key)?.push(subType);
});
});
});
return result;
}

/* We want to get mapping of { operation => [operation] } */
export function getNodeMenu(operationMap: Map<string, Operation>) {
const menuOptions = new Map<string, OperatorMenuItem[]>();

const inputMap = assetToOperation(operationMap);
const outputMap = operationToAsset(operationMap);

const uniqInputMap: Map<string, OperatorMenuItem[]> = new Map();
inputMap.forEach((menuItem, key) => uniqInputMap.set(key, _.uniqBy(menuItem, 'type')));

outputMap.forEach((value, key) => {
menuOptions.set(key, uniqInputMap.get(value[0]) ?? []);
});
return menuOptions;
}

0 comments on commit 25d4855

Please sign in to comment.