Index: arms/html/mapping/content-container.html =================================================================== diff -u -ra1bd3f8931ef8054940f42b2dba5d95d1cba5e38 -r621657e71b30c2be62c5e32e363eeb280de7d0ee --- arms/html/mapping/content-container.html (.../content-container.html) (revision a1bd3f8931ef8054940f42b2dba5d95d1cba5e38) +++ arms/html/mapping/content-container.html (.../content-container.html) (revision 621657e71b30c2be62c5e32e363eeb280de7d0ee) @@ -1,11 +1,12 @@ +

- + - 상태 카테고리별 분류 + A-RMS 상태별 ALM 상태 매핑

@@ -37,142 +38,342 @@ style="font-size: 11px">
+
-
-
- -
-
-
-
-
-
- -
-
-
-
+
+
+

+ - 1. A-RMS 요구사항의 상태 목록을 카테고리별 보드 형태로 확인합니다. + style="font-weight: bold"> + + ALM 서버 선택 + +

+
+
+
+
+
+ 1. ALM 서버 선택
-
-
-
-
-
+
+ <!– 컴포넌트 대상 –> +
+
+
  • +
    +
    + +
    + + 선택된 서버 + : + + 선택되지 않음 + +
    + +
    +
    +
  • +
    +
    +
    +
    +
    +
    + +
    +
    + -
    +
    +
    + 클라우드 지라의 경우 "프로젝트" 항목을 선택하시면 해당 이슈유형별 상태를 매핑할 수 있습니다. +
    -
    -
    - 2. A-RMS 요구사항의 상태와 카테고리를 테이블 형태로 확인합니다. -
    -
    -
    -
    - - - - - - - - -
    c_idc_titlec_id
    -
    +
    +
    + <!–
    +
    +
    +
    + 1. 제품(서비스) 선택 +
    + +
    +
    + +
    +
    +
    +
    +

    + + + 제품(서비스) - 버전 선택 + +

    +
    +
    +
    +
    +
    + 1. 제품 + 서비스 + 선택 → 버전 선택 +
    +
    + <!– 컴포넌트 대상 –> +
    +
    +
  • +
    +
    + +
    + + 선택된 제품 + 서비스 + : + + 선택되지 않음 + +
    +
    + + 선택된 버전 : + + 선택되지 않음 + +
    +
    +
    +
  • +
    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    –> +
    --> +
    +
    +
    +
    + + +
    +
    +
    + + + +
    +
    + +
    @@ -182,7 +383,7 @@
    -
    + + <!–
    + 1. ARMS 요구사항의 상태와 ALM 이슈 상태와 연결합니다. +
    –>
    @@ -249,7 +450,7 @@ 1. ALM 서버 선택
    - + <!– 컴포넌트 대상 –>
    - +
    +
    +
    +
    +

    + + + 제품(서비스) - 버전 선택 + +

    +
    +
    +
    +
    +
    + 1. 제품 + 서비스 + 선택 → 버전 선택 +
    +
    + <!– 컴포넌트 대상 –> +
    +
    +
  • +
    +
    + +
    + + 선택된 제품 + 서비스 + : + + 선택되지 않음 + +
    +
    + + 선택된 버전 : + + 선택되지 않음 + +
    +
    +
    +
  • +
    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + –>
    - - + + +
    -
    +
    -->
    { const state = (item && item.c_etc) || "상태 정보 없음"; @@ -324,20 +348,6 @@ adjustHeight(); } -function state_category_tab_group_click_event() { - $('ul[data-group="state_category_tab_group"] a[data-toggle="tab"]').on("shown.bs.tab", function(e) { - var target = $(e.target).attr("href"); // activated tab - - if (target === "#board") { - setKanban(); - } - else if (target === "#table"){ - dataTableLoad(); - } - }); -} - - //////////////////////////////////////////////////////////////////////////////////////// // --- 데이터 테이블 설정 --- // //////////////////////////////////////////////////////////////////////////////////////// @@ -773,6 +783,8 @@ let alm_server_data = alm_server_list[selected_alm_server_id]; let alm_server_type = alm_server_data.c_jira_server_type; + var $flowchart = $('#state_flow_chart'); + $flowchart.flowchart('setData', {}); if (alm_server_type === "클라우드") { $("#cloud_project_tree").show(); $("#select-project-div").show(); @@ -787,8 +799,65 @@ dataType: "json", progress: true, statusCode: { - 200: function(data) { - console.log(data.response); + 200: function(result) { + console.log(result.response); + let alm_status_list = result.response; + + var data = { + operators: {}, + links: {} + }; + + console.log(alm_status_list); + console.log(arms_state_list); + let inputData = arms_state_list; + let outputData = alm_status_list; + + let width = $flowchart.width(); + + var topPosition = 20; + var leftPositionInput = 20; + var leftPositionOutput = width-200; + + console.log(width); + inputData.forEach(function(input, index) { + var operatorId = 'operator' + (index + 1); + data.operators[operatorId] = { + top: topPosition + index * 80, + left: leftPositionInput, + properties: { + title: "A-RMS - " +input.c_title, + class: 'input-operator', + inputs: {}, + outputs: { + output_1: { + label: input.c_title, + } + } + } + }; + }); + outputData.forEach(function(output, index) { + var operatorId = 'operator' + (inputData.length + index + 1); + data.operators[operatorId] = { + top: topPosition + index * 80, + left: leftPositionOutput, + properties: { + title: "ALM - " + output.c_issue_status_name, + class: 'output-operator', + inputs: { + input_1: { + label: output.c_issue_status_name, + } + }, + outputs: {} + } + }; + }); + + console.log(data); + updateFlowchartData(data); + // mapping_flow_chart(alm_status_list, arms_state_list); ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// jSuccess("ALM 서버 상태 조회가 완료 되었습니다."); @@ -1119,224 +1188,105 @@ //--- draggable operators //----------------------------------------- +} - //----------------------------------------- - //--- save and load - //--- start - function Flow2Text() { - var data = $flowchart.flowchart('getData'); - $('#flowchart_data').val(JSON.stringify(data, null, 2)); - } - $('#get_data').click(Flow2Text); +function mapping_flow_chart() { - function Text2Flow() { - var data = JSON.parse($('#flowchart_data').val()); - $flowchart.flowchart('setData', data); - } - $('#set_data').click(Text2Flow); + $("#state_flow_chart").flowchart(); - /*global localStorage*/ - function SaveToLocalStorage() { - if (typeof localStorage !== 'object') { - alert('local storage not available'); - return; - } - Flow2Text(); - localStorage.setItem("stgLocalFlowChart", $('#flowchart_data').val()); - } - $('#save_local').click(SaveToLocalStorage); + // 스타일 적용 + $(".input-operator").css("background-color", "#f0ad4e"); + $(".output-operator").css("background-color", "#5bc0de"); - function LoadFromLocalStorage() { - if (typeof localStorage !== 'object') { - alert('local storage not available'); - return; - } - var s = localStorage.getItem("stgLocalFlowChart"); - if (s != null) { - $('#flowchart_data').val(s); - Text2Flow(); - } - else { - alert('local storage empty'); - } - } - $('#load_local').click(LoadFromLocalStorage); - //--- end - //--- save and load - //----------------------------------------- + var $lastEvent = $('#last_event_state_flow_chart'); + var $lastEventContainer = $('#last_event_state_flow_chart_container'); - var defaultFlowchartData = { - operators: { - operator1: { - top: 20, - left: 20, - properties: { - title: 'Operator 1', - inputs: {}, - outputs: { - output_1: { - label: 'Output 1', - } - } - } - }, - operator2: { - top: 80, - left: 300, - properties: { - title: 'Operator 2', - inputs: { - input_1: { - label: 'Input 1', - }, - input_2: { - label: 'Input 2', - }, - }, - outputs: {} - } - }, - }, - links: { - link_1: { - fromOperator: 'operator1', - fromConnector: 'output_1', - toOperator: 'operator2', - toConnector: 'input_2', - }, - } - }; - if (false) console.log('remove lint unused warning', defaultFlowchartData); -} + var $flowchart = $('#state_flow_chart'); + var $container = $("#flow_chart_container"); -function example() { - var data = { - operators: { - operator1: { - top: 20, - left: 20, - properties: { - title: 'Operator 1', - inputs: {}, - outputs: { - output_1: { - label: 'Output 1', - } - } - } - }, - operator2: { - top: 80, - left: 300, - properties: { - title: 'Operator 2', - inputs: { - input_1: { - label: 'Input 1', - } - }, - outputs: {} - } - }, - }, - links: { - link_1: { - fromOperator: 'operator1', - fromConnector: 'output_1', - toOperator: 'operator2', - toConnector: 'input_1', - }, - } - }; + var cx = $flowchart.width() / 2; + var cy = $flowchart.height() / 2; - var $lastEvent = $('#last_event_example_6'); - var $lastEventContainer = $('#last_event_example_container_6'); + // Panzoom initialization... + $flowchart.panzoom(); - var $flowchart = $('#example_6'); + // Centering panzoom + $flowchart.panzoom('pan', -cx + $container.width() / 2, -cy + $container.height() / 2); - function showEvent(txt) { - $lastEvent.text(txt + "\n" + $lastEvent.text()); - $lastEventContainer.effect( "highlight", {color: '#3366ff'}, 500); - } + // Panzoom zoom handling... + var possibleZooms = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3]; + var currentZoom = 2.5; + $flowchart.panzoom('zoom', possibleZooms[currentZoom], { + animate: false + }); - // Apply the plugin on a standard, empty div... - $flowchart.flowchart({ - data: data, - onOperatorSelect: function(operatorId) { - showEvent('Operator "' + operatorId + '" selected. Title: ' + $flowchart.flowchart('getOperatorTitle', operatorId) + '.'); - return true; - }, - onOperatorUnselect: function() { - showEvent('Operator unselected.'); - return true; - }, - onLinkSelect: function(linkId) { - showEvent('Link "' + linkId + '" selected. Main color: ' + $flowchart.flowchart('getLinkMainColor', linkId) + '.'); - return true; - }, - onLinkUnselect: function() { - showEvent('Link unselected.'); - return true; - }, - onOperatorCreate: function(operatorId, operatorData, fullElement) { - showEvent('New operator created. Operator ID: "' + operatorId + '", operator title: "' + operatorData.properties.title + '".'); - return true; - }, - onLinkCreate: function(linkId, linkData) { - showEvent('New link created. Link ID: "' + linkId + '", link color: "' + linkData.color + '".'); - return true; - }, - onOperatorDelete: function(operatorId) { - showEvent('Operator deleted. Operator ID: "' + operatorId + '", operator title: "' + $flowchart.flowchart('getOperatorTitle', operatorId) + '".'); - return true; - }, - onLinkDelete: function(linkId, forced) { - showEvent('Link deleted. Link ID: "' + linkId + '", link color: "' + $flowchart.flowchart('getLinkMainColor', linkId) + '".'); - return true; - }, - onOperatorMoved: function(operatorId, position) { - showEvent('Operator moved. Operator ID: "' + operatorId + ', position: ' + JSON.stringify(position) + '.'); - } + $container.on('mousewheel.focal', function( e ) { + e.preventDefault(); + var delta = (e.delta || e.originalEvent.wheelDelta) || e.originalEvent.detail; + var zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0; + currentZoom = Math.max(0, Math.min(possibleZooms.length - 1, (currentZoom + (zoomOut * 2 - 1)))); + $flowchart.flowchart('setPositionRatio', possibleZooms[currentZoom]); + $flowchart.panzoom('zoom', possibleZooms[currentZoom], { + animate: false, + focal: e }); + }); - $flowchart.siblings('.get_data').click(function() { - var data = $flowchart.flowchart('getData'); - $('#flowchart_data').val(JSON.stringify(data, null, 2)); - }); + function showEvent(txt) { + $lastEvent.text(txt + "\n" + $lastEvent.text()); + $lastEventContainer.effect( "highlight", {color: '#3366ff'}, 500); + } - $flowchart.siblings('.set_data').click(function() { - var data = JSON.parse($('#flowchart_data').val()); - $flowchart.flowchart('setData', data); - }); + // Apply the plugin on a standard, empty div... + $flowchart.flowchart({ + defaultOperatorClass: 'flowchart-operator', + onOperatorSelect: function(operatorId) { + showEvent('Operator "' + operatorId + '" selected. Title: ' + $flowchart.flowchart('getOperatorTitle', operatorId) + '.'); + return true; + }, + onOperatorUnselect: function() { + showEvent('Operator unselected.'); + return true; + }, + onLinkSelect: function(linkId) { + showEvent('Link "' + linkId + '" selected. Main color: ' + $flowchart.flowchart('getLinkMainColor', linkId) + '.'); + return true; + }, + onLinkUnselect: function() { + showEvent('Link unselected.'); + return true; + }, + onOperatorCreate: function(operatorId, operatorData, fullElement) { + showEvent('New operator created. Operator ID: "' + operatorId + '", operator title: "' + operatorData.properties.title + '".'); + return true; + }, + onLinkCreate: function(linkId, linkData) { + showEvent('New link created. Link ID: "' + linkId + '", link color: "' + linkData.color + '".'); + return true; + }, + onOperatorDelete: function(operatorId) { + showEvent('Operator deleted. Operator ID: "' + operatorId + '", operator title: "' + $flowchart.flowchart('getOperatorTitle', operatorId) + '".'); + return true; + }, + onLinkDelete: function(linkId, forced) { + showEvent('Link deleted. Link ID: "' + linkId + '", link color: "' + $flowchart.flowchart('getLinkMainColor', linkId) + '".'); + return true; + }, + onOperatorMoved: function(operatorId, position) { + showEvent('Operator moved. Operator ID: "' + operatorId + ', position: ' + JSON.stringify(position) + '.'); + } + }); - var operatorI = 0; - $flowchart.siblings('.create_operator').click(function() { - var operatorId = 'created_operator_' + operatorI; - var operatorData = { - top: 60, - left: 500, - properties: { - title: 'Operator ' + (operatorI + 3), - inputs: { - input_1: { - label: '.', - } - }, - outputs: { - output_1: { - label: '.', - } - } - } - }; + $flowchart.siblings('.delete_selected_button').click(function() { + $flowchart.flowchart('deleteSelected'); + }); - operatorI++; + $(".flowchart-operator").resizable({handles:"se"}); +} - $flowchart.flowchart('createOperator', operatorId, operatorData); - }); - - $flowchart.siblings('.delete_selected_button').click(function() { - $flowchart.flowchart('deleteSelected'); - }); - $(".flowchart-operator").resizable({handles:"se"}); +function updateFlowchartData(newData) { + // 새로운 데이터 설정 + let $flowchart = $("#state_flow_chart"); + let $container = $("#flow_chart_container"); + $flowchart.flowchart('setData', newData); } \ No newline at end of file Index: arms/js/mapping/gojs_setup.js =================================================================== diff -u --- arms/js/mapping/gojs_setup.js (revision 0) +++ arms/js/mapping/gojs_setup.js (revision 621657e71b30c2be62c5e32e363eeb280de7d0ee) @@ -0,0 +1,395 @@ +var gojs = (function () { + "use strict"; + + var myDiagram; + + function init() { + // Since 2.2 you can also author concise templates with method chaining instead of GraphObject.make + // For details, see https://gojs.net/latest/intro/buildingObjects.html + const $ = go.GraphObject.make; // for conciseness in defining templates + myDiagram = new go.Diagram('myDiagramDiv', { + allowCopy: false, + layout: $(go.LayeredDigraphLayout, { + setsPortSpots: false, // Links already know their fromSpot and toSpot + columnSpacing: 5, + isInitial: false, + isOngoing: false, + }), + validCycle: go.CycleMode.NotDirected, + 'undoManager.isEnabled': true, + }); + + // when the document is modified, add a "*" to the title and enable the "Save" button + myDiagram.addDiagramListener('Modified', (e) => { + const button = document.getElementById('SaveButton'); + if (button) button.disabled = !myDiagram.isModified; + const idx = document.title.indexOf('*'); + if (myDiagram.isModified) { + if (idx < 0) document.title += '*'; + } else { + if (idx >= 0) document.title = document.title.slice(0, idx); + } + }); + + const graygrad = $(go.Brush, 'Linear', { 0: 'white', 0.1: 'whitesmoke', 0.9: 'whitesmoke', 1: 'lightgray' }); + + myDiagram.nodeTemplate = // the default node template + $(go.Node, + 'Spot', + { selectionAdorned: false, textEditable: true, locationObjectName: 'BODY' }, + new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify), + // the main body consists of a Rectangle surrounding the text + $(go.Panel, + 'Auto', + { name: 'BODY' }, + $(go.Shape, + 'Rectangle', + { fill: graygrad, stroke: 'gray', minSize: new go.Size(120, 21) }, + new go.Binding('fill', 'isSelected', (s) => (s ? 'dodgerblue' : graygrad)).ofObject() + ), + $(go.TextBlock, + { + stroke: 'black', + font: '12px sans-serif', + editable: true, + margin: new go.Margin(3, 3 + 11, 3, 3 + 4), + alignment: go.Spot.Left, + }, + new go.Binding('text').makeTwoWay() + ) + ), + // output port + $(go.Panel, + 'Auto', + { alignment: go.Spot.Right, portId: 'from', fromLinkable: true, cursor: 'pointer', click: addNodeAndLink }, + $(go.Shape, 'Circle', { width: 22, height: 22, fill: 'white', stroke: 'dodgerblue', strokeWidth: 3 }), + $(go.Shape, 'PlusLine', { width: 11, height: 11, fill: null, stroke: 'dodgerblue', strokeWidth: 3 }) + ), + // input port + $(go.Panel, + 'Auto', + { alignment: go.Spot.Left, portId: 'to', toLinkable: true }, + $(go.Shape, 'Circle', { width: 8, height: 8, fill: 'white', stroke: 'gray' }), + $(go.Shape, 'Circle', { width: 4, height: 4, fill: 'dodgerblue', stroke: null }) + ) + ); + + myDiagram.nodeTemplate.contextMenu = $('ContextMenu', + $('ContextMenuButton', + $(go.TextBlock, 'Rename'), + { click: (e, obj) => e.diagram.commandHandler.editTextBlock() }, + new go.Binding('visible', '', (o) => o.diagram && o.diagram.commandHandler.canEditTextBlock()).ofObject() + ), + // add one for Editing... + $('ContextMenuButton', + $(go.TextBlock, 'Delete'), + { click: (e, obj) => e.diagram.commandHandler.deleteSelection() }, + new go.Binding('visible', '', (o) => o.diagram && o.diagram.commandHandler.canDeleteSelection()).ofObject() + ) + ); + + myDiagram.nodeTemplateMap.add( + 'Loading', + $(go.Node, + 'Spot', + { selectionAdorned: false, textEditable: true, locationObjectName: 'BODY' }, + new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify), + // the main body consists of a Rectangle surrounding the text + $(go.Panel, + 'Auto', + { name: 'BODY' }, + $(go.Shape, + 'Rectangle', + { fill: graygrad, stroke: 'gray', minSize: new go.Size(120, 21) }, + new go.Binding('fill', 'isSelected', (s) => (s ? 'dodgerblue' : graygrad)).ofObject() + ), + $(go.TextBlock, + { + stroke: 'black', + font: '12px sans-serif', + editable: true, + margin: new go.Margin(3, 3 + 11, 3, 3 + 4), + alignment: go.Spot.Left, + }, + new go.Binding('text', 'text') + ) + ), + // output port + $(go.Panel, + 'Auto', + { alignment: go.Spot.Right, portId: 'from', fromLinkable: true, click: addNodeAndLink }, + $(go.Shape, 'Circle', { width: 22, height: 22, fill: 'white', stroke: 'dodgerblue', strokeWidth: 3 }), + $(go.Shape, 'PlusLine', { width: 11, height: 11, fill: null, stroke: 'dodgerblue', strokeWidth: 3 }) + ) + ) + ); + + myDiagram.nodeTemplateMap.add( + 'End', + $(go.Node, + 'Spot', + { selectionAdorned: false, textEditable: true, locationObjectName: 'BODY' }, + new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify), + // the main body consists of a Rectangle surrounding the text + $(go.Panel, + 'Auto', + { name: 'BODY' }, + $(go.Shape, + 'Rectangle', + { fill: graygrad, stroke: 'gray', minSize: new go.Size(120, 21) }, + new go.Binding('fill', 'isSelected', (s) => (s ? 'dodgerblue' : graygrad)).ofObject() + ), + $(go.TextBlock, + { + stroke: 'black', + font: '12px sans-serif', + editable: true, + margin: new go.Margin(3, 3 + 11, 3, 3 + 4), + alignment: go.Spot.Left, + }, + new go.Binding('text', 'text') + ) + ), + // input port + $(go.Panel, + 'Auto', + { alignment: go.Spot.Left, portId: 'to', toLinkable: true }, + $(go.Shape, 'Circle', { width: 8, height: 8, fill: 'white', stroke: 'gray' }), + $(go.Shape, 'Circle', { width: 4, height: 4, fill: 'dodgerblue', stroke: null }) + ) + ) + ); + + // dropping a node on this special node will cause the selection to be deleted; + // linking or relinking to this special node will cause the link to be deleted + myDiagram.nodeTemplateMap.add( + 'Recycle', + $(go.Node, + 'Auto', + { + portId: 'to', + toLinkable: true, + deletable: false, + layerName: 'Background', + locationSpot: go.Spot.Center, + }, + new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify), + { + dragComputation: (node, pt, gridpt) => pt, + mouseDrop: (e, obj) => e.diagram.commandHandler.deleteSelection(), + }, + $(go.Shape, { fill: 'lightgray', stroke: 'gray' }), + $(go.TextBlock, 'Drop Here\nTo Delete', { margin: 5, textAlign: 'center' }) + ) + ); + + // this is a click event handler that adds a node and a link to the diagram, + // connecting with the node on which the click occurred + function addNodeAndLink(e, obj) { + const fromNode = obj.part; + const diagram = fromNode.diagram; + diagram.startTransaction('Add State'); + // get the node data for which the user clicked the button + const fromData = fromNode.data; + // create a new "State" data object, positioned off to the right of the fromNode + const p = fromNode.location.copy(); + p.x += diagram.toolManager.draggingTool.gridSnapCellSize.width; + const toData = { + text: 'new', + loc: go.Point.stringify(p), + }; + // add the new node data to the model + const model = diagram.model; + model.addNodeData(toData); + // create a link data from the old node data to the new node data + const linkdata = { + from: model.getKeyForNodeData(fromData), + to: model.getKeyForNodeData(toData), + }; + // and add the link data to the model + model.addLinkData(linkdata); + // select the new Node + const newnode = diagram.findNodeForData(toData); + diagram.select(newnode); + // snap the new node to a valid location + newnode.location = diagram.toolManager.draggingTool.computeMove(newnode, p); + // then account for any overlap + shiftNodesToEmptySpaces(); + diagram.commitTransaction('Add State'); + } + + // Highlight ports when they are targets for linking or relinking. + let OldTarget = null; // remember the last highlit port + function highlight(port) { + if (OldTarget !== port) { + lowlight(); // remove highlight from any old port + OldTarget = port; + port.scale = 1.3; // highlight by enlarging + } + } + function lowlight() { + // remove any highlight + if (OldTarget) { + OldTarget.scale = 1.0; + OldTarget = null; + } + } + + // Connecting a link with the Recycle node removes the link + myDiagram.addDiagramListener('LinkDrawn', (e) => { + const link = e.subject; + if (link.toNode.category === 'Recycle') myDiagram.remove(link); + lowlight(); + }); + myDiagram.addDiagramListener('LinkRelinked', (e) => { + const link = e.subject; + if (link.toNode.category === 'Recycle') myDiagram.remove(link); + lowlight(); + }); + + myDiagram.linkTemplate = $(go.Link, + { selectionAdorned: false, fromPortId: 'from', toPortId: 'to', relinkableTo: true }, + $(go.Shape, + { stroke: 'gray', strokeWidth: 2 }, + { + mouseEnter: (e, obj) => { + obj.strokeWidth = 5; + obj.stroke = 'dodgerblue'; + }, + mouseLeave: (e, obj) => { + obj.strokeWidth = 2; + obj.stroke = 'gray'; + }, + } + ) + ); + + function commonLinkingToolInit(tool) { + // the temporary link drawn during a link drawing operation (LinkingTool) is thick and blue + tool.temporaryLink = $(go.Link, { layerName: 'Tool' }, $(go.Shape, { stroke: 'dodgerblue', strokeWidth: 5 })); + + // change the standard proposed ports feedback from blue rectangles to transparent circles + tool.temporaryFromPort.figure = 'Circle'; + tool.temporaryFromPort.stroke = null; + tool.temporaryFromPort.strokeWidth = 0; + tool.temporaryToPort.figure = 'Circle'; + tool.temporaryToPort.stroke = null; + tool.temporaryToPort.strokeWidth = 0; + + // provide customized visual feedback as ports are targeted or not + tool.portTargeted = (realnode, realport, tempnode, tempport, toend) => { + if (realport === null) { + // no valid port nearby + lowlight(); + } else if (toend) { + highlight(realport); + } + }; + } + + const ltool = myDiagram.toolManager.linkingTool; + commonLinkingToolInit(ltool); + // do not allow links to be drawn starting at the "to" port + ltool.direction = go.LinkingDirection.ForwardsOnly; + + const rtool = myDiagram.toolManager.relinkingTool; + commonLinkingToolInit(rtool); + // change the standard relink handle to be a shape that takes the shape of the link + rtool.toHandleArchetype = $(go.Shape, { isPanelMain: true, fill: null, stroke: 'dodgerblue', strokeWidth: 5 }); + + // use a special DraggingTool to cause the dragging of a Link to start relinking it + myDiagram.toolManager.draggingTool = new DragLinkingTool(); + + // detect when dropped onto an occupied cell + myDiagram.addDiagramListener('SelectionMoved', shiftNodesToEmptySpaces); + + function shiftNodesToEmptySpaces() { + myDiagram.selection.each((node) => { + if (!(node instanceof go.Node)) return; + // look for Parts overlapping the node + while (true) { + const exist = myDiagram + .findObjectsIn( + node.actualBounds, + // only consider Parts + (obj) => obj.part, + // ignore Links and the dropped node itself + (part) => part instanceof go.Node && part !== node, + // check for any overlap, not complete containment + true + ) + .first(); + if (exist === null) break; + // try shifting down beyond the existing node to see if there's empty space + node.moveTo(node.actualBounds.x, exist.actualBounds.bottom + 10); + } + }); + } + + // prevent nodes from being dragged to the left of where the layout placed them + myDiagram.addDiagramListener('LayoutCompleted', (e) => { + myDiagram.nodes.each((node) => { + if (node.category === 'Recycle') return; + node.minLocation = new go.Point(node.location.x, -Infinity); + }); + }); + + load(); // load initial diagram from the mySavedModel textarea + } + + function save() { + document.getElementById('mySavedModel').value = myDiagram.model.toJson(); + myDiagram.isModified = false; + } + + function load() { + myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').value); + // if any nodes don't have a real location, explicitly do a layout + if (myDiagram.nodes.any((n) => !n.location.isReal())) layout(); + } + + function layout() { + myDiagram.layoutDiagram(true); + } + + // Define a custom tool that changes a drag operation on a Link to a relinking operation, + // but that operates like a normal DraggingTool otherwise. + class DragLinkingTool extends go.DraggingTool { + constructor() { + super(); + this.isGridSnapEnabled = true; + this.isGridSnapRealtime = false; + this.gridSnapCellSize = new go.Size(182, 1); + this.gridSnapOrigin = new go.Point(5.5, 0); + } + + // Handle dragging a link specially -- by starting the RelinkingTool on that Link + doActivate() { + const diagram = this.diagram; + if (diagram === null) return; + this.standardMouseSelect(); + const main = this.currentPart; // this is set by the standardMouseSelect + if (main instanceof go.Link) { + // maybe start relinking instead of dragging + const relinkingtool = diagram.toolManager.relinkingTool; + // tell the RelinkingTool to work on this Link, not what is under the mouse + relinkingtool.originalLink = main; + // start the RelinkingTool + diagram.currentTool = relinkingtool; + // can activate it right now, because it already has the originalLink to reconnect + relinkingtool.doActivate(); + relinkingtool.doMouseMove(); + } else { + super.doActivate(); + } + } + } + // end DragLinkingTool + + + + return { + init, save, load, layout + } + +})(); //즉시실행 함수 \ No newline at end of file