Compare commits
	
		
			5 Commits
		
	
	
		
			65d391e050
			...
			da9cbbe8a5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| da9cbbe8a5 | |||
| ccbe62c0d9 | |||
| b1916fe55d | |||
| eb7fdeddac | |||
| 865fdce8f5 | 
							
								
								
									
										16
									
								
								.devcontainer/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.devcontainer/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.163.1/containers/javascript-node/.devcontainer/base.Dockerfile | ||||||
|  | 
 | ||||||
|  | # [Choice] Node.js version: 14, 12, 10 | ||||||
|  | ARG VARIANT="14-buster" | ||||||
|  | FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} | ||||||
|  | 
 | ||||||
|  | # [Optional] Uncomment this section to install additional OS packages. | ||||||
|  | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ | ||||||
|  | #     && apt-get -y install --no-install-recommends <your-package-list-here> | ||||||
|  | 
 | ||||||
|  | # [Optional] Uncomment if you want to install an additional version of node using nvm | ||||||
|  | # ARG EXTRA_NODE_VERSION=10 | ||||||
|  | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" | ||||||
|  | 
 | ||||||
|  | # [Optional] Uncomment if you want to install more global node modules | ||||||
|  | # RUN su node -c "npm install -g <your-package-list-here>" | ||||||
							
								
								
									
										29
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: | ||||||
|  | // https://github.com/microsoft/vscode-dev-containers/tree/v0.163.1/containers/javascript-node | ||||||
|  | { | ||||||
|  | 	"name": "Node.js", | ||||||
|  | 	"build": { | ||||||
|  | 		"dockerfile": "Dockerfile", | ||||||
|  | 		// Update 'VARIANT' to pick a Node version: 10, 12, 14 | ||||||
|  | 		"args": { "VARIANT": "14" } | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	// Set *default* container specific settings.json values on container create. | ||||||
|  | 	"settings": {  | ||||||
|  | 		"terminal.integrated.shell.linux": "/bin/bash" | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	// Add the IDs of extensions you want installed when the container is created. | ||||||
|  | 	"extensions": [ | ||||||
|  | 		"dbaeumer.vscode-eslint" | ||||||
|  | 	], | ||||||
|  | 
 | ||||||
|  | 	// Use 'forwardPorts' to make a list of ports inside the container available locally. | ||||||
|  | 	// "forwardPorts": [], | ||||||
|  | 
 | ||||||
|  | 	// Use 'postCreateCommand' to run commands after the container is created. | ||||||
|  | 	// "postCreateCommand": "yarn install", | ||||||
|  | 
 | ||||||
|  | 	// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. | ||||||
|  | 	"remoteUser": "node" | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | { | ||||||
|  |     // Use IntelliSense to learn about possible attributes. | ||||||
|  |     // Hover to view descriptions of existing attributes. | ||||||
|  |     // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||||||
|  |     "version": "0.2.0", | ||||||
|  |     "configurations": [ | ||||||
|  |         { | ||||||
|  |             "type": "pwa-chrome", | ||||||
|  |             "request": "launch", | ||||||
|  |             "name": "Launch Chrome against localhost", | ||||||
|  |             "url": "http://localhost:8080/index.html", | ||||||
|  |             "webRoot": "${workspaceFolder}", | ||||||
|  |             "trace": true | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | { | ||||||
|  |     "files.eol": "\n", | ||||||
|  |       "editor.tabSize": 2, | ||||||
|  |       "editor.formatOnPaste": false, | ||||||
|  |       "editor.formatOnSave": true, | ||||||
|  |       "editor.formatOnType": true, | ||||||
|  |       "[javascript]": { | ||||||
|  |         "editor.defaultFormatter": "esbenp.prettier-vscode" | ||||||
|  |       }, | ||||||
|  |       "[typescript]": { | ||||||
|  |         "editor.defaultFormatter": "esbenp.prettier-vscode" | ||||||
|  |       }, | ||||||
|  |       "files.trimTrailingWhitespace": true | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | { | ||||||
|  |   "version": "2.0.0", | ||||||
|  |   "tasks": [ | ||||||
|  |     { | ||||||
|  |       "label": "npm: build", | ||||||
|  |       "type": "npm", | ||||||
|  |       "script": "build", | ||||||
|  |       "problemMatcher": [] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "label": "npm: watch", | ||||||
|  |       "type": "npm", | ||||||
|  |       "script": "watch", | ||||||
|  |       "problemMatcher": [], | ||||||
|  |       "presentation": { | ||||||
|  |         "panel": "shared", | ||||||
|  |         "group": "test" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "label": "npm: serve", | ||||||
|  |       "type": "npm", | ||||||
|  |       "script": "serve", | ||||||
|  |       "problemMatcher": [], | ||||||
|  |       "presentation": { | ||||||
|  |         "panel": "shared", | ||||||
|  |         "group": "test" | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										447
									
								
								DragDropTouch.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										447
									
								
								DragDropTouch.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,447 @@ | |||||||
|  | var DragDropTouch; | ||||||
|  | (function (DragDropTouch_1) { | ||||||
|  |     'use strict'; | ||||||
|  |     /** | ||||||
|  |      * Object used to hold the data that is being dragged during drag and drop operations. | ||||||
|  |      * | ||||||
|  |      * It may hold one or more data items of different types. For more information about | ||||||
|  |      * drag and drop operations and data transfer objects, see | ||||||
|  |      * <a href="https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer">HTML Drag and Drop API</a>. | ||||||
|  |      * | ||||||
|  |      * This object is created automatically by the @see:DragDropTouch singleton and is | ||||||
|  |      * accessible through the @see:dataTransfer property of all drag events. | ||||||
|  |      */ | ||||||
|  |     var DataTransfer = (function () { | ||||||
|  |         function DataTransfer() { | ||||||
|  |             this._dropEffect = 'move'; | ||||||
|  |             this._effectAllowed = 'all'; | ||||||
|  |             this._data = {}; | ||||||
|  |         } | ||||||
|  |         Object.defineProperty(DataTransfer.prototype, "dropEffect", { | ||||||
|  |             /** | ||||||
|  |              * Gets or sets the type of drag-and-drop operation currently selected. | ||||||
|  |              * The value must be 'none',  'copy',  'link', or 'move'. | ||||||
|  |              */ | ||||||
|  |             get: function () { | ||||||
|  |                 return this._dropEffect; | ||||||
|  |             }, | ||||||
|  |             set: function (value) { | ||||||
|  |                 this._dropEffect = value; | ||||||
|  |             }, | ||||||
|  |             enumerable: true, | ||||||
|  |             configurable: true | ||||||
|  |         }); | ||||||
|  |         Object.defineProperty(DataTransfer.prototype, "effectAllowed", { | ||||||
|  |             /** | ||||||
|  |              * Gets or sets the types of operations that are possible. | ||||||
|  |              * Must be one of 'none', 'copy', 'copyLink', 'copyMove', 'link', | ||||||
|  |              * 'linkMove', 'move', 'all' or 'uninitialized'. | ||||||
|  |              */ | ||||||
|  |             get: function () { | ||||||
|  |                 return this._effectAllowed; | ||||||
|  |             }, | ||||||
|  |             set: function (value) { | ||||||
|  |                 this._effectAllowed = value; | ||||||
|  |             }, | ||||||
|  |             enumerable: true, | ||||||
|  |             configurable: true | ||||||
|  |         }); | ||||||
|  |         Object.defineProperty(DataTransfer.prototype, "types", { | ||||||
|  |             /** | ||||||
|  |              * Gets an array of strings giving the formats that were set in the @see:dragstart event. | ||||||
|  |              */ | ||||||
|  |             get: function () { | ||||||
|  |                 return Object.keys(this._data); | ||||||
|  |             }, | ||||||
|  |             enumerable: true, | ||||||
|  |             configurable: true | ||||||
|  |         }); | ||||||
|  |         /** | ||||||
|  |          * Removes the data associated with a given type. | ||||||
|  |          * | ||||||
|  |          * The type argument is optional. If the type is empty or not specified, the data | ||||||
|  |          * associated with all types is removed. If data for the specified type does not exist, | ||||||
|  |          * or the data transfer contains no data, this method will have no effect. | ||||||
|  |          * | ||||||
|  |          * @param type Type of data to remove. | ||||||
|  |          */ | ||||||
|  |         DataTransfer.prototype.clearData = function (type) { | ||||||
|  |             if (type != null) { | ||||||
|  |                 delete this._data[type]; | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 this._data = null; | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         /** | ||||||
|  |          * Retrieves the data for a given type, or an empty string if data for that type does | ||||||
|  |          * not exist or the data transfer contains no data. | ||||||
|  |          * | ||||||
|  |          * @param type Type of data to retrieve. | ||||||
|  |          */ | ||||||
|  |         DataTransfer.prototype.getData = function (type) { | ||||||
|  |             return this._data[type] || ''; | ||||||
|  |         }; | ||||||
|  |         /** | ||||||
|  |          * Set the data for a given type. | ||||||
|  |          * | ||||||
|  |          * For a list of recommended drag types, please see | ||||||
|  |          * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Recommended_Drag_Types.
 | ||||||
|  |          * | ||||||
|  |          * @param type Type of data to add. | ||||||
|  |          * @param value Data to add. | ||||||
|  |          */ | ||||||
|  |         DataTransfer.prototype.setData = function (type, value) { | ||||||
|  |             this._data[type] = value; | ||||||
|  |         }; | ||||||
|  |         /** | ||||||
|  |          * Set the image to be used for dragging if a custom one is desired. | ||||||
|  |          * | ||||||
|  |          * @param img An image element to use as the drag feedback image. | ||||||
|  |          * @param offsetX The horizontal offset within the image. | ||||||
|  |          * @param offsetY The vertical offset within the image. | ||||||
|  |          */ | ||||||
|  |         DataTransfer.prototype.setDragImage = function (img, offsetX, offsetY) { | ||||||
|  |             var ddt = DragDropTouch._instance; | ||||||
|  |             ddt._imgCustom = img; | ||||||
|  |             ddt._imgOffset = { x: offsetX, y: offsetY }; | ||||||
|  |         }; | ||||||
|  |         return DataTransfer; | ||||||
|  |     }()); | ||||||
|  |     DragDropTouch_1.DataTransfer = DataTransfer; | ||||||
|  |     /** | ||||||
|  |      * Defines a class that adds support for touch-based HTML5 drag/drop operations. | ||||||
|  |      * | ||||||
|  |      * The @see:DragDropTouch class listens to touch events and raises the | ||||||
|  |      * appropriate HTML5 drag/drop events as if the events had been caused | ||||||
|  |      * by mouse actions. | ||||||
|  |      * | ||||||
|  |      * The purpose of this class is to enable using existing, standard HTML5 | ||||||
|  |      * drag/drop code on mobile devices running IOS or Android. | ||||||
|  |      * | ||||||
|  |      * To use, include the DragDropTouch.js file on the page. The class will | ||||||
|  |      * automatically start monitoring touch events and will raise the HTML5 | ||||||
|  |      * drag drop events (dragstart, dragenter, dragleave, drop, dragend) which | ||||||
|  |      * should be handled by the application. | ||||||
|  |      * | ||||||
|  |      * For details and examples on HTML drag and drop, see | ||||||
|  |      * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Drag_operations.
 | ||||||
|  |      */ | ||||||
|  |     var DragDropTouch = (function () { | ||||||
|  |         /** | ||||||
|  |          * Initializes the single instance of the @see:DragDropTouch class. | ||||||
|  |          */ | ||||||
|  |         function DragDropTouch() { | ||||||
|  |             this._lastClick = 0; | ||||||
|  |             // enforce singleton pattern
 | ||||||
|  |             if (DragDropTouch._instance) { | ||||||
|  |                 throw 'DragDropTouch instance already created.'; | ||||||
|  |             } | ||||||
|  |             // detect passive event support
 | ||||||
|  |             // https://github.com/Modernizr/Modernizr/issues/1894
 | ||||||
|  |             var supportsPassive = false; | ||||||
|  |             document.addEventListener('test', function () { }, { | ||||||
|  |                 get passive() { | ||||||
|  |                     supportsPassive = true; | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |             // listen to touch events
 | ||||||
|  |             if ('ontouchstart' in document) { | ||||||
|  |                 var d = document, ts = this._touchstart.bind(this), tm = this._touchmove.bind(this), te = this._touchend.bind(this), opt = supportsPassive ? { passive: false, capture: false } : false; | ||||||
|  |                 d.addEventListener('touchstart', ts, opt); | ||||||
|  |                 d.addEventListener('touchmove', tm, opt); | ||||||
|  |                 d.addEventListener('touchend', te); | ||||||
|  |                 d.addEventListener('touchcancel', te); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         /** | ||||||
|  |          * Gets a reference to the @see:DragDropTouch singleton. | ||||||
|  |          */ | ||||||
|  |         DragDropTouch.getInstance = function () { | ||||||
|  |             return DragDropTouch._instance; | ||||||
|  |         }; | ||||||
|  |         // ** event handlers
 | ||||||
|  |         DragDropTouch.prototype._touchstart = function (e) { | ||||||
|  |             var _this = this; | ||||||
|  |             if (this._shouldHandle(e)) { | ||||||
|  |                 // raise double-click and prevent zooming
 | ||||||
|  |                 if (Date.now() - this._lastClick < DragDropTouch._DBLCLICK) { | ||||||
|  |                     if (this._dispatchEvent(e, 'dblclick', e.target)) { | ||||||
|  |                         e.preventDefault(); | ||||||
|  |                         this._reset(); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 // clear all variables
 | ||||||
|  |                 this._reset(); | ||||||
|  |                 // get nearest draggable element
 | ||||||
|  |                 var src = this._closestDraggable(e.target); | ||||||
|  |                 if (src) { | ||||||
|  |                     // give caller a chance to handle the hover/move events
 | ||||||
|  |                     if (!this._dispatchEvent(e, 'mousemove', e.target) && | ||||||
|  |                         !this._dispatchEvent(e, 'mousedown', e.target)) { | ||||||
|  |                         // get ready to start dragging
 | ||||||
|  |                         this._dragSource = src; | ||||||
|  |                         this._ptDown = this._getPoint(e); | ||||||
|  |                         this._lastTouch = e; | ||||||
|  |                         e.preventDefault(); | ||||||
|  |                         // show context menu if the user hasn't started dragging after a while
 | ||||||
|  |                         setTimeout(function () { | ||||||
|  |                             if (_this._dragSource == src && _this._img == null) { | ||||||
|  |                                 if (_this._dispatchEvent(e, 'contextmenu', src)) { | ||||||
|  |                                     _this._reset(); | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         }, DragDropTouch._CTXMENU); | ||||||
|  |                         if (DragDropTouch._ISPRESSHOLDMODE) { | ||||||
|  |                             this._pressHoldInterval = setTimeout(function () { | ||||||
|  |                                 _this._isDragEnabled = true; | ||||||
|  |                                 _this._touchmove(e); | ||||||
|  |                             }, DragDropTouch._PRESSHOLDAWAIT); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         DragDropTouch.prototype._touchmove = function (e) { | ||||||
|  |             if (this._shouldCancelPressHoldMove(e)) { | ||||||
|  |               this._reset(); | ||||||
|  |               return; | ||||||
|  |             } | ||||||
|  |             if (this._shouldHandleMove(e) || this._shouldHandlePressHoldMove(e)) { | ||||||
|  |                 // see if target wants to handle move
 | ||||||
|  |                 var target = this._getTarget(e); | ||||||
|  |                 if (this._dispatchEvent(e, 'mousemove', target)) { | ||||||
|  |                     this._lastTouch = e; | ||||||
|  |                     e.preventDefault(); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 // start dragging
 | ||||||
|  |                 if (this._dragSource && !this._img && this._shouldStartDragging(e)) { | ||||||
|  |                     this._dispatchEvent(e, 'dragstart', this._dragSource); | ||||||
|  |                     this._createImage(e); | ||||||
|  |                     this._dispatchEvent(e, 'dragenter', target); | ||||||
|  |                 } | ||||||
|  |                 // continue dragging
 | ||||||
|  |                 if (this._img) { | ||||||
|  |                     this._lastTouch = e; | ||||||
|  |                     e.preventDefault(); // prevent scrolling
 | ||||||
|  |                     if (target != this._lastTarget) { | ||||||
|  |                         this._dispatchEvent(this._lastTouch, 'dragleave', this._lastTarget); | ||||||
|  |                         this._dispatchEvent(e, 'dragenter', target); | ||||||
|  |                         this._lastTarget = target; | ||||||
|  |                     } | ||||||
|  |                     this._moveImage(e); | ||||||
|  |                     this._isDropZone = this._dispatchEvent(e, 'dragover', target); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         DragDropTouch.prototype._touchend = function (e) { | ||||||
|  |             if (this._shouldHandle(e)) { | ||||||
|  |                 // see if target wants to handle up
 | ||||||
|  |                 if (this._dispatchEvent(this._lastTouch, 'mouseup', e.target)) { | ||||||
|  |                     e.preventDefault(); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 // user clicked the element but didn't drag, so clear the source and simulate a click
 | ||||||
|  |                 if (!this._img) { | ||||||
|  |                     this._dragSource = null; | ||||||
|  |                     this._dispatchEvent(this._lastTouch, 'click', e.target); | ||||||
|  |                     this._lastClick = Date.now(); | ||||||
|  |                 } | ||||||
|  |                 // finish dragging
 | ||||||
|  |                 this._destroyImage(); | ||||||
|  |                 if (this._dragSource) { | ||||||
|  |                     if (e.type.indexOf('cancel') < 0 && this._isDropZone) { | ||||||
|  |                         this._dispatchEvent(this._lastTouch, 'drop', this._lastTarget); | ||||||
|  |                     } | ||||||
|  |                     this._dispatchEvent(this._lastTouch, 'dragend', this._dragSource); | ||||||
|  |                     this._reset(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         // ** utilities
 | ||||||
|  |         // ignore events that have been handled or that involve more than one touch
 | ||||||
|  |         DragDropTouch.prototype._shouldHandle = function (e) { | ||||||
|  |             return e && | ||||||
|  |                 !e.defaultPrevented && | ||||||
|  |                 e.touches && e.touches.length < 2; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         // use regular condition outside of press & hold mode
 | ||||||
|  |         DragDropTouch.prototype._shouldHandleMove = function (e) { | ||||||
|  |           return !DragDropTouch._ISPRESSHOLDMODE && this._shouldHandle(e); | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         // allow to handle moves that involve many touches for press & hold
 | ||||||
|  |         DragDropTouch.prototype._shouldHandlePressHoldMove = function (e) { | ||||||
|  |           return DragDropTouch._ISPRESSHOLDMODE && | ||||||
|  |               this._isDragEnabled && e && e.touches && e.touches.length; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         // reset data if user drags without pressing & holding
 | ||||||
|  |         DragDropTouch.prototype._shouldCancelPressHoldMove = function (e) { | ||||||
|  |           return DragDropTouch._ISPRESSHOLDMODE && !this._isDragEnabled && | ||||||
|  |               this._getDelta(e) > DragDropTouch._PRESSHOLDMARGIN; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         // start dragging when specified delta is detected
 | ||||||
|  |         DragDropTouch.prototype._shouldStartDragging = function (e) { | ||||||
|  |             var delta = this._getDelta(e); | ||||||
|  |             return delta > DragDropTouch._THRESHOLD || | ||||||
|  |                 (DragDropTouch._ISPRESSHOLDMODE && delta >= DragDropTouch._PRESSHOLDTHRESHOLD); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // clear all members
 | ||||||
|  |         DragDropTouch.prototype._reset = function () { | ||||||
|  |             this._destroyImage(); | ||||||
|  |             this._dragSource = null; | ||||||
|  |             this._lastTouch = null; | ||||||
|  |             this._lastTarget = null; | ||||||
|  |             this._ptDown = null; | ||||||
|  |             this._isDragEnabled = false; | ||||||
|  |             this._isDropZone = false; | ||||||
|  |             this._dataTransfer = new DataTransfer(); | ||||||
|  |             clearInterval(this._pressHoldInterval); | ||||||
|  |         }; | ||||||
|  |         // get point for a touch event
 | ||||||
|  |         DragDropTouch.prototype._getPoint = function (e, page) { | ||||||
|  |             if (e && e.touches) { | ||||||
|  |                 e = e.touches[0]; | ||||||
|  |             } | ||||||
|  |             return { x: page ? e.pageX : e.clientX, y: page ? e.pageY : e.clientY }; | ||||||
|  |         }; | ||||||
|  |         // get distance between the current touch event and the first one
 | ||||||
|  |         DragDropTouch.prototype._getDelta = function (e) { | ||||||
|  |             if (DragDropTouch._ISPRESSHOLDMODE && !this._ptDown) { return 0; } | ||||||
|  |             var p = this._getPoint(e); | ||||||
|  |             return Math.abs(p.x - this._ptDown.x) + Math.abs(p.y - this._ptDown.y); | ||||||
|  |         }; | ||||||
|  |         // get the element at a given touch event
 | ||||||
|  |         DragDropTouch.prototype._getTarget = function (e) { | ||||||
|  |             var pt = this._getPoint(e), el = document.elementFromPoint(pt.x, pt.y); | ||||||
|  |             while (el && getComputedStyle(el).pointerEvents == 'none') { | ||||||
|  |                 el = el.parentElement; | ||||||
|  |             } | ||||||
|  |             return el; | ||||||
|  |         }; | ||||||
|  |         // create drag image from source element
 | ||||||
|  |         DragDropTouch.prototype._createImage = function (e) { | ||||||
|  |             // just in case...
 | ||||||
|  |             if (this._img) { | ||||||
|  |                 this._destroyImage(); | ||||||
|  |             } | ||||||
|  |             // create drag image from custom element or drag source
 | ||||||
|  |             var src = this._imgCustom || this._dragSource; | ||||||
|  |             this._img = src.cloneNode(true); | ||||||
|  |             this._copyStyle(src, this._img); | ||||||
|  |             this._img.style.top = this._img.style.left = '-9999px'; | ||||||
|  |             // if creating from drag source, apply offset and opacity
 | ||||||
|  |             if (!this._imgCustom) { | ||||||
|  |                 var rc = src.getBoundingClientRect(), pt = this._getPoint(e); | ||||||
|  |                 this._imgOffset = { x: pt.x - rc.left, y: pt.y - rc.top }; | ||||||
|  |                 this._img.style.opacity = DragDropTouch._OPACITY.toString(); | ||||||
|  |             } | ||||||
|  |             // add image to document
 | ||||||
|  |             this._moveImage(e); | ||||||
|  |             document.body.appendChild(this._img); | ||||||
|  |         }; | ||||||
|  |         // dispose of drag image element
 | ||||||
|  |         DragDropTouch.prototype._destroyImage = function () { | ||||||
|  |             if (this._img && this._img.parentElement) { | ||||||
|  |                 this._img.parentElement.removeChild(this._img); | ||||||
|  |             } | ||||||
|  |             this._img = null; | ||||||
|  |             this._imgCustom = null; | ||||||
|  |         }; | ||||||
|  |         // move the drag image element
 | ||||||
|  |         DragDropTouch.prototype._moveImage = function (e) { | ||||||
|  |             var _this = this; | ||||||
|  |             requestAnimationFrame(function () { | ||||||
|  |                 if (_this._img) { | ||||||
|  |                     var pt = _this._getPoint(e, true), s = _this._img.style; | ||||||
|  |                     s.position = 'absolute'; | ||||||
|  |                     s.pointerEvents = 'none'; | ||||||
|  |                     s.zIndex = '999999'; | ||||||
|  |                     s.left = Math.round(pt.x - _this._imgOffset.x) + 'px'; | ||||||
|  |                     s.top = Math.round(pt.y - _this._imgOffset.y) + 'px'; | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }; | ||||||
|  |         // copy properties from an object to another
 | ||||||
|  |         DragDropTouch.prototype._copyProps = function (dst, src, props) { | ||||||
|  |             for (var i = 0; i < props.length; i++) { | ||||||
|  |                 var p = props[i]; | ||||||
|  |                 dst[p] = src[p]; | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         DragDropTouch.prototype._copyStyle = function (src, dst) { | ||||||
|  |             // remove potentially troublesome attributes
 | ||||||
|  |             DragDropTouch._rmvAtts.forEach(function (att) { | ||||||
|  |                 dst.removeAttribute(att); | ||||||
|  |             }); | ||||||
|  |             // copy canvas content
 | ||||||
|  |             if (src instanceof HTMLCanvasElement) { | ||||||
|  |                 var cSrc = src, cDst = dst; | ||||||
|  |                 cDst.width = cSrc.width; | ||||||
|  |                 cDst.height = cSrc.height; | ||||||
|  |                 cDst.getContext('2d').drawImage(cSrc, 0, 0); | ||||||
|  |             } | ||||||
|  |             // copy style (without transitions)
 | ||||||
|  |             var cs = getComputedStyle(src); | ||||||
|  |             for (var i = 0; i < cs.length; i++) { | ||||||
|  |                 var key = cs[i]; | ||||||
|  |                 if (key.indexOf('transition') < 0) { | ||||||
|  |                     dst.style[key] = cs[key]; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             dst.style.pointerEvents = 'none'; | ||||||
|  |             // and repeat for all children
 | ||||||
|  |             for (var i = 0; i < src.children.length; i++) { | ||||||
|  |                 this._copyStyle(src.children[i], dst.children[i]); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         DragDropTouch.prototype._dispatchEvent = function (e, type, target) { | ||||||
|  |             if (e && target) { | ||||||
|  |                 var evt = document.createEvent('Event'), t = e.touches ? e.touches[0] : e; | ||||||
|  |                 evt.initEvent(type, true, true); | ||||||
|  |                 evt.button = 0; | ||||||
|  |                 evt.which = evt.buttons = 1; | ||||||
|  |                 this._copyProps(evt, e, DragDropTouch._kbdProps); | ||||||
|  |                 this._copyProps(evt, t, DragDropTouch._ptProps); | ||||||
|  |                 evt.dataTransfer = this._dataTransfer; | ||||||
|  |                 target.dispatchEvent(evt); | ||||||
|  |                 return evt.defaultPrevented; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         }; | ||||||
|  |         // gets an element's closest draggable ancestor
 | ||||||
|  |         DragDropTouch.prototype._closestDraggable = function (e) { | ||||||
|  |             for (; e; e = e.parentElement) { | ||||||
|  |                 if (e.hasAttribute('draggable') && e.draggable) { | ||||||
|  |                     return e; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return null; | ||||||
|  |         }; | ||||||
|  |         return DragDropTouch; | ||||||
|  |     }()); | ||||||
|  |     /*private*/ DragDropTouch._instance = new DragDropTouch(); // singleton
 | ||||||
|  |     // constants
 | ||||||
|  |     DragDropTouch._THRESHOLD = 5; // pixels to move before drag starts
 | ||||||
|  |     DragDropTouch._OPACITY = 0.5; // drag image opacity
 | ||||||
|  |     DragDropTouch._DBLCLICK = 500; // max ms between clicks in a double click
 | ||||||
|  |     DragDropTouch._CTXMENU = 900; // ms to hold before raising 'contextmenu' event
 | ||||||
|  |     DragDropTouch._ISPRESSHOLDMODE = false; // decides of press & hold mode presence
 | ||||||
|  |     DragDropTouch._PRESSHOLDAWAIT = 400; // ms to wait before press & hold is detected
 | ||||||
|  |     DragDropTouch._PRESSHOLDMARGIN = 25; // pixels that finger might shiver while pressing
 | ||||||
|  |     DragDropTouch._PRESSHOLDTHRESHOLD = 0; // pixels to move before drag starts
 | ||||||
|  |     // copy styles/attributes from drag source to drag image element
 | ||||||
|  |     DragDropTouch._rmvAtts = 'id,class,style,draggable'.split(','); | ||||||
|  |     // synthesize and dispatch an event
 | ||||||
|  |     // returns true if the event has been handled (e.preventDefault == true)
 | ||||||
|  |     DragDropTouch._kbdProps = 'altKey,ctrlKey,metaKey,shiftKey'.split(','); | ||||||
|  |     DragDropTouch._ptProps = 'pageX,pageY,clientX,clientY,screenX,screenY,offsetX,offsetY'.split(','); | ||||||
|  |     DragDropTouch_1.DragDropTouch = DragDropTouch; | ||||||
|  | })(DragDropTouch || (DragDropTouch = {})); | ||||||
| @ -2,8 +2,9 @@ | |||||||
| <html> | <html> | ||||||
|   <head> |   <head> | ||||||
|     <title>Script graph demo</title> |     <title>Script graph demo</title> | ||||||
|  |     <script src="DragDropTouch.js"></script> | ||||||
|     <script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.14.0/js-yaml.min.js"></script> |     <script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.14.0/js-yaml.min.js"></script> | ||||||
|     <script src="index.js" type="module"></script> |     <script src="index.js" type="module" defer></script> | ||||||
|   </head> |   </head> | ||||||
|   <body style="margin: 24px"> |   <body style="margin: 24px"> | ||||||
|   <style> |   <style> | ||||||
| @ -22,7 +23,7 @@ | |||||||
|       white-space: pre-wrap; |       white-space: pre-wrap; | ||||||
|     } |     } | ||||||
|     #graph { |     #graph { | ||||||
|       max-width: 400px; |       max-width: 600px; | ||||||
|       overflow: auto; |       overflow: auto; | ||||||
|     } |     } | ||||||
|     .code { |     .code { | ||||||
| @ -35,11 +36,6 @@ | |||||||
|     <div> |     <div> | ||||||
|       <div class="card" id="graph"> |       <div class="card" id="graph"> | ||||||
|       </div> |       </div> | ||||||
|       <div class="card" id="graph2"> |  | ||||||
|         <script-graph3> |  | ||||||
|           <script-graph-node></script-graph-node> |  | ||||||
|         </script-graph3> |  | ||||||
|       </div> |  | ||||||
|       <div class="card code"> |       <div class="card code"> | ||||||
|         <h1> Selected node code </h1> |         <h1> Selected node code </h1> | ||||||
|         <wc-codemirror mode="json" id="snippet"> |         <wc-codemirror mode="json" id="snippet"> | ||||||
							
								
								
									
										240
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										240
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -70,24 +70,94 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/@vanillawc/wc-codemirror/-/wc-codemirror-1.8.10.tgz", |       "resolved": "https://registry.npmjs.org/@vanillawc/wc-codemirror/-/wc-codemirror-1.8.10.tgz", | ||||||
|       "integrity": "sha512-UKMD/UOpF1uRl29nlwvwQqSMBqsl+uDWYlGx82wIYbIBJkeqPrSo1Ez1rGi9jc1CL7/XwUr7u+l/kTwZAEkrEg==" |       "integrity": "sha512-UKMD/UOpF1uRl29nlwvwQqSMBqsl+uDWYlGx82wIYbIBJkeqPrSo1Ez1rGi9jc1CL7/XwUr7u+l/kTwZAEkrEg==" | ||||||
|     }, |     }, | ||||||
|  |     "async": { | ||||||
|  |       "version": "2.6.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", | ||||||
|  |       "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "lodash": "^4.17.14" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "basic-auth": { | ||||||
|  |       "version": "1.1.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", | ||||||
|  |       "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "builtin-modules": { |     "builtin-modules": { | ||||||
|       "version": "3.1.0", |       "version": "3.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", |       "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", | ||||||
|       "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", |       "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "call-bind": { | ||||||
|  |       "version": "1.0.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", | ||||||
|  |       "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "function-bind": "^1.1.1", | ||||||
|  |         "get-intrinsic": "^1.0.2" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "colors": { | ||||||
|  |       "version": "1.4.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", | ||||||
|  |       "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "corser": { | ||||||
|  |       "version": "2.0.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", | ||||||
|  |       "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "debug": { | ||||||
|  |       "version": "3.2.7", | ||||||
|  |       "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", | ||||||
|  |       "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "ms": "^2.1.1" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "deepmerge": { |     "deepmerge": { | ||||||
|       "version": "4.2.2", |       "version": "4.2.2", | ||||||
|       "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", |       "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", | ||||||
|       "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", |       "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "ecstatic": { | ||||||
|  |       "version": "3.3.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", | ||||||
|  |       "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "he": "^1.1.1", | ||||||
|  |         "mime": "^1.6.0", | ||||||
|  |         "minimist": "^1.1.0", | ||||||
|  |         "url-join": "^2.0.5" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "estree-walker": { |     "estree-walker": { | ||||||
|       "version": "1.0.1", |       "version": "1.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", |       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", | ||||||
|       "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", |       "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "eventemitter3": { | ||||||
|  |       "version": "4.0.7", | ||||||
|  |       "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", | ||||||
|  |       "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "follow-redirects": { | ||||||
|  |       "version": "1.13.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", | ||||||
|  |       "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "fsevents": { |     "fsevents": { | ||||||
|       "version": "2.1.3", |       "version": "2.1.3", | ||||||
|       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", |       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", | ||||||
| @ -95,6 +165,73 @@ | |||||||
|       "dev": true, |       "dev": true, | ||||||
|       "optional": true |       "optional": true | ||||||
|     }, |     }, | ||||||
|  |     "function-bind": { | ||||||
|  |       "version": "1.1.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", | ||||||
|  |       "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "get-intrinsic": { | ||||||
|  |       "version": "1.1.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", | ||||||
|  |       "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "function-bind": "^1.1.1", | ||||||
|  |         "has": "^1.0.3", | ||||||
|  |         "has-symbols": "^1.0.1" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "has": { | ||||||
|  |       "version": "1.0.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", | ||||||
|  |       "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "function-bind": "^1.1.1" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "has-symbols": { | ||||||
|  |       "version": "1.0.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", | ||||||
|  |       "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "he": { | ||||||
|  |       "version": "1.2.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", | ||||||
|  |       "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "http-proxy": { | ||||||
|  |       "version": "1.18.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", | ||||||
|  |       "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "eventemitter3": "^4.0.0", | ||||||
|  |         "follow-redirects": "^1.0.0", | ||||||
|  |         "requires-port": "^1.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "http-server": { | ||||||
|  |       "version": "0.12.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", | ||||||
|  |       "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "basic-auth": "^1.0.3", | ||||||
|  |         "colors": "^1.4.0", | ||||||
|  |         "corser": "^2.0.1", | ||||||
|  |         "ecstatic": "^3.3.2", | ||||||
|  |         "http-proxy": "^1.18.0", | ||||||
|  |         "minimist": "^1.2.5", | ||||||
|  |         "opener": "^1.5.1", | ||||||
|  |         "portfinder": "^1.0.25", | ||||||
|  |         "secure-compare": "3.0.1", | ||||||
|  |         "union": "~0.5.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "is-module": { |     "is-module": { | ||||||
|       "version": "1.0.0", |       "version": "1.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", |       "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", | ||||||
| @ -114,6 +251,51 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.2.1.tgz", |       "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.2.1.tgz", | ||||||
|       "integrity": "sha512-GSJHHXMGLZDzTRq59IUfL9FCdAlGfqNp/dEa7k7aBaaWD+JKaCjsAk9KYm2V12ItonVaYx2dprN66Zdm1AuBTQ==" |       "integrity": "sha512-GSJHHXMGLZDzTRq59IUfL9FCdAlGfqNp/dEa7k7aBaaWD+JKaCjsAk9KYm2V12ItonVaYx2dprN66Zdm1AuBTQ==" | ||||||
|     }, |     }, | ||||||
|  |     "lodash": { | ||||||
|  |       "version": "4.17.21", | ||||||
|  |       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", | ||||||
|  |       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "mime": { | ||||||
|  |       "version": "1.6.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", | ||||||
|  |       "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "minimist": { | ||||||
|  |       "version": "1.2.5", | ||||||
|  |       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", | ||||||
|  |       "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "mkdirp": { | ||||||
|  |       "version": "0.5.5", | ||||||
|  |       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", | ||||||
|  |       "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "minimist": "^1.2.5" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "ms": { | ||||||
|  |       "version": "2.1.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", | ||||||
|  |       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "object-inspect": { | ||||||
|  |       "version": "1.9.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", | ||||||
|  |       "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "opener": { | ||||||
|  |       "version": "1.5.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", | ||||||
|  |       "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "path-parse": { |     "path-parse": { | ||||||
|       "version": "1.0.6", |       "version": "1.0.6", | ||||||
|       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", |       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", | ||||||
| @ -126,6 +308,32 @@ | |||||||
|       "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", |       "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "portfinder": { | ||||||
|  |       "version": "1.0.28", | ||||||
|  |       "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", | ||||||
|  |       "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "async": "^2.6.2", | ||||||
|  |         "debug": "^3.1.1", | ||||||
|  |         "mkdirp": "^0.5.5" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "qs": { | ||||||
|  |       "version": "6.10.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", | ||||||
|  |       "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "side-channel": "^1.0.4" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "requires-port": { | ||||||
|  |       "version": "1.0.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", | ||||||
|  |       "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "resolve": { |     "resolve": { | ||||||
|       "version": "1.17.0", |       "version": "1.17.0", | ||||||
|       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", |       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", | ||||||
| @ -144,6 +352,23 @@ | |||||||
|         "fsevents": "~2.1.2" |         "fsevents": "~2.1.2" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "secure-compare": { | ||||||
|  |       "version": "3.0.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", | ||||||
|  |       "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "side-channel": { | ||||||
|  |       "version": "1.0.4", | ||||||
|  |       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", | ||||||
|  |       "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "call-bind": "^1.0.0", | ||||||
|  |         "get-intrinsic": "^1.0.2", | ||||||
|  |         "object-inspect": "^1.9.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "tslib": { |     "tslib": { | ||||||
|       "version": "2.0.1", |       "version": "2.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", |       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", | ||||||
| @ -155,6 +380,21 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", |       "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", | ||||||
|       "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", |       "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", | ||||||
|       "dev": true |       "dev": true | ||||||
|  |     }, | ||||||
|  |     "union": { | ||||||
|  |       "version": "0.5.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", | ||||||
|  |       "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "qs": "^6.4.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "url-join": { | ||||||
|  |       "version": "2.0.5", | ||||||
|  |       "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", | ||||||
|  |       "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", | ||||||
|  |       "dev": true | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,13 +6,15 @@ | |||||||
|   "main": "index.js", |   "main": "index.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "build": "rollup -c", |     "build": "rollup -c", | ||||||
|     "watch": "rollup -c -w" |     "watch": "rollup -c --watch", | ||||||
|  |     "serve": "http-server ." | ||||||
|   }, |   }, | ||||||
|   "author": "", |   "author": "", | ||||||
|   "license": "ISC", |   "license": "ISC", | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@rollup/plugin-node-resolve": "^9.0.0", |     "@rollup/plugin-node-resolve": "^9.0.0", | ||||||
|     "@rollup/plugin-typescript": "^5.0.2", |     "@rollup/plugin-typescript": "^5.0.2", | ||||||
|  |     "http-server": "^0.12.3", | ||||||
|     "rollup": "^2.26.4", |     "rollup": "^2.26.4", | ||||||
|     "tslib": "^2.0.1", |     "tslib": "^2.0.1", | ||||||
|     "typescript": "^4.0.2" |     "typescript": "^4.0.2" | ||||||
|  | |||||||
| @ -1,13 +1,11 @@ | |||||||
| import resolve from "@rollup/plugin-node-resolve"; | import nodeResolve from "@rollup/plugin-node-resolve"; | ||||||
| import typescript from "@rollup/plugin-typescript"; | import typescript from "@rollup/plugin-typescript"; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|   input: "./src/main.js", |   input: "src/main.js", | ||||||
|   output: [ |   output: { | ||||||
|     { |  | ||||||
|     file: "index.js", |     file: "index.js", | ||||||
|       format: "cjs" |     format: "es", | ||||||
|     } |   }, | ||||||
|   ], |   plugins: [nodeResolve(), typescript()], | ||||||
|   plugins: [resolve(), typescript()] |  | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										104
									
								
								src/hat-graph/hat-graph-node.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/hat-graph/hat-graph-node.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,104 @@ | |||||||
|  | import { css, LitElement, property, svg } from "lit-element"; | ||||||
|  | 
 | ||||||
|  | const NODE_SIZE = 24; | ||||||
|  | 
 | ||||||
|  | export class HatGraphNode extends LitElement { | ||||||
|  |   @property() iconPath?; | ||||||
|  |   dragtarget = undefined; | ||||||
|  |   config = undefined; | ||||||
|  | 
 | ||||||
|  |   connectedCallback() { | ||||||
|  |     super.connectedCallback(); | ||||||
|  |     if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0"); | ||||||
|  | 
 | ||||||
|  |     this.addEventListener("dragstart", () => { | ||||||
|  |       this.classList.add("dragging"); | ||||||
|  |       (window as any)._dragElement = this.dragtarget ?? this; | ||||||
|  |       this.updateNode(""); | ||||||
|  |     }); | ||||||
|  |     this.addEventListener("dragend", () => { | ||||||
|  |       this.classList.remove("dragging"); | ||||||
|  |       (window as any)._dragElement = undefined; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   updateNode(config) { | ||||||
|  |     this.dispatchEvent( | ||||||
|  |       new CustomEvent("update-node", { | ||||||
|  |         detail: { config }, | ||||||
|  |         bubbles: true, | ||||||
|  |         composed: true, | ||||||
|  |       }) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   deleteNode() { | ||||||
|  |     this.dispatchEvent(new CustomEvent("delete-node", { bubbles: true })); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   placeNode(config) { | ||||||
|  |     this.dispatchEvent( | ||||||
|  |       new CustomEvent("place-node", { detail: { config }, bubbles: true }) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get width() { | ||||||
|  |     return NODE_SIZE + 5; | ||||||
|  |   } | ||||||
|  |   get height() { | ||||||
|  |     return NODE_SIZE + 5; | ||||||
|  |   } | ||||||
|  |   render() { | ||||||
|  |     return svg` | ||||||
|  |     <svg | ||||||
|  |       width="${this.width}" | ||||||
|  |       height="${this.height}" | ||||||
|  |       viewBox="${-this.width / 2} 0 ${this.width} ${this.height}" | ||||||
|  |     > | ||||||
|  |       <circle | ||||||
|  |         cx="0" | ||||||
|  |         cy="${this.width / 2}" | ||||||
|  |         r="${NODE_SIZE / 2}" | ||||||
|  |       /> | ||||||
|  |       <g | ||||||
|  |         style="pointer-events: none" | ||||||
|  |         transform="translate(${-12} ${this.width / 2 - 12})" | ||||||
|  |       > | ||||||
|  |         ${this.iconPath ? svg`<path d="${this.iconPath}"/>` : ""} | ||||||
|  |       </g> | ||||||
|  |       </svg> | ||||||
|  |     `;
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static get styles() { | ||||||
|  |     return css` | ||||||
|  |       :host { | ||||||
|  |         display: flex; | ||||||
|  |         --stroke-clr: var(--stroke-color, rgb(3, 169, 244)); | ||||||
|  |         --hover-clr: var(--hover-color, rgb(255, 152, 0)); | ||||||
|  |       } | ||||||
|  |       :host(:hover) { | ||||||
|  |         --stroke-clr: var(--hover-clr); | ||||||
|  |       } | ||||||
|  |       :host(:focus) { | ||||||
|  |         --stroke-clr: green; | ||||||
|  |         outline: none; | ||||||
|  |       } | ||||||
|  |       :host(.dragging) { | ||||||
|  |         --stroke-clr: gray; | ||||||
|  |         color: gray; | ||||||
|  |       } | ||||||
|  |       :host(.dragging) path { | ||||||
|  |         stroke: gray; | ||||||
|  |         fill: gray; | ||||||
|  |       } | ||||||
|  |       circle { | ||||||
|  |         stroke: var(--stroke-clr); | ||||||
|  |         stroke-width: 2; | ||||||
|  |         fill: white; | ||||||
|  |       } | ||||||
|  |     `;
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | customElements.define("hat-graph-node", HatGraphNode); | ||||||
							
								
								
									
										183
									
								
								src/hat-graph/hat-graph.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/hat-graph/hat-graph.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,183 @@ | |||||||
|  | import { css, html, LitElement, property, svg } from "lit-element"; | ||||||
|  | 
 | ||||||
|  | import "./hat-graph-node"; | ||||||
|  | 
 | ||||||
|  | const BRANCH_HEIGHT = 30; | ||||||
|  | const BRANCH_CURVATURE = 25; | ||||||
|  | const VERTICAL_SPACING = 10; | ||||||
|  | const NODE_SIZE = 24; | ||||||
|  | 
 | ||||||
|  | export class HatGraph extends LitElement { | ||||||
|  |   @property() _num_items = 0; | ||||||
|  | 
 | ||||||
|  |   @property({ reflect: true }) branching?; | ||||||
|  |   config?; | ||||||
|  | 
 | ||||||
|  |   updateNode(config) { | ||||||
|  |     this.dispatchEvent( | ||||||
|  |       new CustomEvent("update-node", { | ||||||
|  |         detail: { config }, | ||||||
|  |         bubbles: true, | ||||||
|  |         composed: true, | ||||||
|  |       }) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   deleteNode() { | ||||||
|  |     this.dispatchEvent(new CustomEvent("delete-node", { bubbles: true })); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   placeNode(config) { | ||||||
|  |     this.dispatchEvent( | ||||||
|  |       new CustomEvent("place-node", { detail: { config }, bubbles: true }) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get width() { | ||||||
|  |     let w = 0; | ||||||
|  |     if (this.branching !== undefined) { | ||||||
|  |       for (const c of this.children) { | ||||||
|  |         if (c.slot === "head") continue; | ||||||
|  |         w += (c as any).width; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       for (const c of this.children) { | ||||||
|  |         w = Math.max(w, (c as any).width); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return w; | ||||||
|  |   } | ||||||
|  |   get height() { | ||||||
|  |     let h = 0; | ||||||
|  |     if (this.branching !== undefined) { | ||||||
|  |       for (const c of this.children) { | ||||||
|  |         if (c.slot === "head") continue; | ||||||
|  |         h = Math.max(h, (c as any).height); | ||||||
|  |       } | ||||||
|  |       h += 2 * BRANCH_HEIGHT; | ||||||
|  |     } else { | ||||||
|  |       for (const c of this.children) { | ||||||
|  |         h += (c as any).height + VERTICAL_SPACING; | ||||||
|  |       } | ||||||
|  |       h; | ||||||
|  |     } | ||||||
|  |     return h; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async updateChildren() { | ||||||
|  |     this._num_items = this.children.length; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     let branch_x = []; | ||||||
|  |     let total = 0; | ||||||
|  |     if (this.branching !== undefined) { | ||||||
|  |       for (const c of Array.from(this.children)) { | ||||||
|  |         if (c.slot === "head") continue; | ||||||
|  |         const rect = c.getBoundingClientRect(); | ||||||
|  |         branch_x.push(rect.width / 2 + total); | ||||||
|  |         total += rect.width; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const line_end = this.height - BRANCH_HEIGHT; | ||||||
|  | 
 | ||||||
|  |     return html` | ||||||
|  |       <svg width="${this.width}" height="${this.height}"> | ||||||
|  |         <rect | ||||||
|  |           x="0" | ||||||
|  |           y="0" | ||||||
|  |           width="${this.width}" | ||||||
|  |           height="${this.height}" | ||||||
|  |           fill="white" | ||||||
|  |         /> | ||||||
|  |         ${this.branching !== undefined | ||||||
|  |           ? branch_x.map((x) => { | ||||||
|  |               return svg` | ||||||
|  |                 <path | ||||||
|  |                   class="line" | ||||||
|  |                   d=" | ||||||
|  |                     M ${this.width / 2} 0 | ||||||
|  |                     C ${this.width / 2} ${BRANCH_CURVATURE} | ||||||
|  |                       ${x} ${BRANCH_HEIGHT - BRANCH_CURVATURE} | ||||||
|  |                       ${x} ${BRANCH_HEIGHT} | ||||||
|  |                     L ${x} ${line_end} | ||||||
|  |                     C ${x} ${line_end + BRANCH_CURVATURE} | ||||||
|  |                       ${this.width / 2} ${this.height - BRANCH_CURVATURE} | ||||||
|  |                       ${this.width / 2} ${this.height} | ||||||
|  |                   " | ||||||
|  |                 /> | ||||||
|  |               `;
 | ||||||
|  |             }) | ||||||
|  |           : svg` | ||||||
|  |               <path | ||||||
|  |               class="line" | ||||||
|  |               d=" | ||||||
|  |                 M ${this.width / 2} 0 | ||||||
|  |                 L ${this.width / 2} ${this.height} | ||||||
|  |               " | ||||||
|  |               /> | ||||||
|  |             `}
 | ||||||
|  |       </svg> | ||||||
|  |       <slot name="head"> </slot> | ||||||
|  |       <div | ||||||
|  |         id="branches" | ||||||
|  |         class="${this.querySelector(":scope > [slot='head']") ? "" : "no-head"}" | ||||||
|  |       > | ||||||
|  |         <slot @slotchange=${this.updateChildren}></slot> | ||||||
|  |       </div> | ||||||
|  |     `;
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static get styles() { | ||||||
|  |     return css` | ||||||
|  |       :host { | ||||||
|  |         position: relative; | ||||||
|  |         display: flex; | ||||||
|  |         --stroke-clr: var(--stroke-color, rgb(3, 169, 244)); | ||||||
|  |         --hover-clr: var(--hover-color, rgb(255, 152, 0)); | ||||||
|  |       } | ||||||
|  |       #branches { | ||||||
|  |         position: absolute; | ||||||
|  |         left: 0; | ||||||
|  |         display: flex; | ||||||
|  |         flex-direction: row; | ||||||
|  |         align-items: center; | ||||||
|  |       } | ||||||
|  |       :host([branching]) #branches { | ||||||
|  |         top: ${BRANCH_HEIGHT}px; | ||||||
|  |         flex-direction: row; | ||||||
|  |         align-items: start; | ||||||
|  |       } | ||||||
|  |       :host(:not([branching])) #branches { | ||||||
|  |         top: ${VERTICAL_SPACING + NODE_SIZE}px; /* SHould be something else*/ | ||||||
|  |         flex-direction: column; | ||||||
|  |       } | ||||||
|  |       :host(:not([branching])) #branches.no-head { | ||||||
|  |         top: 0; | ||||||
|  |       } | ||||||
|  |       path.line { | ||||||
|  |         stroke: var(--stroke-clr); | ||||||
|  |         stroke-width: 2; | ||||||
|  |         fill: none; | ||||||
|  |       } | ||||||
|  |       :host(:not([branching])) ::slotted(*) { | ||||||
|  |         margin-bottom: 10px; | ||||||
|  |       } | ||||||
|  |       ::slotted(:last-child) { | ||||||
|  |         margin-bottom: 0; | ||||||
|  |       } | ||||||
|  |       ::slotted([slot="head"]) { | ||||||
|  |         position: absolute; | ||||||
|  |         top: ${BRANCH_HEIGHT / 2}px; | ||||||
|  |         left: 50%; | ||||||
|  |         transform: translate(-50%, -50%); | ||||||
|  |       } | ||||||
|  |       :host(:focus-within) ::slotted([slot="head"]) { | ||||||
|  |         --stroke-color: green; | ||||||
|  |       } | ||||||
|  |     `;
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | customElements.define("hat-graph", HatGraph); | ||||||
							
								
								
									
										259
									
								
								src/hat-graph/make-graph.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								src/hat-graph/make-graph.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,259 @@ | |||||||
|  | import { HatGraphNode } from "./hat-graph-node"; | ||||||
|  | 
 | ||||||
|  | import { | ||||||
|  |   mdiCallSplit, | ||||||
|  |   mdiAbTesting, | ||||||
|  |   mdiCheck, | ||||||
|  |   mdiClose, | ||||||
|  |   mdiChevronRight, | ||||||
|  |   mdiExclamation, | ||||||
|  |   mdiTimerOutline, | ||||||
|  |   mdiTrafficLight, | ||||||
|  |   mdiRefresh, | ||||||
|  |   mdiArrowUp, | ||||||
|  |   mdiCodeJson, | ||||||
|  |   mdiCheckBoxOutline, | ||||||
|  |   mdiCheckboxBlankOutline, | ||||||
|  |   mdiAsterisk, | ||||||
|  | } from "@mdi/js"; | ||||||
|  | import { HatGraph } from "./hat-graph"; | ||||||
|  | import { html } from "lit-element"; | ||||||
|  | import { render } from "lit-html"; | ||||||
|  | 
 | ||||||
|  | const OPTIONS = [ | ||||||
|  |   "condition", | ||||||
|  |   "delay", | ||||||
|  |   "device_id", | ||||||
|  |   "event", | ||||||
|  |   "scene", | ||||||
|  |   "service", | ||||||
|  |   "wait_template", | ||||||
|  |   "repeat", | ||||||
|  |   "choose", | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | const ICONS = { | ||||||
|  |   new: mdiAsterisk, | ||||||
|  |   service: mdiChevronRight, | ||||||
|  |   condition: mdiAbTesting, | ||||||
|  |   TRUE: mdiCheck, | ||||||
|  |   FALSE: mdiClose, | ||||||
|  |   delay: mdiTimerOutline, | ||||||
|  |   wait_template: mdiTrafficLight, | ||||||
|  |   event: mdiExclamation, | ||||||
|  |   repeat: mdiRefresh, | ||||||
|  |   repeatReturn: mdiArrowUp, | ||||||
|  |   choose: mdiCallSplit, | ||||||
|  |   chooseChoice: mdiCheckBoxOutline, | ||||||
|  |   chooseDefault: mdiCheckboxBlankOutline, | ||||||
|  |   YAML: mdiCodeJson, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const SPECIAL_NODES = { | ||||||
|  |   condition: makeConditionNode, | ||||||
|  |   choose: makeChooseNode, | ||||||
|  |   repeat: makeRepeatNode, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function makeConditionNode(config) { | ||||||
|  |   const graph = document.createElement("hat-graph") as HatGraph; | ||||||
|  |   graph.branching = true; | ||||||
|  |   const focused = () => | ||||||
|  |     graph.dispatchEvent( | ||||||
|  |       new CustomEvent("node-selected", { detail: { config }, bubbles: true }) | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |   render( | ||||||
|  |     html` | ||||||
|  |       <hat-graph-node slot="head" .iconPath=${mdiAbTesting} @focus=${focused}> | ||||||
|  |       </hat-graph-node> | ||||||
|  |       <hat-graph-node .iconPath=${mdiCheck} @focus=${focused}></hat-graph-node> | ||||||
|  |       <hat-graph-node .iconPath=${mdiClose} @focus=${focused}></hat-graph-node> | ||||||
|  |     `,
 | ||||||
|  |     graph | ||||||
|  |   ); | ||||||
|  |   return graph; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function makeChooseNode(config) { | ||||||
|  |   const graph = document.createElement("hat-graph") as HatGraph; | ||||||
|  |   graph.config = config; | ||||||
|  |   graph.branching = true; | ||||||
|  | 
 | ||||||
|  |   const focused = () => | ||||||
|  |     graph.dispatchEvent( | ||||||
|  |       new CustomEvent("node-selected", { detail: { config }, bubbles: true }) | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |   render( | ||||||
|  |     html` | ||||||
|  |       <hat-graph-node | ||||||
|  |         slot="head" | ||||||
|  |         .iconPath="${mdiCallSplit}" | ||||||
|  |         @focus=${focused} | ||||||
|  |         draggable="true" | ||||||
|  |         .dragtarget=${graph} | ||||||
|  |       > | ||||||
|  |       </hat-graph-node> | ||||||
|  |       ${config.choose?.map((branch) => { | ||||||
|  |         const head = document.createElement("hat-graph-node") as HatGraphNode; | ||||||
|  |         head.iconPath = mdiCheckBoxOutline; | ||||||
|  |         head.addEventListener("focus", focused); | ||||||
|  | 
 | ||||||
|  |         return makeGraph(branch.sequence, head); | ||||||
|  |       })} | ||||||
|  |       ${(() => { | ||||||
|  |         const head = document.createElement("hat-graph-node") as HatGraphNode; | ||||||
|  |         head.iconPath = mdiCheckboxBlankOutline; | ||||||
|  |         head.addEventListener("focus", focused); | ||||||
|  |         return makeGraph(config.default, head); | ||||||
|  |       })()} | ||||||
|  |     `,
 | ||||||
|  |     graph | ||||||
|  |   ); | ||||||
|  |   return graph; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function makeRepeatNode(config) { | ||||||
|  |   const graph = document.createElement("hat-graph") as HatGraph; | ||||||
|  |   graph.branching = true; | ||||||
|  | 
 | ||||||
|  |   const focused = () => | ||||||
|  |     graph.dispatchEvent( | ||||||
|  |       new CustomEvent("node-selected", { detail: { config }, bubbles: true }) | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |   render( | ||||||
|  |     html` | ||||||
|  |       <hat-graph-node | ||||||
|  |         .iconPath=${mdiArrowUp} | ||||||
|  |         @focus=${focused} | ||||||
|  |       ></hat-graph-node> | ||||||
|  |       ${makeGraph(config.repeat.sequence)} | ||||||
|  |     `,
 | ||||||
|  |     graph | ||||||
|  |   ); | ||||||
|  |   return graph; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function makeNode(config) { | ||||||
|  |   if (typeof config === "string") return undefined; | ||||||
|  |   const type = OPTIONS.find((key) => key in config) || "yaml"; | ||||||
|  | 
 | ||||||
|  |   if (type in SPECIAL_NODES) { | ||||||
|  |     return SPECIAL_NODES[type](config); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const node = document.createElement("hat-graph-node") as HatGraphNode; | ||||||
|  | 
 | ||||||
|  |   node.iconPath = ICONS[type]; | ||||||
|  |   node.draggable = true; | ||||||
|  | 
 | ||||||
|  |   node.config = config; | ||||||
|  | 
 | ||||||
|  |   node.addEventListener("focus", (ev) => { | ||||||
|  |     node.dispatchEvent( | ||||||
|  |       new CustomEvent("node-selected", { detail: { config }, bubbles: true }) | ||||||
|  |     ); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   return node; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function makeGraph(nodes, head = undefined) { | ||||||
|  |   const graph = document.createElement("hat-graph") as HatGraph; | ||||||
|  |   graph.addEventListener("dragenter", (ev) => { | ||||||
|  |     ev.stopPropagation(); | ||||||
|  |     ev.preventDefault(); | ||||||
|  |     try { | ||||||
|  |       graph.appendChild((window as any)._dragElement); | ||||||
|  |     } catch (e) { | ||||||
|  |       if (!(e instanceof DOMException)) throw e; | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |   (graph as any).test = "Hello!"; | ||||||
|  | 
 | ||||||
|  |   if (head) { | ||||||
|  |     head.slot = "head"; | ||||||
|  |     graph.appendChild(head); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   for (const [i, nodeConfig] of nodes.entries()) { | ||||||
|  |     const node = makeNode(nodeConfig); | ||||||
|  |     if (!node) { | ||||||
|  |       window.setTimeout(() => { | ||||||
|  |         const config = [...nodes]; | ||||||
|  |         config.splice(i, 1); | ||||||
|  | 
 | ||||||
|  |         graph.dispatchEvent( | ||||||
|  |           new CustomEvent("update-node", { detail: { config }, bubbles: true }) | ||||||
|  |         ); | ||||||
|  |       }, 100); | ||||||
|  |       continue; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     node.addEventListener("dragover", (ev) => { | ||||||
|  |       ev.stopPropagation(); | ||||||
|  |       ev.preventDefault(); | ||||||
|  |     }); | ||||||
|  |     node.addEventListener("dragenter", (ev) => { | ||||||
|  |       ev.stopPropagation(); | ||||||
|  |       ev.preventDefault(); | ||||||
|  |       if (node === (window as any)._dragElement) return; | ||||||
|  |       try { | ||||||
|  |         graph.insertBefore((window as any)._dragElement, node); | ||||||
|  |         (window as any)._dragTarget = node; | ||||||
|  |       } catch (e) { | ||||||
|  |         if (!(e instanceof DOMException)) throw e; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     node.addEventListener("drop", (ev) => { | ||||||
|  |       ev.stopPropagation(); | ||||||
|  |       ev.preventDefault(); | ||||||
|  |       if ((window as any)._dragTarget) { | ||||||
|  |         console.log("Drop onto ", (window as any)._dragTarget); | ||||||
|  |         const config = { ...(window as any)._dragElement.config }; | ||||||
|  |         (window as any)._dragTarget.placeNode(config); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     node.addEventListener("update-node", (ev) => { | ||||||
|  |       ev.stopPropagation(); | ||||||
|  | 
 | ||||||
|  |       const config = [...nodes]; | ||||||
|  |       config[i] = ev.detail.config; | ||||||
|  | 
 | ||||||
|  |       graph.dispatchEvent( | ||||||
|  |         new CustomEvent("update-node", { detail: { config }, bubbles: true }) | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     node.addEventListener("delete-node", (ev) => { | ||||||
|  |       ev.stopPropagation(); | ||||||
|  | 
 | ||||||
|  |       const config = [...nodes]; | ||||||
|  |       config.splice(i, 1); | ||||||
|  | 
 | ||||||
|  |       graph.dispatchEvent( | ||||||
|  |         new CustomEvent("update-node", { detail: { config }, bubbles: true }) | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     node.addEventListener("place-node", (ev) => { | ||||||
|  |       ev.stopPropagation(); | ||||||
|  | 
 | ||||||
|  |       const config = [...nodes]; | ||||||
|  |       config.splice(i, 0, ev.detail.config); | ||||||
|  |       console.log(config); | ||||||
|  | 
 | ||||||
|  |       graph.dispatchEvent( | ||||||
|  |         new CustomEvent("update-node", { detail: { config }, bubbles: true }) | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     graph.appendChild(node); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return graph; | ||||||
|  | } | ||||||
							
								
								
									
										80
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								src/main.js
									
									
									
									
									
								
							| @ -1,8 +1,11 @@ | |||||||
| import {demoConfig} from "./demo-config"; | import { demoConfig2 } from "./demo-config"; | ||||||
| import "@vanillawc/wc-codemirror"; | import "@vanillawc/wc-codemirror"; | ||||||
| 
 | 
 | ||||||
| import "./script-graph"; | import "./script-graph"; | ||||||
| import "./script-graph3"; | // import "./script-graph3";
 | ||||||
|  | import "./hat-graph/hat-graph-node"; | ||||||
|  | import "./hat-graph/hat-graph"; | ||||||
|  | import { makeGraph } from "./hat-graph/make-graph"; | ||||||
| 
 | 
 | ||||||
| import { ActionHandler } from "./script-to-graph"; | import { ActionHandler } from "./script-to-graph"; | ||||||
| 
 | 
 | ||||||
| @ -12,18 +15,18 @@ let index_counter = 0; | |||||||
| let nodes = []; | let nodes = []; | ||||||
| 
 | 
 | ||||||
| window.onload = () => { | window.onload = () => { | ||||||
| 
 |   return; | ||||||
|   const graph2 = document.createElement("script-graph3"); |   //const graph2 = document.createElement("script-graph3");
 | ||||||
|   document.querySelector("#graph2").appendChild(graph2); |   //document.querySelector("#graph2").appendChild(graph2);
 | ||||||
| 
 | 
 | ||||||
|   let src = demoConfig; |   let src = demoConfig; | ||||||
| 
 | 
 | ||||||
|   const fullcode = document.querySelector("#fullcode"); |   const fullcode = document.querySelector("#fullcode"); | ||||||
|   fullcode.mode = "yaml"; |   fullcode.mode = "yaml"; | ||||||
|   window.setTimeout(()=> fullcode.value = jsyaml.safeDump(src), 100); |   window.setTimeout(() => (fullcode.value = jsyaml.safeDump(src)), 100); | ||||||
| 
 | 
 | ||||||
|   const updateButton = document.querySelector("#updateButton"); |   const updateButton = document.querySelector("#updateButton"); | ||||||
|   updateButton.addEventListener('click', () => { |   updateButton.addEventListener("click", () => { | ||||||
|     src = jsyaml.safeLoad(fullcode.value); |     src = jsyaml.safeLoad(fullcode.value); | ||||||
|     index_counter = 0; |     index_counter = 0; | ||||||
|     nodes = []; |     nodes = []; | ||||||
| @ -43,11 +46,68 @@ window.onload = () => { | |||||||
|     graph.tree = tr.graph; |     graph.tree = tr.graph; | ||||||
|     const code = document.querySelector("#snippet"); |     const code = document.querySelector("#snippet"); | ||||||
|     code.value = jsyaml.safeDump(action); |     code.value = jsyaml.safeDump(action); | ||||||
|     document.querySelector("#saveSnippet").onclick = () => update(jsyaml.safeLoad(code.value)); |     document.querySelector("#saveSnippet").onclick = () => | ||||||
|     document.querySelector("#deleteSnippet").onclick = () => update(jsyaml.safeLoad(null)); |       update(jsyaml.safeLoad(code.value)); | ||||||
|   } |     document.querySelector("#deleteSnippet").onclick = () => | ||||||
|  |       update(jsyaml.safeLoad(null)); | ||||||
|  |   }; | ||||||
| 
 | 
 | ||||||
|   graph.tree = tr.graph; |   graph.tree = tr.graph; | ||||||
|   document.querySelector("#graph").appendChild(graph); |   document.querySelector("#graph").appendChild(graph); | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
|  | function nodeSelected(ev) { | ||||||
|  |   const code = document.querySelector("#snippet"); | ||||||
|  |   code.value = jsyaml.safeDump(ev.detail.config); | ||||||
|  |   code.currentNode = ev.target; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | function rebuildGraph(config) { | ||||||
|  |   const graphCard = document.querySelector("#graph"); | ||||||
|  | 
 | ||||||
|  |   while (graphCard.firstChild) graphCard.removeChild(graphCard.firstChild); | ||||||
|  | 
 | ||||||
|  |   const graph = makeGraph(config); | ||||||
|  |   graph.addEventListener("node-selected", nodeSelected); | ||||||
|  |   graph.addEventListener("update-node", (ev) => { | ||||||
|  |     const code = document.querySelector("#fullcode"); | ||||||
|  |     code.value = jsyaml.safeDump(ev.detail.config); | ||||||
|  |     window.setTimeout(() => rebuildGraph(ev.detail.config), 100); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   graphCard.appendChild(graph); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function setup_snippet_editor() { | ||||||
|  |   document.querySelector("#saveSnippet").onclick = () => { | ||||||
|  |     const code = document.querySelector("#snippet"); | ||||||
|  |     if (code.currentNode) { | ||||||
|  |       code.currentNode.updateNode(jsyaml.safeLoad(code.value)); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |   document.querySelector("#deleteSnippet").onclick = () => { | ||||||
|  |     const code = document.querySelector("#snippet"); | ||||||
|  |     if (code.currentNode) { | ||||||
|  |       code.currentNode.deleteNode(); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function setup() { | ||||||
|  |   setup_snippet_editor(); | ||||||
|  | 
 | ||||||
|  |   let src = demoConfig2; | ||||||
|  |   const fullcode = document.querySelector("#fullcode"); | ||||||
|  |   fullcode.mode = "yaml"; | ||||||
|  |   window.setTimeout(() => (fullcode.value = jsyaml.safeDump(src)), 100); | ||||||
|  | 
 | ||||||
|  |   rebuildGraph(src); | ||||||
|  | 
 | ||||||
|  |   const updateButton = document.querySelector("#updateButton"); | ||||||
|  |   updateButton.addEventListener("click", () => { | ||||||
|  |     src = jsyaml.safeLoad(fullcode.value); | ||||||
|  |     rebuildGraph(src); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | setup(); | ||||||
|  | |||||||
| @ -4,13 +4,38 @@ import { | |||||||
|   css, |   css, | ||||||
|   svg, |   svg, | ||||||
|   property, |   property, | ||||||
|   TemplateResult |   TemplateResult, | ||||||
| } from "lit-element"; | } from "lit-element"; | ||||||
| 
 | 
 | ||||||
| import { mdiAsterisk } from "@mdi/js"; | import { | ||||||
|  |   mdiCallSplit, | ||||||
|  |   mdiAbTesting, | ||||||
|  |   mdiCheck, | ||||||
|  |   mdiClose, | ||||||
|  |   mdiChevronRight, | ||||||
|  |   mdiExclamation, | ||||||
|  |   mdiTimerOutline, | ||||||
|  |   mdiTrafficLight, | ||||||
|  |   mdiRefresh, | ||||||
|  |   mdiArrowUp, | ||||||
|  |   mdiCodeJson, | ||||||
|  |   mdiCheckBoxOutline, | ||||||
|  |   mdiCheckboxBlankOutline, | ||||||
|  |   mdiAsterisk, | ||||||
|  |   mdiCircleOutline, | ||||||
|  | } from "@mdi/js"; | ||||||
| 
 | 
 | ||||||
| const SIZE = 35; | const ICONS = { | ||||||
| const DIST = 20; |   "call-split": mdiCallSplit, | ||||||
|  |   "ab-testing": mdiAbTesting, | ||||||
|  |   check: mdiCheck, | ||||||
|  |   close: mdiClose, | ||||||
|  |   "chevron-right": mdiChevronRight, | ||||||
|  |   exclamation: mdiExclamation, | ||||||
|  |   asterisk: mdiAsterisk, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const SIZE = 24; | ||||||
| 
 | 
 | ||||||
| interface GraphNode extends LitElement { | interface GraphNode extends LitElement { | ||||||
|   render_svg(): TemplateResult; |   render_svg(): TemplateResult; | ||||||
| @ -19,92 +44,255 @@ interface GraphNode extends LitElement{ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class ScriptGraphNode extends LitElement { | class ScriptGraphNode extends LitElement { | ||||||
|  |   @property() icon = "chevron-right"; | ||||||
| 
 | 
 | ||||||
|   @property() icon = mdiAsterisk; |   connectedCallback() { | ||||||
|  |     super.connectedCallback(); | ||||||
|  |     if (!this.hasAttribute("tabindex")) this.setAttribute("tabindex", "0"); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   get width() { |   get width() { | ||||||
|     return SIZE; |     return SIZE + 5; | ||||||
|   } |   } | ||||||
|   get height() { |   get height() { | ||||||
|     return SIZE + DIST; |     return SIZE + 5; | ||||||
|   } |   } | ||||||
| 
 |   render() { | ||||||
|   render_svg() { |  | ||||||
|     return svg` |     return svg` | ||||||
|  |     <svg | ||||||
|  |       width="${this.width}" | ||||||
|  |       height="${this.height}" | ||||||
|  |       viewBox="${-this.width / 2} 0 ${this.width} ${this.height}" | ||||||
|  |     > | ||||||
|       <circle |       <circle | ||||||
|         cx="0" |         cx="0" | ||||||
|         cy="${this.width / 2}" |         cy="${this.width / 2}" | ||||||
|         r="${SIZE / 2}" |         r="${SIZE / 2}" | ||||||
|       /> |       /> | ||||||
|       <g style="pointer-events: none" transform="translate(-12, ${this.width/2-12})"> |       <g | ||||||
|       </g> |         style="pointer-events: none" | ||||||
|  |         transform="translate(${-12} ${this.width / 2 - 12})" | ||||||
|  |       > | ||||||
|  |         ${ | ||||||
|  |           ICONS[this.icon] | ||||||
|  |             ? svg` | ||||||
|  |           <path d="${ICONS[this.icon]}"/> | ||||||
|           ` |           ` | ||||||
|  |             : "" | ||||||
|  |         } | ||||||
|  |       </g> | ||||||
|  |       </svg> | ||||||
|  |     `;
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   static get styles() { | ||||||
|  |     return css` | ||||||
|  |       :host { | ||||||
|  |         display: flex; | ||||||
|  |         --stroke-clr: var(--stroke-color, rgb(3, 169, 244)); | ||||||
|  |         --hover-clr: var(--hover-color, rgb(255, 152, 0)); | ||||||
|  |       } | ||||||
|  |       circle { | ||||||
|  |         stroke: var(--stroke-clr); | ||||||
|  |         stroke-width: 2; | ||||||
|  |         fill: white; | ||||||
|  |       } | ||||||
|  |       :host(:hover) { | ||||||
|  |         --stroke-clr: var(--hover-clr); | ||||||
|  |       } | ||||||
|  |       :host(:focus) { | ||||||
|  |         --stroke-clr: green; | ||||||
|  |         outline: none; | ||||||
|  |       } | ||||||
|  |     `;
 | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class ScriptGraph3 extends LitElement { | class ScriptGraphBranch extends LitElement { | ||||||
| 
 |   @property() _num_items = 0; | ||||||
|   @property() content = []; |   @property() _branch_height = 30; | ||||||
|   @property() _width = 0; |   @property() _branch_curve = 25; | ||||||
|   @property() _height = 0; |  | ||||||
| 
 |  | ||||||
|   connectedCallback() { |  | ||||||
|     super.connectedCallback(); |  | ||||||
|     console.log(this.querySelectorAll('*')) |  | ||||||
| 
 | 
 | ||||||
|  |   get width() { | ||||||
|  |     let w = 0; | ||||||
|  |     for (const c of this.children) { | ||||||
|  |       w += (c as any).width ?? 0; | ||||||
|  |     } | ||||||
|  |     return w; | ||||||
|  |   } | ||||||
|  |   get height() { | ||||||
|  |     let h = 0; | ||||||
|  |     for (const c of this.children) { | ||||||
|  |       h = Math.max(h, (c as any).height ?? 0); | ||||||
|  |     } | ||||||
|  |     return h + 2 * this._branch_height; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async updateChildren() { |   async updateChildren() { | ||||||
|     this.content = []; |     this._num_items = this.children.length; | ||||||
|     let y = 0; |  | ||||||
|     let w = 0; |  | ||||||
|     for (const e of this.querySelectorAll('*') as NodeListOf<GraphNode>) { |  | ||||||
|       this.content.push({ |  | ||||||
|         svg: e.render_svg(), |  | ||||||
|         offset_y: y, |  | ||||||
|       }); |  | ||||||
|       y += e.height; |  | ||||||
|       w = Math.max(w, e.width); |  | ||||||
|   } |   } | ||||||
|     this._width = w; | 
 | ||||||
|     this._height = y; |   render() { | ||||||
|     this.requestUpdate(); |     let branch_x = []; | ||||||
|  |     let total = 0; | ||||||
|  |     for (const c of Array.from(this.children)) { | ||||||
|  |       const rect = c.getBoundingClientRect(); | ||||||
|  |       branch_x.push(rect.width / 2 + total); | ||||||
|  |       total += rect.width; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const line_end = this.height - this._branch_height; | ||||||
|  | 
 | ||||||
|  |     return html` | ||||||
|  |       <svg width="${this.width}" height="${this.height}"> | ||||||
|  |         <rect | ||||||
|  |           x="0" | ||||||
|  |           y="0" | ||||||
|  |           width="${this.width}" | ||||||
|  |           height="${this.height}" | ||||||
|  |           fill="white" | ||||||
|  |         /> | ||||||
|  |         ${branch_x.map((x) => { | ||||||
|  |           return svg` | ||||||
|  |               <path | ||||||
|  |                 class="line" | ||||||
|  |                 d=" | ||||||
|  |                   M ${this.width / 2} 0 | ||||||
|  |                   C ${this.width / 2} ${this._branch_curve} | ||||||
|  |                     ${x} ${this._branch_height - this._branch_curve} | ||||||
|  |                     ${x} ${this._branch_height} | ||||||
|  |                   L ${x} ${line_end} | ||||||
|  |                   C ${x} ${line_end + this._branch_curve} | ||||||
|  |                     ${this.width / 2} ${this.height - this._branch_curve} | ||||||
|  |                     ${this.width / 2} ${this.height} | ||||||
|  |                 " | ||||||
|  |               /> | ||||||
|  |             `;
 | ||||||
|  |         })} | ||||||
|  |       </svg> | ||||||
|  |       <script-graph-node id="head" .icon=${"call-split"}> </script-graph-node> | ||||||
|  |       <div id="branches" style="top: ${this._branch_height}px;"> | ||||||
|  |         <slot @slotchange=${this.updateChildren}></slot> | ||||||
|  |       </div> | ||||||
|  |     `;
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static get styles() { | ||||||
|  |     return css` | ||||||
|  |       :host { | ||||||
|  |         position: relative; | ||||||
|  |         display: flex; | ||||||
|  |         --stroke-clr: var(--stroke-color, rgb(3, 169, 244)); | ||||||
|  |         --hover-clr: var(--hover-color, rgb(255, 152, 0)); | ||||||
|  |       } | ||||||
|  |       #branches { | ||||||
|  |         position: absolute; | ||||||
|  |         top: 20px; | ||||||
|  |         left: 0; | ||||||
|  |         display: flex; | ||||||
|  |         flex-direction: row; | ||||||
|  |         align-items: center; | ||||||
|  |       } | ||||||
|  |       path.line { | ||||||
|  |         stroke: var(--stroke-clr); | ||||||
|  |         stroke-width: 2; | ||||||
|  |         fill: white; | ||||||
|  |       } | ||||||
|  |       #head { | ||||||
|  |         position: Absolute; | ||||||
|  |         top: 5px; | ||||||
|  |         left: 50%; | ||||||
|  |         transform: translate(-50%, -50%); | ||||||
|  |       } | ||||||
|  |       :host(:focus-within) #head { | ||||||
|  |         --stroke-color: green; | ||||||
|  |       } | ||||||
|  |     `;
 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class ScriptGraph3 extends LitElement { | ||||||
|  |   @property() content = []; | ||||||
|  |   @property() _width = 0; | ||||||
|  |   @property() _height = 0; | ||||||
|  |   @property() _distance = 20; | ||||||
|  | 
 | ||||||
|  |   async updateChildren() { | ||||||
|  |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   childrenChangedCallback() { |   childrenChangedCallback() { | ||||||
|     console.log("Children changed"); |     console.log("Children changed"); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   get height() { | ||||||
|  |     let h = 0; | ||||||
|  |     for (const c of this.children) { | ||||||
|  |       h += (c as any).height ?? 0; | ||||||
|  |       h += this._distance; | ||||||
|  |     } | ||||||
|  |     return h + this._distance; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get width() { | ||||||
|  |     let w = 0; | ||||||
|  |     for (const c of this.children) { | ||||||
|  |       w = Math.max(w, (c as any).width ?? 0); | ||||||
|  |     } | ||||||
|  |     return w; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   render() { |   render() { | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     let y = 0; |  | ||||||
|     let nodes = []; |  | ||||||
|     for (const e of this.content) { |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return html` |     return html` | ||||||
|     <svg width=500 height=500> |       <svg width="${this.width}" height="${this.height}"> | ||||||
|       ${this.content.map(e => |         <rect | ||||||
|         svg` |           x="0" | ||||||
|           <g transform="translate(${this._width/2} ${e.offset_y})"> |           y="0" | ||||||
|           ${e.svg} |           width="${this.width}" | ||||||
|           </g> |           height="${this.height}" | ||||||
|         `)}
 |           fill="white" | ||||||
|  |         /> | ||||||
|  |         <path | ||||||
|  |           class="line" | ||||||
|  |           d=" | ||||||
|  |             M ${this.width / 2} 0 | ||||||
|  |             L ${this.width / 2} ${this.height} | ||||||
|  |           " | ||||||
|  |         /> | ||||||
|       </svg> |       </svg> | ||||||
|     <slot |       <div id="nodes" style="--distance: ${this._distance}px;"> | ||||||
|     @slotchange=${this.updateChildren} |         <slot @slotchange=${this.updateChildren}></slot> | ||||||
|     ></slot> |       </div> | ||||||
|     <style> |     `;
 | ||||||
|       slot { |  | ||||||
|         display: none; |  | ||||||
|       } |  | ||||||
|       </style> |  | ||||||
|     ` |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   static get styles() { | ||||||
|  |     return css` | ||||||
|  |       :host { | ||||||
|  |         position: relative; | ||||||
|  |         display: flex; | ||||||
|  |         --stroke-clr: var(--stroke-color, rgb(3, 169, 244)); | ||||||
|  |         --hover-clr: var(--hover-color, rgb(255, 152, 0)); | ||||||
|  |       } | ||||||
|  |       #nodes { | ||||||
|  |         position: absolute; | ||||||
|  |         top: var(--distance, 10px); | ||||||
|  |         left: 0; | ||||||
|  |         display: flex; | ||||||
|  |         flex-direction: column; | ||||||
|  |         align-items: center; | ||||||
|  |       } | ||||||
|  |       ::slotted(*) { | ||||||
|  |         padding-bottom: var(--distance, 10px); | ||||||
|  |       } | ||||||
|  |       path.line { | ||||||
|  |         stroke: var(--stroke-clr); | ||||||
|  |         stroke-width: 2; | ||||||
|  |         fill: white; | ||||||
|  |       } | ||||||
|  |     `;
 | ||||||
|  |   } | ||||||
| } | } | ||||||
| customElements.define("script-graph3", ScriptGraph3); | customElements.define("script-graph3", ScriptGraph3); | ||||||
| customElements.define("script-graph-node", ScriptGraphNode); | customElements.define("script-graph-node", ScriptGraphNode); | ||||||
|  | customElements.define("script-graph-branch", ScriptGraphBranch); | ||||||
|  | |||||||
| @ -1,18 +1,9 @@ | |||||||
| { | { | ||||||
|   "compilerOptions": { |   "compilerOptions": { | ||||||
|     "target": "es6", |     "target": "es2017", | ||||||
|     "module": "ESNext", |  | ||||||
|     "moduleResolution": "node", |     "moduleResolution": "node", | ||||||
|     "sourceMap": true, |     "resolveJsonModule": true, | ||||||
|     "emitDecoratorMetadata": true, |     "allowSyntheticDefaultImports": true, | ||||||
|     "experimentalDecorators": true, |     "experimentalDecorators": true | ||||||
|     "removeComments": false, |   } | ||||||
|     "noImplicitAny": false |  | ||||||
|   }, |  | ||||||
|   "include": [ |  | ||||||
|     "src/**/*.ts" |  | ||||||
|   ], |  | ||||||
|   "exclude": [ |  | ||||||
|     "node_modules" |  | ||||||
|   ] |  | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user