diff --git a/package-lock.json b/package-lock.json index c1e86eedc4..29c539c05f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@_unit/unit", - "version": "1.0.43", + "version": "1.0.44", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@_unit/unit", - "version": "1.0.43", + "version": "1.0.44", "license": "MIT", "dependencies": { "commander": "12.1.0", diff --git a/src/Class/Graph/index.ts b/src/Class/Graph/index.ts index 8a96c688a9..b3d3ebe752 100644 --- a/src/Class/Graph/index.ts +++ b/src/Class/Graph/index.ts @@ -985,7 +985,11 @@ export class Graph = any, O extends Dict = any> private _destroy = () => { this._spec = clone(this._spec) + this._destroying = true + forEachValueKey(this._unit, (u) => u.destroy()) + + this._destroying = false } private _reset = (): void => { @@ -3734,6 +3738,10 @@ export class Graph = any, O extends Dict = any> const data = pin.peak() const set = (data: any) => { + if (this._paused) { + return + } + this._fork() this._specSetUnitPinData(unitId, type, pinId, data) @@ -3760,7 +3768,7 @@ export class Graph = any, O extends Dict = any> all_unlisten.push(pin_unlisten) - if (data !== undefined && !this._paused) { + if (data !== undefined) { set(data) } } @@ -3778,6 +3786,10 @@ export class Graph = any, O extends Dict = any> all_unlisten.push( unit.addListener('destroy', () => { + if (this._destroying) { + return + } + if (this._removingUnit.has(unitId)) { return } @@ -4073,6 +4085,8 @@ export class Graph = any, O extends Dict = any> private _removingUnit: Set = new Set() + private _destroying: boolean + private _removeUnit( unitId: string, take: boolean = true, diff --git a/src/Primitive.ts b/src/Primitive.ts index 9680ebec76..3cdc67d2a2 100644 --- a/src/Primitive.ts +++ b/src/Primitive.ts @@ -88,6 +88,8 @@ export class Primitive< const { ref } = this.getInputOpt(name as keyof I) if (event === 'data') { + this._activateInput(name as keyof I, data) + if (ref) { this.__onRefInputData(name as keyof I, data) } else { @@ -288,13 +290,13 @@ export class Primitive< data: any, backpropagation: boolean ): void => { - this._activateInput(name, data) - if (backpropagation) { return } if (!this._paused) { + this._activateInput(name, data) + this.onDataInputData(name, data) } else { this.__buffer.push({ @@ -312,13 +314,13 @@ export class Primitive< data: any, backpropagation: boolean ): void => { - this._activateInput(name, data) - if (backpropagation) { return } if (!this._paused) { + this._activateInput(name, data) + this.__onRefInputData(name, data) } else { this.__buffer.push({ diff --git a/src/bundle.ts b/src/bundle.ts index 21c486b91f..a43e19cb18 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -116,6 +116,10 @@ function _bundleUnit( } _bundleUnit(bundle.unit, specs, custom, branch) + + if (bundle.specs) { + delete bundle.specs + } } } } diff --git a/src/client/component.ts b/src/client/component.ts index 946bf1b65b..094dca3bb5 100644 --- a/src/client/component.ts +++ b/src/client/component.ts @@ -1066,7 +1066,9 @@ export class Component< this.commit() } - this.dispatchEvent('mount', {}, false) + if (this.isBase()) { + this.dispatchEvent('mount', {}, false) + } } unmount() { @@ -1092,7 +1094,9 @@ export class Component< this.onUnmount($context) - this.dispatchEvent('unmount', {}, false) + if (this.isBase()) { + this.dispatchEvent('unmount', {}, false) + } } focus(options: FocusOptions | undefined = { preventScroll: true }) { diff --git a/src/client/platform/node/boot.ts b/src/client/platform/node/boot.ts index f7b92641d9..5da1ddb601 100644 --- a/src/client/platform/node/boot.ts +++ b/src/client/platform/node/boot.ts @@ -14,7 +14,7 @@ export function boot(opt?: BootOpt): [System, Unlisten] { const root = window.document.getElementById(SYSTEM_ROOT_ID) - window.fetch = fetch + window.fetch = globalThis.fetch window.HTMLCanvasElement.prototype.getContext = < T extends '2d' | 'webgl' | 'webgl2' | 'bitmaprenderer', >( diff --git a/src/client/render/attachStyle.ts b/src/client/render/attachStyle.ts index 4ce31e8fc6..2c06dd7d69 100644 --- a/src/client/render/attachStyle.ts +++ b/src/client/render/attachStyle.ts @@ -127,6 +127,7 @@ export const ROOT_STYLE = ` caret-color: currentcolor; outline-color: #00000000; outline-style: none; + interpolate-size: allow-keywords; } ` diff --git a/src/client/spec.ts b/src/client/spec.ts index 4161606845..a94181d928 100644 --- a/src/client/spec.ts +++ b/src/client/spec.ts @@ -307,7 +307,11 @@ export function findInputDataExamples( for (const subPinId in plug) { const subPin = plug[subPinId] - const { unitId, pinId, mergeId } = subPin + const { unitId, kind = 'input', pinId, mergeId } = subPin + + if (kind !== 'input') { + continue + } if (unitId && pinId) { const unit = units[unitId] diff --git a/src/spec/bundleClass.ts b/src/spec/bundleClass.ts index f247f9923a..9d714e0c69 100644 --- a/src/spec/bundleClass.ts +++ b/src/spec/bundleClass.ts @@ -4,6 +4,7 @@ import { UnitBundle } from '../types/UnitBundle' import { UnitBundleSpec } from '../types/UnitBundleSpec' import { UnitClass } from '../types/UnitClass' import { parseMemorySpec } from './evaluate/parseMemorySpec' +import { remapSpecs } from './remapBundle' export function bundleClass( Class: UnitClass, @@ -18,7 +19,11 @@ export function bundleClass( static __bundle = bundle constructor(system: System) { - bundle.specs && system.injectSpecs(bundle.specs) + if (bundle.specs) { + const map = system.injectSpecs(bundle.specs) + + remapSpecs(bundle, map) + } super(system, id) diff --git a/src/spec/fromSpec.ts b/src/spec/fromSpec.ts index 56ef375754..8d58cf42de 100644 --- a/src/spec/fromSpec.ts +++ b/src/spec/fromSpec.ts @@ -1,78 +1,15 @@ +import { unitBundleSpec } from '../bundle' import { Graph } from '../Class/Graph' import { System } from '../system' import { Classes, PinSpec, Specs } from '../types' import { Dict } from '../types/Dict' import { GraphBundle, GraphClass } from '../types/GraphClass' import { GraphSpec } from '../types/GraphSpec' -import { GraphSpecs } from '../types/GraphSpecs' import { GraphUnitPinSpec } from '../types/GraphUnitPinSpec' import { GraphUnitSpec } from '../types/GraphUnitSpec' import { io } from '../types/IOOf' -import { deepGet } from '../util/object' import { weakMerge } from '../weakMerge' import { bundleClass } from './bundleClass' -import { evaluateDataValue } from './evaluateDataValue' - -export function extractGraphSpecs( - spec: GraphSpec, - specs: Specs, - graphs: GraphSpecs = {}, - classes: Classes = {} -): GraphSpecs { - graphs[spec.id] = spec - - const { units } = spec - - for (const unit_id in units) { - const unit = units[unit_id] - - const { id, input } = unit - - for (const inputId in input) { - const _input = input[inputId] ?? {} - - const { data } = _input - - if (data !== undefined) { - const dataRef = evaluateDataValue(data, specs, classes) - - for (const path of dataRef.ref ?? []) { - const bundle = deepGet(dataRef.data, path) - - for (const specId in bundle.specs) { - const spec = bundle.specs[specId] - - graphs[specId] = spec - } - - for (const specId in bundle.specs) { - const spec = bundle.specs[specId] - - extractGraphSpecs(spec, weakMerge(specs, graphs), graphs) - } - } - } - } - - const unit_spec = specs[id] - - if (!unit_spec) { - return - } - - const { system } = unit_spec - - if (!system) { - if (!graphs[id]) { - graphs[id] = unit_spec as GraphSpec - - extractGraphSpecs(graphs[id], specs, graphs) - } - } - } - - return graphs -} export function fromSpec = any, O extends Dict = any>( spec: GraphSpec, @@ -88,11 +25,9 @@ export function fromSpec = any, O extends Dict = any>( throw new Error('spec id is required') } - const specs = extractGraphSpecs(spec, specs_, {}) - - const unit = { id } + const bundle = unitBundleSpec({ id }, weakMerge({ [id]: spec }, specs_)) - const Bundle = bundleClass(Class, { unit, specs }) + const Bundle = bundleClass(Class, bundle) return Bundle } @@ -156,7 +91,7 @@ export function classFromSpec( class Class extends Graph { constructor(system: System) { - const spec = system.getSpec(id) as GraphSpec + const spec = specs[id] as GraphSpec super(spec, branch, system, id) } diff --git a/src/spec/type.ts b/src/spec/type.ts index 45a0c5c916..3bb6a88f75 100644 --- a/src/spec/type.ts +++ b/src/spec/type.ts @@ -195,11 +195,13 @@ export const _getGraphTypeInterface = ( undefined ) } else if (unitId && pinId) { - subPinType = deepGetOrDefault( - unitTypeMap, - [unitId, kind, pinId], - undefined - ) + if (kind === 'input') { + subPinType = deepGetOrDefault( + unitTypeMap, + [unitId, kind, pinId], + undefined + ) + } } if (subPinType) { diff --git a/src/system/platform/component/app/Editor/Component.ts b/src/system/platform/component/app/Editor/Component.ts index d0ab683ad2..f7ceff88d2 100644 --- a/src/system/platform/component/app/Editor/Component.ts +++ b/src/system/platform/component/app/Editor/Component.ts @@ -157,6 +157,7 @@ import { getCircle, getLine, getRectangle } from '../../../../../client/drawing' import { IODragEvent } from '../../../../../client/event/drag' import { makeDragEnterListener } from '../../../../../client/event/drag/dragenter' import { makeDragLeaveListener } from '../../../../../client/event/drag/dragleave' +import { makeDragOverListener } from '../../../../../client/event/drag/dragover' import { makeDragStartListener } from '../../../../../client/event/drag/dragstart' import { makeDropListener } from '../../../../../client/event/drag/drop' import { makeBlurListener } from '../../../../../client/event/focus/blur' @@ -318,6 +319,7 @@ import { randomUnitVector, resizeVector, roundPoint, + subtractVector, surfaceDistance, unitVector, } from '../../../../../client/util/geometry' @@ -647,6 +649,7 @@ import { last, pull, push, + remove, } from '../../../../../util/array' import { randomInArray } from '../../../../../util/array/randomInArray' import { bit } from '../../../../../util/boolean' @@ -2582,6 +2585,10 @@ export class Editor_ extends Element { private _drag_node_id: Dict = {} private _drag_node_pointer_id: Dict = {} + private _drag_along_node: Dict = {} + private _drag_along_source: Dict = {} + private _drag_along_relative_position: Dict> = {} + private _drag_ext_node_id: Set = new Set() private _drag_ext_node_count: number = 0 @@ -2901,6 +2908,7 @@ export class Editor_ extends Element { private _green_drag: boolean = false private _green_drag_node_id: string | null = null private _green_drag_clone_id: string | null = null + private _green_drag_actions: Action[] = [] private _yellow_drag: boolean = false private _yellow_drag_node_id: string | null = null @@ -3275,6 +3283,7 @@ export class Editor_ extends Element { onLongClickCancel: this._on_long_click_cancel, onClickHold: this._on_click_hold, }), + makeDragOverListener(this._on_drag_over), makeDropListener(this._on_drop), ]) @@ -3322,9 +3331,6 @@ export class Editor_ extends Element { main.registerParentRoot(layout_comp) main.registerParentRoot(area_select_svg) - main.$element.ondragover = (event: DragEvent) => { - event.preventDefault() - } this._main = main const foreground = new Div( @@ -3391,6 +3397,10 @@ export class Editor_ extends Element { private _last_open_filename: string + private _on_drag_over = async (event, _event: DragEvent) => { + _event.preventDefault() + } + private _on_drop = async (event, _event: DragEvent) => { _event.preventDefault() @@ -3450,7 +3460,7 @@ export class Editor_ extends Element { if (handle) { if (handle.kind === 'directory') { - this._drop_folder(handle, screen_position) + this._drop_file_system_directory_handle(handle, screen_position) return } @@ -3458,10 +3468,10 @@ export class Editor_ extends Element { } if (item.webkitGetAsEntry) { - const entry = item.webkitGetAsEntry() as FileSystemDirectoryEntry + const entry = item.webkitGetAsEntry() as FileSystemEntry if (entry) { - this._drop_directory_entry(entry) + this._drop_file_system_entry(entry, position, screen_position) return } @@ -3483,38 +3493,63 @@ export class Editor_ extends Element { } } - private _drop_directory_entry = (entry: FileSystemDirectoryEntry) => { + private _drop_file_system_entry = ( + entry: FileSystemEntry, + position: Position, + screen_position: Position + ) => { if (entry.isDirectory) { - const reader = entry.createReader() + this._animate_pulse(screen_position.x, screen_position.y, 'out') - reader.readEntries( - (entries) => { - this._paste_entries(entries) - }, - (err: any) => { - // - } - ) + this._drop_file_system_directory_entry(entry as FileSystemDirectoryEntry) } else if (entry.isFile) { + this._drop_file_system_file_entry(entry as FileSystemFileEntry, position) } else { - // + throw new InvalidStateError() } } - private _drop_folder = async ( + private _drop_file_system_directory_entry = ( + entry: FileSystemDirectoryEntry + ) => { + const reader = entry.createReader() + + reader.readEntries( + (entries) => { + this._paste_file_system_directory_entries(entries) + }, + (err: any) => { + // + } + ) + } + + private _drop_file_system_file_entry = ( + entry: FileSystemFileEntry, + position: Position + ) => { + entry.file( + async (file: File) => { + this._drop_file(file, position) + }, + (err: any) => { + // + } + ) + } + + private _drop_file_system_directory_handle = async ( dirHandle: FileSystemDirectoryHandle, screenPosition: Position ) => { this._animate_pulse(screenPosition.x, screenPosition.y, 'out') - this._paste_folder(dirHandle) + await this._paste_file_system_directory_handle(dirHandle) } - private _paste_folder = async (dirHandle: FileSystemDirectoryHandle) => { - await this.__paste_folder(dirHandle) - } - - private __paste_folder = async (dirHandle: FileSystemDirectoryHandle) => { + private _paste_file_system_directory_handle = async ( + dirHandle: FileSystemDirectoryHandle + ) => { // @ts-ignore if (dirHandle.entries) { // @ts-ignore @@ -3541,7 +3576,9 @@ export class Editor_ extends Element { remapBundle(bundle_, specIdMap) } } else if (handle.kind === 'directory') { - this.__paste_folder(handle as FileSystemDirectoryHandle) + this._paste_file_system_directory_handle( + handle as FileSystemDirectoryHandle + ) } else { throw new InvalidStateError() } @@ -3549,7 +3586,9 @@ export class Editor_ extends Element { } } - private _paste_entries = async (entries: FileSystemEntry[]) => { + private _paste_file_system_directory_entries = async ( + entries: FileSystemEntry[] + ) => { for (const entry of entries) { if (entry.isFile) { ;(entry as FileSystemFileEntry).file( @@ -3569,7 +3608,9 @@ export class Editor_ extends Element { } ) } else if (entry.isDirectory) { - this._drop_directory_entry(entry as FileSystemDirectoryEntry) + this._drop_file_system_directory_entry( + entry as FileSystemDirectoryEntry + ) } else { throw new InvalidStateError() } @@ -3821,7 +3862,7 @@ export class Editor_ extends Element { const bundle = await this._read_bundle_file(file) if (bundle) { - this.paste_bundle(bundle, position) + this.paste_bundle(bundle, position, true, true) } else { throw new Error('invalid bundle file') } @@ -3837,7 +3878,11 @@ export class Editor_ extends Element { let json: string if (file.name.endsWith('.unit')) { - json = await file.text() + try { + json = await file.text() + } catch (err) { + return null + } } else if (file.name.endsWith('.unit.gzip')) { const decompressionStream = new DecompressionStream('gzip') const decompressedStream = file @@ -9151,7 +9196,15 @@ export class Editor_ extends Element { sub_pin_spec: GraphSubPinSpec, position: { int?: Position; ext?: Position } = {} ): void => { - // console.log('Graph', '_sim_add_exposed_pin', type, pin_id, sub_pin_id, sub_pin_spec, position) + // console.log( + // 'Graph', + // '_sim_add_exposed_pin', + // type, + // pin_id, + // sub_pin_id, + // sub_pin_spec, + // position + // ) const { config = {} } = this.$props @@ -13487,6 +13540,40 @@ export class Editor_ extends Element { this.__set_node_mode_color(node_id, this._mode) } + private _is_link_pin_displayed = (pin_node_id: string): boolean => { + const { unitId } = segmentLinkPinNodeId(pin_node_id) + + return ( + this._is_node_displayed(pin_node_id) || this._is_node_displayed(unitId) + ) + } + + private _is_node_displayed = (node_id: string): boolean => { + if (this._is_node_hovered(node_id) || this._is_node_dragged(node_id)) { + return true + } else if (this._is_node_selected(node_id)) { + if (this._hover_node_count > 0) { + let hover_selected_count = 0 + + for (const hover_node_id in this._hover_node_id) { + if (this._is_node_selected(hover_node_id)) { + hover_selected_count += 1 + + break + } + } + + if (hover_selected_count > 0) { + return true + } + + return false + } + + return true + } + } + private __set_node_mode_color = (node_id: string, mode: Mode): void => { // console.log('Graph', '__set_node_mode_color', node_id, mode) @@ -13495,120 +13582,103 @@ export class Editor_ extends Element { const color = this._get_color() if (this.__is_mode_colored(mode)) { - if (this._is_node_mode_colorable(node_id, mode)) { - const mode_color = getThemeModeColor($theme, mode, color) - const mode_link_color = getThemeLinkModeColor($theme, mode) + // if (this._is_node_mode_colorable(node_id, mode)) { + const mode_color = getThemeModeColor($theme, mode, color) + const mode_link_color = getThemeLinkModeColor($theme, mode) - const mode_text_color = mode_link_color - const mode_pin_icon_color = mode_color + const mode_text_color = mode_link_color + const mode_pin_icon_color = mode_color - if (this._is_unit_node_id(node_id)) { - const unit_id = node_id - - this._set_unit_color( - unit_id, - mode_color, - mode_link_color, - mode_text_color, - mode_pin_icon_color - ) - - if (mode === 'remove') { - if (this._err[unit_id]) { - const err_node_id = getErrNodeId(unit_id) - - this._set_err_color(err_node_id, mode_color) - } + if (this._is_unit_node_id(node_id)) { + const unit_id = node_id - this._for_each_unit_pin(unit_id, (pin_node_id) => { - const datum_node_id = this._pin_to_datum[pin_node_id] + this._set_unit_color( + unit_id, + mode_color, + mode_link_color, + mode_text_color, + mode_pin_icon_color + ) - if (datum_node_id) { - this._set_datum_color( - datum_node_id, - mode_color, - mode_link_color - ) - } - }) - } else if (mode === 'data') { - this._for_each_unit_pin(unit_id, (pin_node_id) => { - const datum_node_id = this._get_pin_datum_node_id(pin_node_id) + if (mode === 'remove') { + if (this._err[unit_id]) { + const err_node_id = getErrNodeId(unit_id) - if (datum_node_id) { - this._set_datum_color(datum_node_id, mode_color, mode_color) - } - }) + this._set_err_color(err_node_id, mode_color) } - } else if (this._is_link_pin_node_id(node_id)) { - this._set_link_pin_color( - node_id, - mode_color, - mode_link_color, - mode_text_color, - mode_pin_icon_color - ) - if (mode === 'remove') { - const datum_node_id = this._pin_to_datum[node_id] + + this._for_each_unit_pin(unit_id, (pin_node_id) => { + const datum_node_id = this._pin_to_datum[pin_node_id] if (datum_node_id) { this._set_datum_color(datum_node_id, mode_color, mode_link_color) } - } - } else if (this._is_merge_node_id(node_id)) { - if (mode === 'data') { - this._set_merge_input_color(node_id, mode_color) - - const merge_inputs = this._merge_to_input[node_id] + }) + } else if (mode === 'data') { + this._for_each_unit_pin(unit_id, (pin_node_id) => { + const datum_node_id = this._get_pin_datum_node_id(pin_node_id) - for (const input_node_id in merge_inputs) { - this._set_link_pin_link_color(input_node_id, mode_link_color) + if (datum_node_id) { + this._set_datum_color(datum_node_id, mode_color, mode_color) } - } else { - const merge_inputs = this._merge_to_input[node_id] - for (const input_node_id in merge_inputs) { - const { unitId } = segmentLinkPinNodeId(input_node_id) + }) + } + } else if (this._is_link_pin_node_id(node_id)) { + this._set_link_pin_color( + node_id, + mode_color, + mode_link_color, + mode_text_color, + mode_pin_icon_color + ) + if (mode === 'remove') { + const datum_node_id = this._pin_to_datum[node_id] - if ( - this._is_node_hovered(input_node_id) || - this._is_node_hovered(unitId) || - this._is_node_selected(input_node_id) || - this._is_node_selected(unitId) - ) { - this._set_merge_input_color(node_id, mode_color) + if (datum_node_id) { + this._set_datum_color(datum_node_id, mode_color, mode_link_color) + } + } + } else if (this._is_merge_node_id(node_id)) { + if (mode === 'data') { + this._set_merge_input_color(node_id, mode_color) - break - } - } - const merge_outputs = this._merge_to_output[node_id] - for (const output_node_id in merge_outputs) { - const { unitId } = segmentLinkPinNodeId(output_node_id) - if ( - this._is_node_hovered(output_node_id) || - this._is_node_hovered(unitId) || - this._is_node_selected(output_node_id) || - this._is_node_selected(unitId) - ) { - this._set_merge_output_color(node_id, mode_color) + const merge_inputs = this._merge_to_input[node_id] - break - } + for (const input_node_id in merge_inputs) { + this._set_link_pin_link_color(input_node_id, mode_link_color) + } + } else { + const merge_inputs = this._merge_to_input[node_id] + for (const input_node_id in merge_inputs) { + if (this._is_link_pin_displayed(input_node_id)) { + this._set_merge_input_color(node_id, mode_color) + + break } } - } else if (this._is_datum_node_id(node_id)) { - this._set_datum_color(node_id, mode_color, mode_link_color) - } else if (this._is_err_node_id(node_id)) { - this._set_err_color(node_id, mode_color) - } else if (this._is_plug_node_id(node_id)) { - const { type, pinId, subPinId } = segmentPlugNodeId(node_id) + const merge_outputs = this._merge_to_output[node_id] + for (const output_node_id in merge_outputs) { + if (this._is_link_pin_displayed(output_node_id)) { + this._set_merge_output_color(node_id, mode_color) - if (mode === 'change') { - this._set_exposed_pin_set_color(type, pinId, mode_color) - } else { - this._set_exposed_sub_pin_color(type, pinId, subPinId, mode_color) + break + } } } + } else if (this._is_datum_node_id(node_id)) { + this._set_datum_color(node_id, mode_color, mode_link_color) + } else if (this._is_err_node_id(node_id)) { + this._set_err_color(node_id, mode_color) + } else if (this._is_plug_node_id(node_id)) { + const { type, pinId, subPinId } = segmentPlugNodeId(node_id) + + if (mode === 'change') { + this._set_exposed_pin_set_color(type, pinId, mode_color) + } else { + this._set_exposed_sub_pin_color(type, pinId, subPinId, mode_color) + } } + // } } } @@ -13652,7 +13722,7 @@ export class Editor_ extends Element { this._for_each_unit_pin(node_id, (pin_node_id, type) => { const datum_node_id = this._pin_to_datum[pin_node_id] if (datum_node_id) { - this._reset_datum_color(datum_node_id) + this._refresh_datum_color(datum_node_id) } const { pinId, subPinId } = @@ -13810,7 +13880,7 @@ export class Editor_ extends Element { if (merge_node_id) { const merge = this._get_merge(merge_node_id) - this._reset_link_pin_link_color(pin_node_id) + this._refresh_link_pin_color(pin_node_id) const merge_unit_id = this._merge_to_ref_unit[merge_node_id] const merge_output_ref = this._merge_to_ref_output[merge_node_id] @@ -13821,13 +13891,13 @@ export class Editor_ extends Element { if (merge_unit_id) { // } else if (merge_output_ref === pin_node_id) { - this._reset_link_pin_color(pin_node_id) + // } else { - this._reset_merge_pin_pin_color(merge_node_id, type) + this._refresh_merge_pin_pin_color(merge_node_id, type) } } } else { - this._reset_link_pin_color(pin_node_id) + this._refresh_link_pin_color(pin_node_id) } }) @@ -13851,23 +13921,13 @@ export class Editor_ extends Element { private _refresh_merge_pin_pin_color = (merge_node_id: string, type: IO) => { if (this._is_mode_colored()) { - if ( - this._is_node_hovered(merge_node_id) || - this._is_node_selected(merge_node_id) - ) { + if (this._is_node_displayed(merge_node_id)) { this._refresh_node_color(merge_node_id) } else { const merge_input_node_ids = this._merge_to_input[merge_node_id] for (const merge_input_node_id in merge_input_node_ids) { - const { unitId } = segmentLinkPinNodeId(merge_input_node_id) - - if ( - this._is_node_hovered(merge_input_node_id) || - this._is_node_selected(merge_input_node_id) || - this._is_node_hovered(unitId) || - this._is_node_selected(unitId) - ) { + if (this._is_link_pin_displayed(merge_input_node_id)) { this._refresh_node_color(merge_node_id) return @@ -14411,13 +14471,7 @@ export class Editor_ extends Element { private _refresh_link_pin_link_color = (pin_node_id: string): void => { // console.log('Graph', '_refresh_link_pin_link_color') if (this._is_mode_colored()) { - const { unitId } = segmentLinkPinNodeId(pin_node_id) - if ( - this._is_node_selected(pin_node_id) || - this._is_node_hovered(pin_node_id) || - this._is_node_selected(unitId) || - this._is_node_hovered(unitId) - ) { + if (this._is_link_pin_displayed(pin_node_id)) { this._refresh_node_color(pin_node_id) } else { this._reset_link_pin_link_color(pin_node_id) @@ -15429,6 +15483,7 @@ export class Editor_ extends Element { style: { strokeWidth: `${strokeWidth}`, stroke: COLOR_NONE, + ...userSelect('none'), }, }, this.$system @@ -15445,6 +15500,7 @@ export class Editor_ extends Element { style: { strokeWidth: '0px', }, + ...userSelect('none'), }, this.$system ) @@ -17748,7 +17804,7 @@ export class Editor_ extends Element { const _position = this._screen_to_world(position.x, position.y) - this.paste_bundle(bundle, _position) + this.paste_bundle(bundle, _position, true, true) this._cancel_drag_and_drop() @@ -21900,6 +21956,7 @@ export class Editor_ extends Element { node.fx = x - node.hx node.fy = y - node.hy + node.x = node.fx node.y = node.fy @@ -21916,18 +21973,7 @@ export class Editor_ extends Element { const node_drag_max_distance = this._node_drag_max_distance[node_id] - this._on_node_drag_end(node_id) - - this._set_drag_node(node_id, false) - - this._stop_drag_node_static(node_id) - - if (this._is_ext_node_id(node_id)) { - const int_node_id = getIntNodeIdFromExtNodeId(node_id) - - this._delete_all_node_to_node_charge(node_id) - this._delete_all_node_to_node_charge(int_node_id) - } + this.__on_node_drag_end(node_id) if (this._is_droppable_mode()) { if (node_drag_max_distance > MIN_DRAG_DROP_MAX_D) { @@ -21939,9 +21985,27 @@ export class Editor_ extends Element { } } + this._refresh_node_color(node_id) this._refresh_compatible() } + private __on_node_drag_end = (node_id: string) => { + // console.log('Graph', '__on_node_drag_end', node_id) + + this._on_node_drag_end(node_id) + + this._set_drag_node(node_id, false) + + this._stop_drag_node_static(node_id) + + if (this._is_ext_node_id(node_id)) { + const int_node_id = getIntNodeIdFromExtNodeId(node_id) + + this._delete_all_node_to_node_charge(node_id) + this._delete_all_node_to_node_charge(int_node_id) + } + } + private _on_node_drag_end = (node_id: string) => { // console.log('Graph', '__on_node_drag_end', node_id) @@ -21959,6 +22023,15 @@ export class Editor_ extends Element { delete this._node_drag_max_distance[node_id] delete this._node_drag_init_position[node_id] + + const drag_along_source = clone(this._drag_along_source[node_id]) + + if (drag_along_source) { + this._remove_node_drag_along(drag_along_source, node_id) + } + + delete this._drag_along_node[node_id] + delete this._drag_along_relative_position[node_id] } private _start_drag_node_static = (node_id: string): void => { @@ -23428,7 +23501,7 @@ export class Editor_ extends Element { } private _is_node_dragged = (node_id: string): boolean => { - return !!this._drag_node_id[node_id] + return !!this._drag_node_id[node_id] || !!this._drag_along_source[node_id] } private _is_node_ascend = (node_id: string): boolean => { @@ -25161,10 +25234,6 @@ export class Editor_ extends Element { ): boolean => { const { config } = this.$props - // if (this._drag_node_id[pin_node_id]) { - // return false - // } - if (config?.disable?.dataCompatible) { return false } @@ -25177,17 +25246,6 @@ export class Editor_ extends Element { ) { return false } - // if (this._is_node_selected(pin_node_id)) { - // if (datum_pin_node_id === pin_node_id) { - // return false - // } - - // const datum_pin_merge_node_id = this._pin_to_merge[datum_pin_node_id] - - // if (datum_pin_merge_node_id === pin_node_id) { - // return false - // } - // } } if (this._is_pin_node_ref(pin_node_id)) { @@ -27135,14 +27193,18 @@ export class Editor_ extends Element { return null } - private _state_duplicate_unit = (unit_id: string): string => { + private _state_duplicate_unit = ( + unit_id: string, + new_unit_id?: string + ): string => { // console.log('Graph', '_state_duplicate_unit', unit_id) const unit = this._get_unit(unit_id) const { id } = unit - const new_unit_id = this._new_unit_id(id) + new_unit_id = new_unit_id ?? this._new_unit_id(id) + const new_unit = clone(unit) const unit_position = this._get_node_position(unit_id) @@ -36547,13 +36609,7 @@ export class Editor_ extends Element { private _refresh_link_pin_color = (pin_node_id: string) => { // console.log('Graph', '_refresh_link_pin_color', pin_node_id) - const { unitId } = segmentLinkPinNodeId(pin_node_id) - if ( - this._is_node_hovered(pin_node_id) || - this._is_node_selected(pin_node_id) || - this._is_node_hovered(unitId) || - this._is_node_selected(unitId) - ) { + if (this._is_link_pin_displayed(pin_node_id)) { this._refresh_node_color(pin_node_id) } else { this._reset_link_pin_color(pin_node_id) @@ -41474,6 +41530,8 @@ export class Editor_ extends Element { clientX: number, clientY: number ) => { + // console.log('Graph', '__drag_start', node_id, pointerId) + const layer = this._get_node_default_layer(node_id) this._start_graph_simulation(clamp(layer - 1, 0, Infinity)) @@ -41494,6 +41552,27 @@ export class Editor_ extends Element { } } + if (this._drag_along_node[node_id]) { + for (const drag_along_node_id of this._drag_along_node[node_id]) { + if ((this._node_pressed_count[drag_along_node_id] ?? 0) === 0) { + this._drag_node_pointer_id[drag_along_node_id] = pointerId + + const relative = deepGet(this._drag_along_relative_position, [ + node_id, + drag_along_node_id, + ]) + + const node = this._node[node_id] + + this.__on_node_drag_start( + drag_along_node_id, + x + relative.x - node.hx, + y + relative.y - node.hy + ) + } + } + } + if (this._is_ext_node_id(node_id)) { this._set_ext_node_compatible_charge(node_id) } else if (this._is_int_node_id(node_id)) { @@ -41786,6 +41865,35 @@ export class Editor_ extends Element { } } } + + if (this._drag_along_node[node_id]) { + for (const drag_along_node_id of this._drag_along_node[node_id]) { + if ((this._node_pressed_count[drag_along_node_id] ?? 0) === 0) { + const relative = deepGet(this._drag_along_relative_position, [ + node_id, + drag_along_node_id, + ]) + + const node = this._node[node_id] + + if (this._drag_node_pointer_id[drag_along_node_id] !== pointerId) { + this._drag_node_pointer_id[drag_along_node_id] = pointerId + + this.__on_node_drag_start( + drag_along_node_id, + x + relative.x - node.hx, + y + relative.y - node.hy + ) + } + + this._node_drag_move( + drag_along_node_id, + x + relative.x - node.hx, + y + relative.y - node.hy + ) + } + } + } } if (this._is_datum_node_id(node_id)) { @@ -41877,15 +41985,247 @@ export class Editor_ extends Element { return new_node_id } + private _set_node_drag_along = ( + node_id: string, + drag_along_node_id: string + ): void => { + this._drag_along_node[node_id] = this._drag_along_node[node_id] ?? [] + this._drag_along_node[node_id].push(drag_along_node_id) + + this._drag_along_source[drag_along_node_id] = node_id + } + + private _remove_node_drag_along = ( + node_id: string, + drag_along_node_id: string + ): void => { + remove(this._drag_along_node[node_id], drag_along_node_id) + + delete this._drag_along_source[drag_along_node_id] + } + private _on_node_green_drag_start = ( node_id: string, event: UnitPointerEvent ): string | null => { // console.log('Graph', '_on_node_green_drag_start', node_id) + const { specs } = this.$props + const { pointerId, clientX, clientY } = event - const new_node_id: string | null = this._state_duplicate_node(node_id) + let new_node_id: string | null = null + let new_node_ids: string[] = [] + + if ( + this._is_node_selected(node_id) && + (this._is_node_id(node_id) || this._is_plug_node_id(node_id)) + ) { + const selected_node_ids = keys(this._selected_node_id) + + const node_position = this._get_node_position(node_id) + + const new_bundle = this._sub_graph_selection(selected_node_ids, false) + + this._stop_graph_simulation() + + const { map_unit_id, map_merge_id, map_plug_id, map_datum_id, actions } = + this.paste_bundle(new_bundle, this._screen_center(), false, false) + + this._green_drag_actions = actions + + const new_node_id_map = {} + + new_node_ids = selected_node_ids.map((selected_node_id) => { + return this._node_type__template(selected_node_id, { + unit: (unit_id: string) => { + const new_unit_id = map_unit_id[unit_id] + + if (!new_unit_id) { + return null + } + + new_node_id_map[selected_node_id] = new_unit_id + + return new_unit_id + }, + link: (pin_node_id: string) => { + const { unitId, type, pinId } = segmentLinkPinNodeId(pin_node_id) + + if (!selected_node_ids.includes(unitId)) { + return null + } + + const new_unit_id = map_unit_id[unitId] + + if (!new_unit_id) { + return null + } + + const new_pin_node_id = getPinNodeId(new_unit_id, type, pinId) + + new_node_id_map[selected_node_id] = new_pin_node_id + + return new_pin_node_id + }, + merge: (merge_node_id: string) => { + const { mergeId } = segmentMergeNodeId(merge_node_id) + + const new_merge_id = map_merge_id[mergeId] + + if (!new_merge_id) { + return null + } + + const new_merge_node_id = getMergeNodeId(new_merge_id) + + new_node_id_map[selected_node_id] = new_merge_node_id + + return new_merge_node_id + }, + plug: (plug_node_id: string) => { + const { type, pinId, subPinId } = segmentPlugNodeId(plug_node_id) + + const new_plug_sub_pin_id = map_plug_id[type][pinId][subPinId] + + if (!new_plug_sub_pin_id) { + return null + } + + const new_plug_node_id = getExtNodeId( + type, + pinId, + new_plug_sub_pin_id + ) + + new_node_id_map[selected_node_id] = new_plug_node_id + + return new_plug_node_id + }, + datum: (datum_node_id: string) => { + const { datumId } = segmentDatumNodeId(datum_node_id) + + const new_datum_id = map_datum_id[datumId] + + if (new_datum_id) { + const new_datum_node_id = getDatumNodeId(new_datum_id) + + new_node_id_map[selected_node_id] = new_datum_node_id + + return new_datum_node_id + } + + return null + }, + err: (err_node_id: string) => { + return null + }, + }) + }) + + new_node_ids = new_node_ids.filter((n) => !!n) + + new_node_id = new_node_id_map[node_id] ?? node_id + + const drag = (selected_node_id: string, next_node_id: string) => { + if (!this._has_node(next_node_id)) { + return + } + + const selected_node_position = + this._get_anchor_node_position(selected_node_id) + + const relative_position = subtractVector( + selected_node_position, + node_position + ) + + deepSet( + this._drag_along_relative_position, + [new_node_id, next_node_id], + relative_position + ) + + const next_position = selected_node_position + + this._set_node_position(next_node_id, next_position) + + if (selected_node_id !== node_id) { + this._set_node_drag_along(new_node_id, next_node_id) + + this._drag_node_pointer_id[next_node_id] = pointerId + + this.__on_node_drag_start( + next_node_id, + next_position.x, + next_position.y + ) + } + + this._set_node_fixed(next_node_id, true) + } + + for (const selected_node_id of selected_node_ids) { + const next_node_id = new_node_id_map[selected_node_id] + + if (!next_node_id) { + continue + } + + drag(selected_node_id, next_node_id) + + if (this._is_unit_node_id(selected_node_id)) { + this._for_each_unit_pin( + selected_node_id, + (pin_node_id, type, pin_id) => { + if (!this._is_node_selected(pin_node_id)) { + const next_pin_node_id = getPinNodeId( + next_node_id, + type, + pin_id + ) + + drag(pin_node_id, next_pin_node_id) + } + } + ) + } + + this._ascend_node(next_node_id, pointerId) + } + + remove(new_node_ids, new_node_id) + } else { + new_node_id = this._state_duplicate_node(node_id) + new_node_ids = [new_node_id] + + this._ascend_node(new_node_id, pointerId) + + if (this._is_unit_node_id(node_id)) { + const _unit = this._get_unit(node_id) + + const actions = [] + + const parent_id = this._spec_get_sub_component_parent_id(new_node_id) + + const unit = clone(_unit) + + const bundle = unitBundleSpec(unit, specs) + + actions.push( + makeAddUnitAction( + new_node_id, + bundle, + undefined, + undefined, + undefined, + parent_id + ) + ) + + this._green_drag_actions = actions + } + } if (new_node_id) { this._green_drag = true @@ -41899,10 +42239,10 @@ export class Editor_ extends Element { clientX, clientY ) + } - this._ascend_node(new_node_id, pointerId) - - this._refresh_node_color(node_id) + for (const new_node_id of new_node_ids) { + this._refresh_node_color(new_node_id) this._refresh_all_selected_node_color() } @@ -41913,8 +42253,6 @@ export class Editor_ extends Element { cloned_node_id: string, node_id: string ) => { - const { specs } = this.$props - if (this._is_unit_node_id(node_id)) { const unit_id = node_id @@ -41922,22 +42260,7 @@ export class Editor_ extends Element { const unit = clone(_unit) - const bundle = unitBundleSpec(unit, specs) - - const actions = [] - - const parent_id = this._spec_get_sub_component_parent_id(cloned_node_id) - - actions.push( - makeAddUnitAction( - unit_id, - bundle, - undefined, - undefined, - undefined, - parent_id - ) - ) + const actions = this._green_drag_actions this._dispatch_add_unit_action(unit_id, unit) @@ -41949,9 +42272,15 @@ export class Editor_ extends Element { } } + if (this._drag_along_node[node_id]) { + for (const drag_along_node_id of this._drag_along_node[node_id]) { + } + } + this._green_drag = false this._green_drag_node_id = null this._green_drag_clone_id = null + this._green_drag_actions = [] } private _on_node_red_drag_start = ( @@ -42775,9 +43104,6 @@ export class Editor_ extends Element { this._negate_node_layer(node_id) this._ascend_node_z(node_id) - // reset pointer capture (probably because node was moved in the DOM) - this._set_node_pointer_capture(node_id, pointer_id) - if (this._is_unit_node_id(node_id)) { const ascend_pin = (pin_node_id: string, type: IO) => { if (!this._is_link_pin_merged(pin_node_id)) { @@ -42978,12 +43304,22 @@ export class Editor_ extends Element { // delay "drag start" conditioning it to pointer's "first move" if (!this._drag_node_id[pressed_node_id]) { if (this._is_freeze_mode()) { + const pointer_down_position = + this._pointer_down_position[pointerId] + + const pointer_screen_position = { + x: clientX, + y: clientY, + } + + const delta = subtractVector( + pointer_screen_position, + pointer_down_position + ) + const d = pointDistance( - this._pointer_down_position[pointerId], - { - x: clientX, - y: clientY, - } + pointer_down_position, + pointer_screen_position ) if (d < POINTER_CLICK_RADIUS) { @@ -44284,6 +44620,14 @@ export class Editor_ extends Element { this._is_plug_node_id(pressed_node_id) ) { this._descend_node(pressed_node_id) + + if (this._drag_along_node[pressed_node_id]) { + for (const drag_along_node_id of this._drag_along_node[ + pressed_node_id + ]) { + this._descend_node(drag_along_node_id) + } + } } } @@ -44314,6 +44658,16 @@ export class Editor_ extends Element { } } + if (this._drag_along_node[pressed_node_id]) { + for (const drag_along_node_id of [ + ...this._drag_along_node[pressed_node_id], + ]) { + if ((this._node_pressed_count[drag_along_node_id] ?? 0) === 0) { + this._on_node_drag_end_and_drop(drag_along_node_id) + } + } + } + if (!this._collapse_node_id.has(pressed_node_id)) { this._on_node_drag_end_and_drop(pressed_node_id) } @@ -51984,19 +52338,10 @@ export class Editor_ extends Element { } } - public _copy_nodes = async ( + public _sub_graph_selection = ( node_ids: string[], - deep: boolean, - callback: Callback - ) => { - // console.log('Graph', '_copy_nodes', node_ids, deep) - - const { - api: { - clipboard: { writeText }, - }, - } = this.$system - + deep: boolean + ): BundleSpec => { const { specs, newSpec, newSpecId } = this.$props const id = newSpecId() @@ -52025,8 +52370,6 @@ export class Editor_ extends Element { const _ref_unit_merge_count: Dict = {} - let unit_memory_processed_count = 0 - for (const unit_id of unit_ids) { const unit = clone(this._get_unit(unit_id)) @@ -52191,22 +52534,34 @@ export class Editor_ extends Element { const { unit } = unit_bundle_spec deepSet(bundle, ['spec', 'units', unit_id], unit) - - unit_memory_processed_count++ - - if (unit_memory_processed_count === unit_ids.length) { - const json = JSON.stringify(bundle) - - await writeText(json) - - callback(bundle) - } } ) })() } } + return bundle + } + + public _copy_nodes = async ( + node_ids: string[], + deep: boolean, + callback: Callback + ) => { + // console.log('Graph', '_copy_nodes', node_ids, deep) + + const { + api: { + clipboard: { writeText }, + }, + } = this.$system + + const { specs, newSpec, newSpecId } = this.$props + + const id = newSpecId() + + const bundle = this._sub_graph_selection(node_ids, deep) + try { const json = JSON.stringify(bundle) @@ -52370,7 +52725,7 @@ export class Editor_ extends Element { const valid = this._validate_text_bundle_spec(json) if (valid) { - this.paste_bundle(json, position) + this.paste_bundle(json, position, true, true) } else { const datum_id = this.add_new_datum(stringify(json), position, true) @@ -52427,7 +52782,12 @@ export class Editor_ extends Element { return datum_id } - public paste_bundle = (bundle: BundleSpec, position: Position) => { + public paste_bundle = ( + bundle: BundleSpec, + position: Position, + mount: boolean, + emit: boolean + ) => { // console.log('Graph', 'paste_bundle', bundle) const { specs, injectSpecs } = this.$props @@ -52452,15 +52812,19 @@ export class Editor_ extends Element { this._new_datum_id ) - this._remap_paste_spec( + const { actions } = this._remap_paste_spec( spec, position, map_spec_id, map_unit_id, map_merge_id, map_plug_id, - map_datum_id + map_datum_id, + mount, + emit ) + + return { map_unit_id, map_merge_id, map_plug_id, map_datum_id, actions } } public _remap_paste_spec = ( @@ -52470,8 +52834,10 @@ export class Editor_ extends Element { map_unit_id: Dict, map_merge_id: Dict, map_plug_id: IOOf>>, - map_datum_id: Dict - ): void => { + map_datum_id: Dict, + mount: boolean, + emit: boolean + ): { actions: Action[] } => { // console.log('Graph', '_paste_spec', graph) const remapped_graph = remapGraph( @@ -52482,7 +52848,7 @@ export class Editor_ extends Element { map_datum_id ) - this._paste_spec(remapped_graph, position) + return this._paste_spec(remapped_graph, position, mount, emit) } public _state_paste_spec = ( @@ -52505,25 +52871,39 @@ export class Editor_ extends Element { map_datum_id ) - this.__state_paste_spec(_graph, position, restart_simulation, register) + return this.__state_paste_spec( + _graph, + position, + restart_simulation, + true, + register + ) } - public _paste_spec = (graph: GraphSpec, position: Position): void => { + public _paste_spec = ( + graph: GraphSpec, + position: Position, + mount: boolean, + emit: boolean + ): { actions: Action[] } => { // console.log('Graph', '_paste_spec', graph) const actions = this._make_paste_spec_bulk_action(graph) this._dispatch_action(makeBulkEditAction(actions)) - this.__state_paste_spec(graph, position) - this.__pod_paste_spec(clone(graph), actions) + this.__state_paste_spec(graph, position, undefined, undefined, mount) + emit && this.__pod_paste_spec(clone(graph), actions) + + return { actions } } public __state_paste_spec = ( graph: GraphSpec, position: Position, restart_simulation: boolean = true, - register: boolean = true + register: boolean = true, + mount: boolean ): void => { // console.log('Graph', '__state_paste_spec', graph) @@ -52736,7 +53116,8 @@ export class Editor_ extends Element { const layout_position = NULL_VECTOR this._sim_add_core_component(unit_id, parent_id, layout_position) - this._sim_add_sub_component(unit_id, {}, undefined, false) + + mount && this._sim_add_sub_component(unit_id, {}, undefined, false) } const sub_component_ids = keys(subComponents) @@ -52744,8 +53125,10 @@ export class Editor_ extends Element { const ordered_sub_component_ids = this._order_sub_component_ids(sub_component_ids) - for (const unit_id of ordered_sub_component_ids) { - this._sim_add_sub_component_to_parent(unit_id, undefined) + if (mount) { + for (const unit_id of ordered_sub_component_ids) { + this._sim_add_sub_component_to_parent(unit_id, undefined) + } } for (const datum_id in data) { @@ -56962,7 +57345,7 @@ export class Editor_ extends Element { const position = this._jiggle_world_screen_center() - this._paste_spec(spec, position) + this._paste_spec(spec, position, true, true) } } diff --git a/src/test/spec/createClass.ts b/src/test/spec/createClass.ts index a9a8795352..e6e26f8e46 100644 --- a/src/test/spec/createClass.ts +++ b/src/test/spec/createClass.ts @@ -2,7 +2,6 @@ import * as assert from 'assert' import { UNTITLED } from '../../constant/STRING' import { watchGraphAndLog } from '../../debug' import { fromSpec } from '../../spec/fromSpec' -import _specs from '../../system/_specs' import { system } from '../util/system' const spec = system.newSpec({ @@ -25,7 +24,7 @@ const spec = system.newSpec({ outputs: {}, }) -const Class = fromSpec(spec, _specs, system.classes) +const Class = fromSpec(spec, system.specs, system.classes) const composition = new Class(system) diff --git a/src/test/system/core/RandomHEXColor.ts b/src/test/system/core/RandomHEXColor.ts index 376c226662..40dbe723bc 100644 --- a/src/test/system/core/RandomHEXColor.ts +++ b/src/test/system/core/RandomHEXColor.ts @@ -2,7 +2,6 @@ import * as assert from 'assert' import { Graph } from '../../../Class/Graph' import { watchGraphAndLog, watchUnitAndLog } from '../../../debug' import { fromSpec } from '../../../spec/fromSpec' -import _specs from '../../../system/_specs' import { GraphSpec } from '../../../types/GraphSpec' import { system } from '../../util/system' @@ -68,7 +67,7 @@ const spec = { }, } as GraphSpec -const RandomHEXColor = fromSpec(spec, _specs) +const RandomHEXColor = fromSpec(spec, system.specs) const randomHEXColor = new RandomHEXColor(system)