From 181437c33b9e6a140d15c87445c7c3efa001c71c Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Fri, 26 Jul 2024 09:34:45 +0100 Subject: [PATCH 01/33] added bottom toolbar --- rollup.config.mjs | 2 +- src/style.scss | 1 + src/svg/ar-close.svg | 7 -- src/svg/ar-mode.svg | 7 -- src/svg/crop.svg | 3 + src/svg/frame-selection.svg | 4 + src/svg/hotspot.svg | 24 ------ src/svg/redo.svg | 3 + src/svg/select-brush.svg | 4 + src/svg/select-lasso.svg | 4 + src/svg/select-picker.svg | 3 + src/svg/show-hide-selection.svg | 4 + src/svg/undo.svg | 3 + src/tools/rect-selection.ts | 23 +++++- src/ui/bottom-toolbar.scss | 74 +++++++++++++++++ src/ui/bottom-toolbar.ts | 135 ++++++++++++++++++++++++++++++++ src/ui/editor.ts | 9 ++- src/ui/toolbar.ts | 2 +- 18 files changed, 266 insertions(+), 46 deletions(-) delete mode 100644 src/svg/ar-close.svg delete mode 100644 src/svg/ar-mode.svg create mode 100644 src/svg/crop.svg create mode 100644 src/svg/frame-selection.svg delete mode 100644 src/svg/hotspot.svg create mode 100644 src/svg/redo.svg create mode 100644 src/svg/select-brush.svg create mode 100644 src/svg/select-lasso.svg create mode 100644 src/svg/select-picker.svg create mode 100644 src/svg/show-hide-selection.svg create mode 100644 src/svg/undo.svg create mode 100644 src/ui/bottom-toolbar.scss create mode 100644 src/ui/bottom-toolbar.ts diff --git a/rollup.config.mjs b/rollup.config.mjs index 7129271a..052518b9 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -63,7 +63,7 @@ const application = { }), alias({entries: aliasEntries}), resolve(), - image({dom: true}), + image({dom: false}), sass({ output: false, insert: true diff --git a/src/style.scss b/src/style.scss index e087adcc..042eea12 100644 --- a/src/style.scss +++ b/src/style.scss @@ -1,4 +1,5 @@ @import '../node_modules/@playcanvas/pcui/dist/pcui-theme-grey.scss'; +@import 'ui/bottom-toolbar.scss'; * { font-size: 12px; diff --git a/src/svg/ar-close.svg b/src/svg/ar-close.svg deleted file mode 100644 index 28e7ced8..00000000 --- a/src/svg/ar-close.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - Layer 1 - - - \ No newline at end of file diff --git a/src/svg/ar-mode.svg b/src/svg/ar-mode.svg deleted file mode 100644 index 361fd062..00000000 --- a/src/svg/ar-mode.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - Layer 1 - - - \ No newline at end of file diff --git a/src/svg/crop.svg b/src/svg/crop.svg new file mode 100644 index 00000000..fa2680b9 --- /dev/null +++ b/src/svg/crop.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/frame-selection.svg b/src/svg/frame-selection.svg new file mode 100644 index 00000000..a703fa5e --- /dev/null +++ b/src/svg/frame-selection.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/svg/hotspot.svg b/src/svg/hotspot.svg deleted file mode 100644 index 09149611..00000000 --- a/src/svg/hotspot.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/svg/redo.svg b/src/svg/redo.svg new file mode 100644 index 00000000..0bf27c3f --- /dev/null +++ b/src/svg/redo.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/select-brush.svg b/src/svg/select-brush.svg new file mode 100644 index 00000000..5ef0ad38 --- /dev/null +++ b/src/svg/select-brush.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/svg/select-lasso.svg b/src/svg/select-lasso.svg new file mode 100644 index 00000000..f3e962b5 --- /dev/null +++ b/src/svg/select-lasso.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/svg/select-picker.svg b/src/svg/select-picker.svg new file mode 100644 index 00000000..3c04961a --- /dev/null +++ b/src/svg/select-picker.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/show-hide-selection.svg b/src/svg/show-hide-selection.svg new file mode 100644 index 00000000..e5dbce33 --- /dev/null +++ b/src/svg/show-hide-selection.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/svg/undo.svg b/src/svg/undo.svg new file mode 100644 index 00000000..13491bc4 --- /dev/null +++ b/src/svg/undo.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/tools/rect-selection.ts b/src/tools/rect-selection.ts index 14b5ad35..e3e0f32e 100644 --- a/src/tools/rect-selection.ts +++ b/src/tools/rect-selection.ts @@ -20,6 +20,7 @@ class RectSelection { const start = { x: 0, y: 0 }; const end = { x: 0, y: 0 }; let dragId: number | undefined; + let dragMoved = false; const updateRect = () => { const x = Math.min(start.x, end.x); @@ -39,6 +40,7 @@ class RectSelection { e.stopPropagation(); dragId = e.pointerId; + dragMoved = false; parent.setPointerCapture(dragId); start.x = end.x = e.offsetX; @@ -55,6 +57,7 @@ class RectSelection { e.preventDefault(); e.stopPropagation(); + dragMoved = true; end.x = e.offsetX; end.y = e.offsetY; @@ -78,10 +81,22 @@ class RectSelection { dragEnd(); - events.fire('select.rect', e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), { - start: { x: Math.min(start.x, end.x) / w, y: Math.min(start.y, end.y) / h }, - end: { x: Math.max(start.x, end.x) / w, y: Math.max(start.y, end.y) / h }, - }); + if (dragMoved) { + // rect select + events.fire( + 'select.rect', + e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), { + start: { x: Math.min(start.x, end.x) / w, y: Math.min(start.y, end.y) / h }, + end: { x: Math.max(start.x, end.x) / w, y: Math.max(start.y, end.y) / h }, + }); + } else { + // pick + events.fire( + 'select.point', + e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), + { x: e.offsetX / parent.clientWidth, y: e.offsetY / parent.clientHeight } + ); + } } }; diff --git a/src/ui/bottom-toolbar.scss b/src/ui/bottom-toolbar.scss new file mode 100644 index 00000000..f54652e7 --- /dev/null +++ b/src/ui/bottom-toolbar.scss @@ -0,0 +1,74 @@ +$icon-disabled: #7c7678; +$icon-inactive: #b3aaac; +$icon-active: white; +$icon-bg-light: #333; +$icon-bg-active: #f60; + +#bottom-toolbar { + position: absolute; + left: 50%; + translate: -50%; + bottom: 50px; + height: 54px; + padding-left: 8px; + padding-right: 8px; + background-color: $bcg-dark; + border-radius: 8px; + + display: flex; + flex-direction: row; + align-items: center; + + pointer-events: all; +} + +.bottom-toolbar-button, .bottom-toolbar-tool { + width: 38px; + height: 38px; + border-radius: 2px; + margin: 0px 1px; + padding: 0px; + border: 0px; +} + +.bottom-toolbar-separator { + width: 1px; + height: 38px; + background-color: $icon-bg-light; + margin: 0px 8px; +} + +.bottom-toolbar-button { + svg { + path { + fill: $icon-inactive; + } + } +} + +.bottom-toolbar-tool { + svg { + path { + fill: $icon-inactive; + } + } + background-color: $icon-bg-light; +} + +.bottom-toolbar-tool.active { + svg { + path { + fill: $icon-active; + stroke: $icon-active; + } + } +} + +.bottom-toolbar-tool.disabled { + svg { + path { + fill: $icon-disabled; + } + } + background-color: $bcg-dark; +} \ No newline at end of file diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts new file mode 100644 index 00000000..b2bfacaa --- /dev/null +++ b/src/ui/bottom-toolbar.ts @@ -0,0 +1,135 @@ +import { Button, Element, Container } from 'pcui'; +import { Events } from '../events'; + +import undoSvg from '../svg/undo.svg'; +import redoSvg from '../svg/redo.svg'; +import pickerSvg from '../svg/select-picker.svg'; +import lassoSvg from '../svg/select-lasso.svg'; +import brushSvg from '../svg/select-brush.svg'; +import cropSvg from '../svg/crop.svg'; +import frameSelectionSvg from '../svg/frame-selection.svg'; +import showHideSelectionSvg from '../svg/show-hide-selection.svg'; + +class BottomToolbar extends Container { + constructor(events: Events, args = {}) { + args = { + ...args, + id: 'bottom-toolbar' + }; + + super(args); + + const handleDown = (event: PointerEvent) => { + event.preventDefault(); + event.stopPropagation(); + }; + + this.dom.addEventListener('pointerdown', (event) => { + handleDown(event); + }); + + const createSvg = (svgString: string) => { + const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length)); + return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement; + }; + + const undo = new Button({ + id: 'bottom-toolbar-undo', + class: 'bottom-toolbar-button' + }); + + const redo = new Button({ + id: 'bottom-toolbar-redo', + class: 'bottom-toolbar-button', + }); + + const picker = new Button({ + id: 'bottom-toolbar-picker', + class: 'bottom-toolbar-tool' + }); + + const brush = new Button({ + id: 'bottom-toolbar-brush', + class: 'bottom-toolbar-tool' + }); + + const lasso = new Button({ + id: 'bottom-toolbar-lasso', + class: ['bottom-toolbar-tool', 'disabled'] + }); + + const crop = new Button({ + id: 'bottom-toolbar-crop', + class: ['bottom-toolbar-tool', 'disabled'] + }); + + const frame = new Button({ + id: 'bottom-toolbar-frame', + class: 'bottom-toolbar-button' + }); + + const showHide = new Button({ + id: 'bottom-toolbar-show-hide', + class: ['bottom-toolbar-tool', 'active'] + }); + + undo.dom.appendChild(createSvg(undoSvg)); + redo.dom.appendChild(createSvg(redoSvg)); + picker.dom.appendChild(createSvg(pickerSvg)); + brush.dom.appendChild(createSvg(brushSvg)); + lasso.dom.appendChild(createSvg(lassoSvg)); + crop.dom.appendChild(createSvg(cropSvg)); + frame.dom.appendChild(createSvg(frameSelectionSvg)); + showHide.dom.appendChild(createSvg(showHideSelectionSvg)); + + this.append(undo); + this.append(redo); + this.append(new Element({ class: 'bottom-toolbar-separator' })); + this.append(picker); + this.append(brush); + this.append(lasso); + this.append(crop); + this.append(new Element({ class: 'bottom-toolbar-separator' })); + this.append(frame); + this.append(showHide); + + undo.dom.addEventListener('click', () => { + events.fire('edit.undo'); + }); + + redo.dom.addEventListener('click', () => { + events.fire('edit.redo'); + }); + + picker.dom.addEventListener('click', () => { + events.fire('tool.rectSelection'); + }); + + brush.dom.addEventListener('click', () => { + events.fire('tool.brushSelection'); + }); + + frame.dom.addEventListener('click', () => { + events.fire('camera.focus'); + }); + + let splatSizeSave = 2; + events.on('splatSize', (size: number) => { + if (size !== 0) { + splatSizeSave = size; + } + showHide.class[size === 0 ? 'remove' : 'add']('active'); + }); + + showHide.dom.addEventListener('click', () => { + events.fire('splatSize', events.invoke('splatSize') === 0 ? splatSizeSave : 0); + }); + + events.on('tool.activated', (toolName: string) => { + picker.class[toolName === 'rectSelection' ? 'add' : 'remove']('active'); + brush.class[toolName === 'brushSelection' ? 'add' : 'remove']('active'); + }); + } +} + +export { BottomToolbar }; diff --git a/src/ui/editor.ts b/src/ui/editor.ts index 343a1a51..ede5658f 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -1,11 +1,13 @@ import { Container, Label } from 'pcui'; +import { Mat4 } from 'playcanvas'; import { ControlPanel } from './control-panel'; import { DataPanel } from './data-panel'; import { Toolbar } from './toolbar'; import { Events } from '../events'; import { Popup } from './popup'; import { ViewCube } from './view-cube'; -import { Mat4 } from 'playcanvas'; +import { BottomToolbar } from './bottom-toolbar'; + import logo from './playcanvas-logo.png'; class EditorUI { @@ -22,7 +24,7 @@ class EditorUI { // favicon const link = document.createElement('link'); link.rel = 'icon'; - link.href = logo.src; + link.href = logo; document.head.appendChild(link); // app @@ -76,9 +78,12 @@ class EditorUI { id: 'tools-container' }); + const bottomToolbar = new BottomToolbar(events); + canvasContainer.dom.appendChild(canvas); canvasContainer.append(filenameLabel); canvasContainer.append(toolsContainer); + canvasContainer.append(bottomToolbar); // view axes container const viewCube = new ViewCube(events); diff --git a/src/ui/toolbar.ts b/src/ui/toolbar.ts index 9ecc474c..1e37cdac 100644 --- a/src/ui/toolbar.ts +++ b/src/ui/toolbar.ts @@ -22,7 +22,7 @@ class Toolbar extends Container { const appLogo = document.createElement('img'); appLogo.classList.add('toolbar-button'); appLogo.id = 'app-logo'; - appLogo.src = logo.src; + appLogo.src = logo; // file const fileButton = new Button({ From f9b6083daedfb2e28e8761708f6d3f9b13572a89 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Fri, 26 Jul 2024 11:49:17 +0100 Subject: [PATCH 02/33] added right toolbar --- src/index.ts | 2 +- src/ui/bottom-toolbar.scss | 10 +-- src/ui/bottom-toolbar.ts | 13 +++- src/ui/editor.ts | 11 +++- src/ui/right-toolbar.scss | 57 +++++++++++++++++ src/ui/right-toolbar.ts | 61 +++++++++++++++++++ src/{ => ui}/style.scss | 6 +- src/ui/tooltips.scss | 11 ++++ src/ui/tooltips.ts | 122 +++++++++++++++++++++++++++++++++++++ 9 files changed, 283 insertions(+), 10 deletions(-) create mode 100644 src/ui/right-toolbar.scss create mode 100644 src/ui/right-toolbar.ts rename src/{ => ui}/style.scss (98%) create mode 100644 src/ui/tooltips.scss create mode 100644 src/ui/tooltips.ts diff --git a/src/index.ts b/src/index.ts index 1cd93b77..22043559 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import style from './style.scss'; +import style from './ui/style.scss'; import { main } from './main'; import { version as appVersion } from '../package.json'; import { version as pcuiVersion, revision as pcuiRevision } from 'pcui'; diff --git a/src/ui/bottom-toolbar.scss b/src/ui/bottom-toolbar.scss index f54652e7..e92e245b 100644 --- a/src/ui/bottom-toolbar.scss +++ b/src/ui/bottom-toolbar.scss @@ -7,14 +7,14 @@ $icon-bg-active: #f60; #bottom-toolbar { position: absolute; left: 50%; - translate: -50%; bottom: 50px; height: 54px; - padding-left: 8px; - padding-right: 8px; - background-color: $bcg-dark; + transform: translate(-50%, 0); + padding: 0px 8px; border-radius: 8px; + background-color: $bcg-dark; + display: flex; flex-direction: row; align-items: center; @@ -71,4 +71,4 @@ $icon-bg-active: #f60; } } background-color: $bcg-dark; -} \ No newline at end of file +} diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts index b2bfacaa..fdd72416 100644 --- a/src/ui/bottom-toolbar.ts +++ b/src/ui/bottom-toolbar.ts @@ -1,5 +1,6 @@ import { Button, Element, Container } from 'pcui'; import { Events } from '../events'; +import { Tooltips } from './tooltips'; import undoSvg from '../svg/undo.svg'; import redoSvg from '../svg/redo.svg'; @@ -11,7 +12,7 @@ import frameSelectionSvg from '../svg/frame-selection.svg'; import showHideSelectionSvg from '../svg/show-hide-selection.svg'; class BottomToolbar extends Container { - constructor(events: Events, args = {}) { + constructor(events: Events, tooltips: Tooltips, args = {}) { args = { ...args, id: 'bottom-toolbar' @@ -129,6 +130,16 @@ class BottomToolbar extends Container { picker.class[toolName === 'rectSelection' ? 'add' : 'remove']('active'); brush.class[toolName === 'brushSelection' ? 'add' : 'remove']('active'); }); + + // register tooltips + tooltips.register(undo, 'Undo'); + tooltips.register(redo, 'Redo'); + tooltips.register(picker, 'Select Picker'); + tooltips.register(brush, 'Select Brush'); + tooltips.register(lasso, 'Select Lasso'); + tooltips.register(crop, 'Crop'); + tooltips.register(frame, 'Frame Selection'); + tooltips.register(showHide, 'Show/Hide Selection'); } } diff --git a/src/ui/editor.ts b/src/ui/editor.ts index ede5658f..316051e7 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -7,6 +7,8 @@ import { Events } from '../events'; import { Popup } from './popup'; import { ViewCube } from './view-cube'; import { BottomToolbar } from './bottom-toolbar'; +import { RightToolbar } from './right-toolbar'; +import { Tooltips } from './tooltips'; import logo from './playcanvas-logo.png'; @@ -78,12 +80,19 @@ class EditorUI { id: 'tools-container' }); - const bottomToolbar = new BottomToolbar(events); + // tooltips + const tooltips = new Tooltips(); + tooltipsContainer.append(tooltips); + + // bottom toolbar + const bottomToolbar = new BottomToolbar(events, tooltips); + const rightToolbar = new RightToolbar(events, tooltips); canvasContainer.dom.appendChild(canvas); canvasContainer.append(filenameLabel); canvasContainer.append(toolsContainer); canvasContainer.append(bottomToolbar); + canvasContainer.append(rightToolbar); // view axes container const viewCube = new ViewCube(events); diff --git a/src/ui/right-toolbar.scss b/src/ui/right-toolbar.scss new file mode 100644 index 00000000..37cbd031 --- /dev/null +++ b/src/ui/right-toolbar.scss @@ -0,0 +1,57 @@ + +#right-toolbar { + position: absolute; + right: 70px; + top: 50%; + width: 54px; + transform: translate(50%, -50%); + padding: 8px 0px; + border-radius: 8px; + + background-color: $bcg-dark; + + display: flex; + flex-direction: column; + align-items: center; + + pointer-events: all; +} + +.right-toolbar-button { + width: 38px; + height: 38px; + border-radius: 2px; + margin: 0px 1px; + padding: 0px; + border: 0px; + + &::before { + font-size: 18px !important; + line-height: 100%; + } + + svg { + path { + fill: $icon-inactive; + } + } + background-color: $icon-bg-light; +} + +.right-toolbar-button.active { + svg { + path { + fill: $icon-active; + stroke: $icon-active; + } + } +} + +.right-toolbar-button.disabled { + svg { + path { + fill: $icon-disabled; + } + } + background-color: $bcg-dark; +} diff --git a/src/ui/right-toolbar.ts b/src/ui/right-toolbar.ts new file mode 100644 index 00000000..23e98a55 --- /dev/null +++ b/src/ui/right-toolbar.ts @@ -0,0 +1,61 @@ +import { Button, Container } from 'pcui'; +import { Events } from '../events'; +import { Tooltips } from './tooltips'; + +class RightToolbar extends Container { + constructor(events: Events, tooltips: Tooltips, args = {}) { + args = { + ...args, + id: 'right-toolbar' + }; + + super(args); + + const handleDown = (event: PointerEvent) => { + event.preventDefault(); + event.stopPropagation(); + }; + + this.dom.addEventListener('pointerdown', (event) => { + handleDown(event); + }); + + const translate = new Button({ + id: 'right-toolbar-translate', + class: 'right-toolbar-button', + icon: 'E111' + }); + + const rotate = new Button({ + id: 'right-toolbar-rotate', + class: 'right-toolbar-button', + icon: 'E113' + }); + + const scale = new Button({ + id: 'right-toolbar-scale', + class: 'right-toolbar-button', + icon: 'E112' + }); + + this.append(translate); + this.append(rotate); + this.append(scale); + + translate.on('click', () => events.fire('tool.move')); + rotate.on('click', () => events.fire('tool.rotate')); + scale.on('click', () => events.fire('tool.scale')); + + events.on('tool.activated', (toolName: string) => { + translate.class[toolName === 'move' ? 'add' : 'remove']('active'); + rotate.class[toolName === 'rotate' ? 'add' : 'remove']('active'); + scale.class[toolName === 'scale' ? 'add' : 'remove']('active'); + }); + + tooltips.register(translate, 'Translate', 'left'); + tooltips.register(rotate, 'Rotate', 'left'); + tooltips.register(scale, 'Scale', 'left'); + } +}; + +export { RightToolbar }; diff --git a/src/style.scss b/src/ui/style.scss similarity index 98% rename from src/style.scss rename to src/ui/style.scss index 042eea12..16baad54 100644 --- a/src/style.scss +++ b/src/ui/style.scss @@ -1,5 +1,7 @@ -@import '../node_modules/@playcanvas/pcui/dist/pcui-theme-grey.scss'; -@import 'ui/bottom-toolbar.scss'; +@import '../../node_modules/@playcanvas/pcui/dist/pcui-theme-grey.scss'; +@import 'bottom-toolbar.scss'; +@import 'right-toolbar.scss'; +@import 'tooltips.scss'; * { font-size: 12px; diff --git a/src/ui/tooltips.scss b/src/ui/tooltips.scss new file mode 100644 index 00000000..1d311ad8 --- /dev/null +++ b/src/ui/tooltips.scss @@ -0,0 +1,11 @@ +.tooltips { + position: absolute; + background-color: $bcg-darkest; + border-radius: 3px; + padding: 1px; +} + +.tooltips-content { + color: $icon-inactive; + margin: 2px; +} diff --git a/src/ui/tooltips.ts b/src/ui/tooltips.ts new file mode 100644 index 00000000..6024acd4 --- /dev/null +++ b/src/ui/tooltips.ts @@ -0,0 +1,122 @@ +import { Container, Element, Label } from 'pcui'; + +class Tooltips extends Container { + register: (target: Element, text: string) => void; + unregister: (target: Element) => void; + destroy: () => void; + + constructor(args: any = {}) { + args = { + ...args, + class: 'tooltips', + hidden: true + }; + + super(args); + + const text = new Label({ + class: 'tooltips-content' + }); + + this.append(text); + + const targets = new Map(); + const style = this.dom.style; + let timer: number = 0; + + this.register = (target: Element, textString: string, direction: 'left' | 'right' | 'top' | 'bottom' = 'bottom') => { + + const activate = () => { + const rect = target.dom.getBoundingClientRect(); + const midx = Math.floor((rect.left + rect.right) * 0.5); + const midy = Math.floor((rect.top + rect.bottom) * 0.5); + + switch (direction) { + case 'left': + style.left = `${rect.left}px`; + style.top = `${midy}px`; + style.transform = `translate(calc(-100% - 10px), -50%)`; + break; + case 'right': + style.left = `${rect.right}px`; + style.top = `${midy}px`; + style.transform = `translate(10px, -50%)`; + break; + case 'top': + style.left = `${midx}px`; + style.top = `${rect.top}px`; + style.transform = `translate(-50%, calc(-100% - 10px))`; + break; + case 'bottom': + style.left = `${midx}px`; + style.top = `${rect.bottom}px`; + style.transform = `translate(-50%, 10px)`; + break; + } + + text.text = textString; + style.display = 'inline'; + }; + + const startTimer = (fn: () => void) => { + timer = setTimeout(() => { + fn(); + timer = -1; + }, 250); + } + + const cancelTimer = () => { + if (timer >= 0) { + clearTimeout(timer); + timer = -1; + } + } + + const enter = () => { + cancelTimer(); + + if (style.display === 'inline') { + activate(); + } else { + startTimer(() => activate()); + } + }; + + const leave = () => { + cancelTimer(); + + if (style.display === 'inline') { + startTimer(() => { + style.display = 'none'; + }); + } + }; + + target.dom.addEventListener('pointerenter', enter); + target.dom.addEventListener('pointerleave', leave); + + target.on('destroy', () => { + this.unregister(target); + }); + + targets.set(target, { enter, leave }); + }; + + this.unregister = (target: Element) => { + const value = targets.get(target); + if (value) { + target.dom.removeEventListener('pointerenter', value.enter); + target.dom.removeEventListener('pointerleave', value.leave); + targets.delete(target); + } + }; + + this.destroy = () => { + for (let target of targets.keys()) { + this.unregister(target); + } + }; + } +} + +export { Tooltips }; From 35232ea67baa3f10256c7b0d9ca3c2b1dbdbcd39 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Fri, 26 Jul 2024 15:07:07 +0100 Subject: [PATCH 03/33] add top menu --- src/ui/bottom-toolbar.scss | 24 ++--- src/ui/editor.ts | 12 +++ src/ui/menu.scss | 66 ++++++++++++++ src/ui/menu.ts | 181 +++++++++++++++++++++++++++++++++++++ src/ui/right-toolbar.scss | 12 +-- src/ui/style.scss | 8 ++ src/ui/toolbar.ts | 7 +- src/ui/tooltips.scss | 2 +- 8 files changed, 284 insertions(+), 28 deletions(-) create mode 100644 src/ui/menu.scss create mode 100644 src/ui/menu.ts diff --git a/src/ui/bottom-toolbar.scss b/src/ui/bottom-toolbar.scss index e92e245b..008d9035 100644 --- a/src/ui/bottom-toolbar.scss +++ b/src/ui/bottom-toolbar.scss @@ -1,13 +1,7 @@ -$icon-disabled: #7c7678; -$icon-inactive: #b3aaac; -$icon-active: white; -$icon-bg-light: #333; -$icon-bg-active: #f60; - #bottom-toolbar { position: absolute; left: 50%; - bottom: 50px; + bottom: 24px; height: 54px; transform: translate(-50%, 0); padding: 0px 8px; @@ -25,23 +19,23 @@ $icon-bg-active: #f60; .bottom-toolbar-button, .bottom-toolbar-tool { width: 38px; height: 38px; - border-radius: 2px; margin: 0px 1px; padding: 0px; border: 0px; + border-radius: 2px; } .bottom-toolbar-separator { width: 1px; height: 38px; - background-color: $icon-bg-light; + background-color: $bgclr-light; margin: 0px 8px; } .bottom-toolbar-button { svg { path { - fill: $icon-inactive; + fill: $clr-default; } } } @@ -49,17 +43,17 @@ $icon-bg-active: #f60; .bottom-toolbar-tool { svg { path { - fill: $icon-inactive; + fill: $clr-default; } } - background-color: $icon-bg-light; + background-color: $bgclr-light; } .bottom-toolbar-tool.active { svg { path { - fill: $icon-active; - stroke: $icon-active; + fill: $clr-active; + stroke: $clr-active; } } } @@ -67,7 +61,7 @@ $icon-bg-active: #f60; .bottom-toolbar-tool.disabled { svg { path { - fill: $icon-disabled; + fill: $clr-disabled; } } background-color: $bcg-dark; diff --git a/src/ui/editor.ts b/src/ui/editor.ts index 316051e7..6ab31f44 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -6,9 +6,11 @@ import { Toolbar } from './toolbar'; import { Events } from '../events'; import { Popup } from './popup'; import { ViewCube } from './view-cube'; +import { Menu } from './menu'; import { BottomToolbar } from './bottom-toolbar'; import { RightToolbar } from './right-toolbar'; import { Tooltips } from './tooltips'; +import { ShortcutsPopup } from './shortcuts-popup'; import logo from './playcanvas-logo.png'; @@ -85,12 +87,14 @@ class EditorUI { tooltipsContainer.append(tooltips); // bottom toolbar + const menu = new Menu(events); const bottomToolbar = new BottomToolbar(events, tooltips); const rightToolbar = new RightToolbar(events, tooltips); canvasContainer.dom.appendChild(canvas); canvasContainer.append(filenameLabel); canvasContainer.append(toolsContainer); + canvasContainer.append(menu); canvasContainer.append(bottomToolbar); canvasContainer.append(rightToolbar); @@ -121,9 +125,13 @@ class EditorUI { // message popup this.popup = new Popup(topContainer); + // shortcuts popup + const shortcutsPopup = new ShortcutsPopup(); + appContainer.append(editorContainer); appContainer.append(tooltipsContainer); appContainer.append(topContainer); + appContainer.append(shortcutsPopup); this.appContainer = appContainer; this.topContainer = topContainer; @@ -136,6 +144,10 @@ class EditorUI { document.body.appendChild(appContainer.dom); document.body.setAttribute('tabIndex', '-1'); + events.on('show.shortcuts', () => { + shortcutsPopup.hidden = false; + }); + events.function('showPopup', (options: { type: 'error' | 'info' | 'yesno' | 'okcancel', message: string, value: string}) => { return this.popup.show(options.type, options.message, options.value); }); diff --git a/src/ui/menu.scss b/src/ui/menu.scss new file mode 100644 index 00000000..a968b606 --- /dev/null +++ b/src/ui/menu.scss @@ -0,0 +1,66 @@ +#menu { + position: absolute; + top: 24px; + left: 24px; + height: 54px; + padding: 0px 8px 0px 0px; + border-radius: 8px; + border: 1px solid $bcg-dark; + + overflow: hidden; + + background-color: $bcg-dark; + + display: flex; + flex-direction: row; + align-items: center; + + pointer-events: all; +} + +#menu-icon { + width: 54px; +} + +.menu-button { + height: 38px; + margin: 0px 1px; + padding: 0px 20px; + border: 0px; + border-radius: 2px; +} + +#menu-dropdown > div > div { + // WARNING: first and last child will hide any submenus + &:first-child { + border-radius: 10px 10px 0px 0px; + overflow: hidden; + } + + &:last-child { + border-radius: 0px 0px 10px 10px; + overflow: hidden; + } +} + +#menu-dropdown > div > div > div { + margin: 1px 0px 0px 0px; + + background-color: $bcg-dark; + color: $clr-default; + + &:hover { + background-color: $bcg-darker; + color: $clr-active; + } +} + +#menu-dropdown > div > div > div > span { + height: 28px; + line-height: 28px; + + &::before { + margin: 0px 10px; + font-size: 14px; + } +} diff --git a/src/ui/menu.ts b/src/ui/menu.ts new file mode 100644 index 00000000..879b0b35 --- /dev/null +++ b/src/ui/menu.ts @@ -0,0 +1,181 @@ +import { Button, Container, Menu as PcuiMenu } from 'pcui'; +import { Events } from '../events'; +import { version } from '../../package.json'; + +import logo from './playcanvas-logo.png'; + +class Menu extends Container { + constructor(events: Events, args = {}) { + args = { + ...args, + id: 'menu' + }; + + super(args); + + const handleDown = (event: PointerEvent) => { + event.preventDefault(); + event.stopPropagation(); + }; + + this.dom.addEventListener('pointerdown', (event) => { + handleDown(event); + }); + + const icon = document.createElement('img'); + icon.setAttribute('id', 'menu-icon'); + icon.src = logo; + + const file = new Button({ + text: 'File', + class: 'menu-button' + }); + + const scene = new Button({ + text: 'Scene', + class: 'menu-button' + }); + + const help = new Button({ + text: 'Help', + class: 'menu-button' + }); + + this.dom.appendChild(icon); + this.append(file); + this.append(scene); + this.append(help); + + const fileMenu = new PcuiMenu({ + id: 'menu-dropdown', + items: [{ + class: 'menu-dropdown-item', + text: 'New', + icon: 'E208', + onSelect: () => events.fire('scene.new') + }, { + class: 'menu-dropdown-item', + text: 'Open...', + icon: 'E226', + onSelect: () => events.fire('scene.open') + }, { + class: 'menu-dropdown-item', + text: 'Save', + icon: 'E216', + onSelect: () => events.fire('scene.save'), + onIsEnabled: () => events.invoke('scene.canSave') + }, { + class: 'menu-dropdown-item', + text: 'Save As...', + icon: 'E216', + onSelect: () => events.fire('scene.saveAs'), + onIsEnabled: () => events.invoke('scene.canSave') + }, { + class: 'menu-dropdown-item', + text: 'Export', + icon: 'E225', + onIsEnabled: () => events.invoke('scene.canSave'), + items: [{ + class: 'menu-dropdown-item', + text: 'Compressed Ply', + icon: 'E245', + onSelect: () => events.invoke('scene.export', 'compressed-ply') + }, { + class: 'menu-dropdown-item', + text: 'Splat file', + icon: 'E245', + onSelect: () => events.invoke('scene.export', 'splat') + }] + }, { + class: 'menu-dropdown-item', + text: `SUPERSPLAT v${version}`, + icon: 'E0020', + enabled: false + }] + }); + + const sceneMenu = new PcuiMenu({ + id: 'menu-dropdown', + items: [{ + class: 'menu-dropdown-item', + text: 'Select All', + icon: 'E0020', + onSelect: () => events.fire('selection.all') + }, { + class: 'menu-dropdown-item', + text: 'Select None', + icon: 'E0020', + onSelect: () => events.fire('selection.none') + }, { + class: 'menu-dropdown-item', + text: 'Invert Selection', + icon: 'E0020', + onSelect: () => events.fire('selection.invert') + }] + }); + + const helpMenu = new PcuiMenu({ + id: 'menu-dropdown', + items: [{ + class: 'menu-dropdown-item', + text: 'About SuperSplat', + icon: 'E138', + onSelect: () => events.fire('show.about') + }, { + class: 'menu-dropdown-item', + text: 'Keyboard Shortcuts', + icon: 'E136', + onSelect: () => events.fire('show.shortcuts') + }, { + class: 'menu-dropdown-item', + text: 'GitHub Repo', + icon: 'E259', + onSelect: () => window.open('https://github.com/playcanvas/supersplat', '_blank').focus() + }] + }); + + this.append(fileMenu); + this.append(sceneMenu); + this.append(helpMenu); + + file.on('click', () => { + const r = file.dom.getBoundingClientRect(); + fileMenu.position(r.left, r.bottom + 8); + fileMenu.hidden = false; + }); + + scene.on('click', () => { + const r = scene.dom.getBoundingClientRect(); + sceneMenu.position(r.left, r.bottom + 8); + sceneMenu.hidden = false; + }); + + help.on('click', () => { + const r = help.dom.getBoundingClientRect(); + helpMenu.position(r.left, r.bottom + 8); + helpMenu.hidden = false; + }); + + window.addEventListener('click', (e: Event) => { + if (!fileMenu.hidden && + !fileMenu.dom.contains(e.target as Node) && + e.target !== file.dom) { + fileMenu.hidden = true; + } + + if (!sceneMenu.hidden && + !sceneMenu.dom.contains(e.target as Node) && + e.target !== scene.dom) { + sceneMenu.hidden = true; + } + + if (!helpMenu.hidden && + !helpMenu.dom.contains(e.target as Node) && + e.target !== help.dom) { + helpMenu.hidden = true; + } + }); + } +} + +export { Menu }; diff --git a/src/ui/right-toolbar.scss b/src/ui/right-toolbar.scss index 37cbd031..3ea3e8d5 100644 --- a/src/ui/right-toolbar.scss +++ b/src/ui/right-toolbar.scss @@ -20,10 +20,10 @@ .right-toolbar-button { width: 38px; height: 38px; - border-radius: 2px; margin: 0px 1px; padding: 0px; border: 0px; + border-radius: 2px; &::before { font-size: 18px !important; @@ -32,17 +32,17 @@ svg { path { - fill: $icon-inactive; + fill: $clr-default; } } - background-color: $icon-bg-light; + background-color: $bgclr-light; } .right-toolbar-button.active { svg { path { - fill: $icon-active; - stroke: $icon-active; + fill: $clr-active; + stroke: $clr-active; } } } @@ -50,7 +50,7 @@ .right-toolbar-button.disabled { svg { path { - fill: $icon-disabled; + fill: $clr-disabled; } } background-color: $bcg-dark; diff --git a/src/ui/style.scss b/src/ui/style.scss index 16baad54..c63cbd7c 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -1,4 +1,12 @@ @import '../../node_modules/@playcanvas/pcui/dist/pcui-theme-grey.scss'; + +$clr-default: #b3aaac; +$clr-disabled: #7c7678; +$clr-active: white; +$bgclr-light: #333; +$bgclr-active: #f60; + +@import 'menu.scss'; @import 'bottom-toolbar.scss'; @import 'right-toolbar.scss'; @import 'tooltips.scss'; diff --git a/src/ui/toolbar.ts b/src/ui/toolbar.ts index 1e37cdac..94d49b2c 100644 --- a/src/ui/toolbar.ts +++ b/src/ui/toolbar.ts @@ -173,18 +173,13 @@ class Toolbar extends Container { // toolbarToolsContainer.append(rectTool); // toolbarToolsContainer.append(brushTool); - // keyboard shortcuts - const shortcutsPopup = new ShortcutsPopup(); - - appContainer.append(shortcutsPopup); - // keyboard shortcuts const shortcuts = new Button({ class: 'toolbar-button', icon: 'E136' }); shortcuts.on('click', () => { - shortcutsPopup.hidden = false; + events.fire('show.shortcuts'); }); // github diff --git a/src/ui/tooltips.scss b/src/ui/tooltips.scss index 1d311ad8..5202ed9c 100644 --- a/src/ui/tooltips.scss +++ b/src/ui/tooltips.scss @@ -6,6 +6,6 @@ } .tooltips-content { - color: $icon-inactive; + color: $clr-default; margin: 2px; } From cc034ed6611c80e00504963e20b3d367f65dc1c2 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Fri, 26 Jul 2024 16:58:13 +0100 Subject: [PATCH 04/33] added transform panel --- src/ui/control-panel.ts | 10 +++- src/ui/editor.ts | 3 + src/ui/menu.scss | 1 + src/ui/scene-panel.scss | 12 ++++ src/ui/scene-panel.ts | 41 +++++++++++++ src/ui/style.scss | 13 +--- src/ui/transform.scss | 66 +++++++++++++++++++++ src/ui/{transform-panel.ts => transform.ts} | 42 +++++++------ 8 files changed, 154 insertions(+), 34 deletions(-) create mode 100644 src/ui/scene-panel.scss create mode 100644 src/ui/scene-panel.ts create mode 100644 src/ui/transform.scss rename src/ui/{transform-panel.ts => transform.ts} (79%) diff --git a/src/ui/control-panel.ts b/src/ui/control-panel.ts index 5a6b392c..9fb58be2 100644 --- a/src/ui/control-panel.ts +++ b/src/ui/control-panel.ts @@ -1,7 +1,7 @@ import { BooleanInput, Button, Container, Label, NumericInput, Panel, RadioButton, SelectInput, SliderInput, VectorInput } from 'pcui'; import { Events } from '../events'; import { SplatItem, SplatList } from './splat-list'; -import { TransformPanel } from './transform-panel'; +import { Transform } from './transform'; import { Element, ElementType } from '../element'; import { Splat } from '../splat'; import { version as appVersion } from '../../package.json'; @@ -120,7 +120,13 @@ class ControlPanel extends Panel { } }); - const transformPanel = new TransformPanel(events); + const transformPanel = new Panel({ + id: 'transform-panel', + class: 'control-panel', + headerText: 'TRANSFORM', + }); + + transformPanel.content.append(new Transform(events)); // camera panel const cameraPanel = new Panel({ diff --git a/src/ui/editor.ts b/src/ui/editor.ts index 6ab31f44..369de665 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -7,6 +7,7 @@ import { Events } from '../events'; import { Popup } from './popup'; import { ViewCube } from './view-cube'; import { Menu } from './menu'; +import { ScenePanel } from './scene-panel'; import { BottomToolbar } from './bottom-toolbar'; import { RightToolbar } from './right-toolbar'; import { Tooltips } from './tooltips'; @@ -88,6 +89,7 @@ class EditorUI { // bottom toolbar const menu = new Menu(events); + const scenePanel = new ScenePanel(events); const bottomToolbar = new BottomToolbar(events, tooltips); const rightToolbar = new RightToolbar(events, tooltips); @@ -95,6 +97,7 @@ class EditorUI { canvasContainer.append(filenameLabel); canvasContainer.append(toolsContainer); canvasContainer.append(menu); + canvasContainer.append(scenePanel); canvasContainer.append(bottomToolbar); canvasContainer.append(rightToolbar); diff --git a/src/ui/menu.scss b/src/ui/menu.scss index a968b606..e8e38f7d 100644 --- a/src/ui/menu.scss +++ b/src/ui/menu.scss @@ -2,6 +2,7 @@ position: absolute; top: 24px; left: 24px; + width: 320px; height: 54px; padding: 0px 8px 0px 0px; border-radius: 8px; diff --git a/src/ui/scene-panel.scss b/src/ui/scene-panel.scss new file mode 100644 index 00000000..312b6d67 --- /dev/null +++ b/src/ui/scene-panel.scss @@ -0,0 +1,12 @@ +#scene-panel { + position: absolute; + top: 102px; + left: 24px; + width: 320px; + border-radius: 8px; + border: 1px solid $bcg-dark; +} + +.scene-panel-header { + background-color: $bcg-dark; +} diff --git a/src/ui/scene-panel.ts b/src/ui/scene-panel.ts new file mode 100644 index 00000000..b9a547ba --- /dev/null +++ b/src/ui/scene-panel.ts @@ -0,0 +1,41 @@ +import { Container, Label } from 'pcui'; +import { Events } from '../events'; +import { Transform } from './transform'; + +class ScenePanel extends Container { + constructor(events: Events, args = {}) { + args = { + ...args, + id: 'scene-panel', + headerText: 'SCENE MANAGER' + }; + + super(args); + + const sceneHeader = new Container({ + class: 'scene-panel-header' + }); + + const sceneLabel = new Label({ + text: 'SCENE MANAGER' + }); + + sceneHeader.append(sceneLabel); + + const transformHeader = new Container({ + class: 'scene-panel-header' + }); + + const transformLabel = new Label({ + text: 'TRANSFORM' + }); + + transformHeader.append(transformLabel); + + this.append(sceneHeader); + this.append(transformHeader); + this.append(new Transform(events)); + } +}; + +export { ScenePanel }; diff --git a/src/ui/style.scss b/src/ui/style.scss index c63cbd7c..60122dfc 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -6,10 +6,12 @@ $clr-active: white; $bgclr-light: #333; $bgclr-active: #f60; +@import 'tooltips.scss'; @import 'menu.scss'; +@import 'scene-panel.scss'; +@import 'transform.scss'; @import 'bottom-toolbar.scss'; @import 'right-toolbar.scss'; -@import 'tooltips.scss'; * { font-size: 12px; @@ -340,15 +342,6 @@ body { flex-direction: column; } -#transform-panel > .pcui-panel-content { - display: flex; - flex-direction: column; -} - -.transform-panel-axis-label { - text-align: center; -} - #camera-panel > .pcui-panel-content { display: flex; flex-direction: column; diff --git a/src/ui/transform.scss b/src/ui/transform.scss new file mode 100644 index 00000000..d64cf854 --- /dev/null +++ b/src/ui/transform.scss @@ -0,0 +1,66 @@ + +#transform { + display: flex; + flex-direction: column; + + background-color: $bgclr-light; + + padding: 6px; +} + +.transform-row { + height: 32px; + line-height: 32px; + width: 100%; + display: flex; + flex-direction: row; + flex-grow: 1; + align-items: center; +} + +.transform-label { + width: 70px; + flex-shrink: 0; + flex-grow: 0; + margin: 0px; +} + +.transform-axis-label { + text-align: center; +} + +.transform-expand { + flex-grow: 1; +} + +$height: 22px; + +#transform > div > div.pcui-vector-input { + margin: 0px; + gap: 10px; + height: $height; +} + +#transform > div > div.pcui-numeric-input { + margin: 0px; + height: $height; + line-height: $height; + + & > input { + padding: 0px; + margin: 0px 0px 0px 4px; + height: $height; + } +} + +#transform > div > div > div.pcui-numeric-input { + margin: 0px; + height: $height; + line-height: $height; + + & > input { + padding: 0px; + margin: 0px 0px 0px 4px; + height: $height; + } +} \ No newline at end of file diff --git a/src/ui/transform-panel.ts b/src/ui/transform.ts similarity index 79% rename from src/ui/transform-panel.ts rename to src/ui/transform.ts index 28261642..9412b050 100644 --- a/src/ui/transform-panel.ts +++ b/src/ui/transform.ts @@ -1,40 +1,38 @@ -import { Container, Label, NumericInput, Panel, PanelArgs, VectorInput } from 'pcui'; +import { Container, ContainerArgs, Label, NumericInput, Panel, PanelArgs, VectorInput } from 'pcui'; import { Quat, Vec3 } from 'playcanvas'; import { Events } from '../events'; import { Splat } from '../splat'; -class TransformPanel extends Panel { - constructor(events: Events, args: PanelArgs = {}) { +class Transform extends Container { + constructor(events: Events, args: ContainerArgs = {}) { args = { - id: 'transform-panel', - class: 'control-panel', - headerText: 'TRANSFORM', - ...args + ...args, + id: 'transform' }; super(args); const axis = new Container({ - class: 'control-parent' + class: 'transform-row' }); const axisLabel = new Label({ - class: 'control-label', + class: 'transform-label', text: '' }); const xLabel = new Label({ - class: ['control-element-expand', 'transform-panel-axis-label'], + class: ['transform-expand', 'transform-label', 'transform-axis-label'], text: 'x' }); const yLabel = new Label({ - class: ['control-element-expand', 'transform-panel-axis-label'], + class: ['transform-expand', 'transform-label', 'transform-axis-label'], text: 'y' }); const zLabel = new Label({ - class: ['control-element-expand', 'transform-panel-axis-label'], + class: ['transform-expand', 'transform-label', 'transform-axis-label'], text: 'z' }); @@ -45,16 +43,16 @@ class TransformPanel extends Panel { // position const position = new Container({ - class: 'control-parent' + class: 'transform-row' }); const positionLabel = new Label({ - class: 'control-label', + class: 'transform-label', text: 'Position' }); const positionVector = new VectorInput({ - class: 'control-element-expand', + class: 'transform-expand', precision: 2, dimensions: 3, value: [0, 0, 0], @@ -66,16 +64,16 @@ class TransformPanel extends Panel { // rotation const rotation = new Container({ - class: 'control-parent' + class: 'transform-row' }); const rotationLabel = new Label({ - class: 'control-label', + class: 'transform-label', text: 'Rotation' }); const rotationVector = new VectorInput({ - class: 'control-element-expand', + class: 'transform-expand', precision: 2, dimensions: 3, value: [0, 0, 0], @@ -87,16 +85,16 @@ class TransformPanel extends Panel { // scale const scale = new Container({ - class: 'control-parent' + class: 'transform-row' }); const scaleLabel = new Label({ - class: 'control-label', + class: 'transform-label', text: 'Scale' }); const scaleInput = new NumericInput({ - class: 'control-element-expand', + class: 'transform-expand', precision: 2, value: 1, min: 0.01, @@ -172,4 +170,4 @@ class TransformPanel extends Panel { } } -export { TransformPanel }; +export { Transform }; From 343639fbb9b79c67f6c95439f0e47c6deb3c067b Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Mon, 29 Jul 2024 12:03:29 +0100 Subject: [PATCH 05/33] scene panel working --- src/ui/control-panel.ts | 93 ++-------------------------------- src/ui/editor.ts | 2 +- src/ui/scene-panel.scss | 35 ++++++++++++- src/ui/scene-panel.ts | 65 +++++++++++++++++++++--- src/ui/splat-list.scss | 86 +++++++++++++++++++++++++++++++ src/ui/splat-list.ts | 109 ++++++++++++++++++++++++++++++++++++---- src/ui/style.scss | 89 ++------------------------------ 7 files changed, 288 insertions(+), 191 deletions(-) create mode 100644 src/ui/splat-list.scss diff --git a/src/ui/control-panel.ts b/src/ui/control-panel.ts index 9fb58be2..32968951 100644 --- a/src/ui/control-panel.ts +++ b/src/ui/control-panel.ts @@ -1,9 +1,7 @@ import { BooleanInput, Button, Container, Label, NumericInput, Panel, RadioButton, SelectInput, SliderInput, VectorInput } from 'pcui'; import { Events } from '../events'; -import { SplatItem, SplatList } from './splat-list'; +import { SplatList } from './splat-list'; import { Transform } from './transform'; -import { Element, ElementType } from '../element'; -import { Splat } from '../splat'; import { version as appVersion } from '../../package.json'; class ControlPanel extends Panel { @@ -23,103 +21,22 @@ class ControlPanel extends Panel { // scene panel const scenePanel = new Panel({ - id: 'scene-panel', + id: 'control-panel-scene-panel', class: 'control-panel', headerText: 'SCENE' }); const splatListContainer = new Container({ - id: 'scene-panel-splat-list-container', + id: 'control-panel-scene-panel-splat-list-container', resizable: 'bottom', resizeMin: 50 }); - const splatList = new SplatList({ - id: 'scene-panel-splat-list' - }); + const splatList = new SplatList(events); splatListContainer.append(splatList); scenePanel.content.append(splatListContainer); - // handle selection and scene updates - - const items = new Map(); - - events.on('scene.elementAdded', (element: Element) => { - if (element.type === ElementType.splat) { - const splat = element as Splat; - const item = new SplatItem(splat.filename); - splatList.append(item); - items.set(splat, item); - - item.on('visible', () => { - splat.visible = true; - - // also select it if there is no other selection - if (!events.invoke('selection')) { - events.fire('selection', splat); - } - }); - item.on('invisible', () => splat.visible = false); - } - }); - - events.on('scene.elementRemoved', (element: Element) => { - if (element.type === ElementType.splat) { - const splat = element as Splat; - const item = items.get(splat); - if (item) { - splatList.remove(item); - items.delete(splat); - } - } - }); - - events.on('selection.changed', (selection: Splat) => { - items.forEach((value, key) => { - value.selected = key === selection; - }); - }); - - events.on('splat.vis', (splat: Splat) => { - const item = items.get(splat); - if (item) { - item.visible = splat.visible; - } - }); - - splatList.on('click', (item: SplatItem) => { - for (const [key, value] of items) { - if (item === value) { - events.fire('selection', key); - break; - } - } - }); - - splatList.on('removeClicked', async (item: SplatItem) => { - let splat; - for (const [key, value] of items) { - if (item === value) { - splat = key; - break; - } - } - - if (!splat) { - return; - } - - const result = await events.invoke('showPopup', { - type: 'yesno', - message: `Would you like to remove '${splat.filename}' from the scene?` - }); - - if (result?.action === 'yes') { - splat.destroy(); - } - }); - const transformPanel = new Panel({ id: 'transform-panel', class: 'control-panel', @@ -469,6 +386,7 @@ class ControlPanel extends Panel { id: 'control-panel-controls' }); + controlsContainer.append(scenePanel); controlsContainer.append(transformPanel); controlsContainer.append(cameraPanel) controlsContainer.append(selectionPanel); @@ -477,7 +395,6 @@ class ControlPanel extends Panel { controlsContainer.append(optionsPanel); // append - this.content.append(scenePanel); this.content.append(controlsContainer); rectSelectButton.on('click', () => { diff --git a/src/ui/editor.ts b/src/ui/editor.ts index 369de665..398c783e 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -89,7 +89,7 @@ class EditorUI { // bottom toolbar const menu = new Menu(events); - const scenePanel = new ScenePanel(events); + const scenePanel = new ScenePanel(events, tooltips); const bottomToolbar = new BottomToolbar(events, tooltips); const rightToolbar = new RightToolbar(events, tooltips); diff --git a/src/ui/scene-panel.scss b/src/ui/scene-panel.scss index 312b6d67..d0d95ad7 100644 --- a/src/ui/scene-panel.scss +++ b/src/ui/scene-panel.scss @@ -4,9 +4,42 @@ left: 24px; width: 320px; border-radius: 8px; - border: 1px solid $bcg-dark; + overflow: hidden; + + background-color: $bcg-primary; + + pointer-events: all; } .scene-panel-header { + display: flex; + flex-direction: row; + padding: 2px; + background-color: $bcg-dark; + + & > .scene-panel-header-icon { + font-family: pc-icon; + font-weight: bold; + font-size: 13px; + color: #ff6600; + } + + & > .scene-panel-header-label { + color: $text-primary; + font-weight: bold; + flex-grow: 1; + } + + & > .scene-panel-header-button { + font-family: pc-icon; + font-weight: bold; + font-size: 13px; + color: #ff6600; + + &:hover { + color: #ff9900; + cursor: pointer; + } + } } diff --git a/src/ui/scene-panel.ts b/src/ui/scene-panel.ts index b9a547ba..8a9a75c8 100644 --- a/src/ui/scene-panel.ts +++ b/src/ui/scene-panel.ts @@ -1,38 +1,91 @@ import { Container, Label } from 'pcui'; import { Events } from '../events'; +import { Tooltips } from './tooltips'; +import { SplatList } from './splat-list'; import { Transform } from './transform'; +const CLASS = 'scene-panel'; + class ScenePanel extends Container { - constructor(events: Events, args = {}) { + constructor(events: Events, tooltips: Tooltips, args = {}) { args = { ...args, - id: 'scene-panel', + id: CLASS, headerText: 'SCENE MANAGER' }; super(args); + const handleDown = (event: PointerEvent) => { + event.preventDefault(); + event.stopPropagation(); + }; + + this.dom.addEventListener('pointerdown', (event) => { + handleDown(event); + }); + const sceneHeader = new Container({ - class: 'scene-panel-header' + class: `${CLASS}-header` + }); + + const sceneIcon = new Label({ + text: '\uE344', + class: `${CLASS}-header-icon` }); const sceneLabel = new Label({ - text: 'SCENE MANAGER' + text: 'SCENE MANAGER', + class: `${CLASS}-header-label` }); + const sceneImport = new Label({ + text: '\uE245', + class: `${CLASS}-header-button` + }); + + const sceneNew = new Label({ + text: '\uE208', + class: `${CLASS}-header-button` + }); + + sceneHeader.append(sceneIcon); sceneHeader.append(sceneLabel); + sceneHeader.append(sceneImport); + sceneHeader.append(sceneNew); + + sceneImport.on('click', () => { + events.fire('scene.open'); + }); + + sceneNew.on('click', () => { + events.fire('scene.new'); + }); + + tooltips.register(sceneImport, 'Import Scene', 'top'); + tooltips.register(sceneNew, 'New Scene', 'top'); + + const splatList = new SplatList(events); const transformHeader = new Container({ - class: 'scene-panel-header' + class: `${CLASS}-header` + }); + + const transformIcon = new Label({ + text: '\uE111', + class: `${CLASS}-header-icon` }); const transformLabel = new Label({ - text: 'TRANSFORM' + text: 'TRANSFORM', + class: `${CLASS}-header-label` }); + transformHeader.append(transformIcon); transformHeader.append(transformLabel); this.append(sceneHeader); + this.append(splatList); this.append(transformHeader); this.append(new Transform(events)); } diff --git a/src/ui/splat-list.scss b/src/ui/splat-list.scss new file mode 100644 index 00000000..53c3cda3 --- /dev/null +++ b/src/ui/splat-list.scss @@ -0,0 +1,86 @@ +.splat-list { + min-height: 80px; + padding: 1px 0px; +} + +.splat-item { + display: flex; + flex-direction: row; + padding: 2px; + + &:hover:not(.selected).visible { + cursor: pointer; + } + + &.selected { + background-color: $bcg-darker; + } +} + +.splat-item-text { + flex-grow: 1; + flex-shrink: 1; + + .visible & { + &:hover:not(.selected) { + color: $text-primary; + } + } + + .selected & { + color: $text-primary; + } +} + +.splat-item-visible { + flex-grow: 0; + flex-shrink: 0; + + padding: 4px; + width: 16px; + height: 16px; + line-height: 16px; + + color: black; + background-color: transparent; + + cursor: pointer; + + .visible & { + color: $text-secondary; + } + + &:hover { + background-color: $bcg-dark; + } + + &::after { + font-family: pc-icon; + font-size: 16px; + content: '\E117'; + } +} + +.splat-item-delete { + flex-grow: 0; + flex-shrink: 0; + + padding: 4px; + width: 16px; + height: 16px; + line-height: 16px; + + color: $text-secondary; + + cursor: pointer; + + &:hover { + background-color: $bcg-dark; + } + + &::after { + font-family: pc-icon; + font-size: 14px; + content: '\E124'; + } +} \ No newline at end of file diff --git a/src/ui/splat-list.ts b/src/ui/splat-list.ts index 1c9decfc..e2cb32bc 100644 --- a/src/ui/splat-list.ts +++ b/src/ui/splat-list.ts @@ -1,4 +1,7 @@ -import { Container, Label, Element } from 'pcui'; +import { Container, Label, Element as PcuiElement } from 'pcui'; +import { Events } from '../events'; +import { Splat } from '../splat'; +import { Element, ElementType } from '../element'; class SplatItem extends Container { getSelected: () => boolean; @@ -10,22 +13,22 @@ class SplatItem extends Container { constructor(name: string, args = {}) { args = { ...args, - class: 'scene-panel-splat-item' + class: ['splat-item', 'visible'] }; super(args); const text = new Label({ - class: 'scene-panel-splat-item-text', + class: 'splat-item-text', text: name }); - const visible = new Element({ - class: ['scene-panel-splat-item-visible', 'checked'] + const visible = new PcuiElement({ + class: 'splat-item-visible', }); - const remove = new Element({ - class: 'scene-panel-splat-item-delete' + const remove = new PcuiElement({ + class: 'splat-item-delete' }); this.append(text); @@ -49,16 +52,16 @@ class SplatItem extends Container { }; this.getVisible = () => { - return visible.class.contains('checked'); + return this.class.contains('visible'); }; this.setVisible = (value: boolean) => { if (value !== this.visible) { if (value) { - visible.class.add('checked'); + this.class.add('visible'); this.emit('visible', this); } else { - visible.class.remove('checked'); + this.class.remove('visible'); this.emit('invisible', this); } } @@ -102,6 +105,92 @@ class SplatItem extends Container { } class SplatList extends Container { + constructor(events: Events, args = {}) { + args = { + ...args, + class: 'splat-list' + }; + + super(args); + + const items = new Map(); + + events.on('scene.elementAdded', (element: Element) => { + if (element.type === ElementType.splat) { + const splat = element as Splat; + const item = new SplatItem(splat.filename); + this.append(item); + items.set(splat, item); + + item.on('visible', () => { + splat.visible = true; + + // also select it if there is no other selection + if (!events.invoke('selection')) { + events.fire('selection', splat); + } + }); + item.on('invisible', () => splat.visible = false); + } + }); + + events.on('scene.elementRemoved', (element: Element) => { + if (element.type === ElementType.splat) { + const splat = element as Splat; + const item = items.get(splat); + if (item) { + this.remove(item); + items.delete(splat); + } + } + }); + + events.on('selection.changed', (selection: Splat) => { + items.forEach((value, key) => { + value.selected = key === selection; + }); + }); + + events.on('splat.vis', (splat: Splat) => { + const item = items.get(splat); + if (item) { + item.visible = splat.visible; + } + }); + + this.on('click', (item: SplatItem) => { + for (const [key, value] of items) { + if (item === value) { + events.fire('selection', key); + break; + } + } + }); + + this.on('removeClicked', async (item: SplatItem) => { + let splat; + for (const [key, value] of items) { + if (item === value) { + splat = key; + break; + } + } + + if (!splat) { + return; + } + + const result = await events.invoke('showPopup', { + type: 'yesno', + message: `Would you like to remove '${splat.filename}' from the scene?` + }); + + if (result?.action === 'yes') { + splat.destroy(); + } + }); + } + protected _onAppendChild(element: Element): void { super._onAppendChild(element); diff --git a/src/ui/style.scss b/src/ui/style.scss index 60122dfc..22e88e7c 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -9,6 +9,7 @@ $bgclr-active: #f60; @import 'tooltips.scss'; @import 'menu.scss'; @import 'scene-panel.scss'; +@import 'splat-list.scss'; @import 'transform.scss'; @import 'bottom-toolbar.scss'; @import 'right-toolbar.scss'; @@ -166,105 +167,23 @@ body { #keyboard-panel & { content: '\E136'}; } -#scene-panel { +#control-panel-scene-panel { flex-shrink: 0; } -#scene-panel-splat-list-container { +#control-panel-scene-panel-splat-list-container { // padding: 10px; overflow: scroll; width: 100%; height: 100px; display: flex; - flex-direction: row; + flex-direction: column; flex-grow: 1; background-color: $bcg-primary; } -#scene-panel-splat-list { - width: 100%; - height: 100%; - padding: 4px; -} - -#scene-panel-splat-list .pcui-treeview-item-contents.pcui-treeview-item-selected { - background-color: royalblue; -} - -.scene-panel-splat-item { - display: flex; - flex-direction: row; - - border-bottom: 1px solid $bcg-darker; - - &:hover:not(.selected) { - background-color: $bcg-darkest; - } - - &.selected { - color: $text-primary; - background-color: royalblue; - } -} - -.scene-panel-splat-item-text { - flex-grow: 1; - flex-shrink: 1; - - .selected & { - color: $text-primary; - } -} - -.scene-panel-splat-item-visible { - flex-grow: 0; - flex-shrink: 0; - - // border: 1px solid black; - padding: 4px; - width: 16px; - height: 16px; - line-height: 16px; - - color: black; - background-color: transparent; - - cursor: pointer; - - &.checked { - color: $text-active; - } - - &::after { - font-family: pc-icon; - font-size: 18px; - content: '\E117'; - } -} - -.scene-panel-splat-item-delete { - flex-grow: 0; - flex-shrink: 0; - - // border: 1px solid black; - padding: 4px; - width: 16px; - height: 16px; - line-height: 16px; - - color: $text-secondary; - - cursor: pointer; - - &::after { - font-family: pc-icon; - font-size: 16px; - content: '\E124'; - } -} - #file-selector { display: none; } From 6775db25b0fafca3e7c868a9e63e3ab83ba92348 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Mon, 29 Jul 2024 14:32:40 +0100 Subject: [PATCH 06/33] add logo svg --- src/svg/playcanvas-logo.svg | 13 +++++++++++ src/ui/menu.scss | 6 ++--- src/ui/menu.ts | 44 ++++++++++++++++++------------------- src/ui/toolbar.ts | 1 - 4 files changed, 37 insertions(+), 27 deletions(-) create mode 100644 src/svg/playcanvas-logo.svg diff --git a/src/svg/playcanvas-logo.svg b/src/svg/playcanvas-logo.svg new file mode 100644 index 00000000..75eb3134 --- /dev/null +++ b/src/svg/playcanvas-logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ui/menu.scss b/src/ui/menu.scss index e8e38f7d..844f105e 100644 --- a/src/ui/menu.scss +++ b/src/ui/menu.scss @@ -3,14 +3,12 @@ top: 24px; left: 24px; width: 320px; - height: 54px; - padding: 0px 8px 0px 0px; + height: 50px; border-radius: 8px; - border: 1px solid $bcg-dark; overflow: hidden; - background-color: $bcg-dark; + background-color: $bcg-primary; display: flex; flex-direction: row; diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 879b0b35..23d37477 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -2,7 +2,7 @@ import { Button, Container, Menu as PcuiMenu } from 'pcui'; import { Events } from '../events'; import { version } from '../../package.json'; -import logo from './playcanvas-logo.png'; +import logoSvg from '../svg/playcanvas-logo.svg'; class Menu extends Container { constructor(events: Events, args = {}) { @@ -24,15 +24,15 @@ class Menu extends Container { const icon = document.createElement('img'); icon.setAttribute('id', 'menu-icon'); - icon.src = logo; + icon.src = logoSvg; - const file = new Button({ - text: 'File', + const scene = new Button({ + text: 'Scene', class: 'menu-button' }); - const scene = new Button({ - text: 'Scene', + const selection = new Button({ + text: 'Selection', class: 'menu-button' }); @@ -42,11 +42,11 @@ class Menu extends Container { }); this.dom.appendChild(icon); - this.append(file); this.append(scene); + this.append(selection); this.append(help); - const fileMenu = new PcuiMenu({ + const sceneMenu = new PcuiMenu({ id: 'menu-dropdown', items: [{ class: 'menu-dropdown-item', @@ -94,7 +94,7 @@ class Menu extends Container { }] }); - const sceneMenu = new PcuiMenu({ + const selectionMenu = new PcuiMenu({ id: 'menu-dropdown', items: [{ class: 'menu-dropdown-item', @@ -134,22 +134,22 @@ class Menu extends Container { }] }); - this.append(fileMenu); this.append(sceneMenu); + this.append(selectionMenu); this.append(helpMenu); - file.on('click', () => { - const r = file.dom.getBoundingClientRect(); - fileMenu.position(r.left, r.bottom + 8); - fileMenu.hidden = false; - }); - scene.on('click', () => { const r = scene.dom.getBoundingClientRect(); sceneMenu.position(r.left, r.bottom + 8); sceneMenu.hidden = false; }); + selection.on('click', () => { + const r = selection.dom.getBoundingClientRect(); + selectionMenu.position(r.left, r.bottom + 8); + selectionMenu.hidden = false; + }); + help.on('click', () => { const r = help.dom.getBoundingClientRect(); helpMenu.position(r.left, r.bottom + 8); @@ -157,18 +157,18 @@ class Menu extends Container { }); window.addEventListener('click', (e: Event) => { - if (!fileMenu.hidden && - !fileMenu.dom.contains(e.target as Node) && - e.target !== file.dom) { - fileMenu.hidden = true; - } - if (!sceneMenu.hidden && !sceneMenu.dom.contains(e.target as Node) && e.target !== scene.dom) { sceneMenu.hidden = true; } + if (!selectionMenu.hidden && + !selectionMenu.dom.contains(e.target as Node) && + e.target !== selection.dom) { + selectionMenu.hidden = true; + } + if (!helpMenu.hidden && !helpMenu.dom.contains(e.target as Node) && e.target !== help.dom) { diff --git a/src/ui/toolbar.ts b/src/ui/toolbar.ts index 94d49b2c..181cb5ce 100644 --- a/src/ui/toolbar.ts +++ b/src/ui/toolbar.ts @@ -1,5 +1,4 @@ import { Button, Container, Element, Menu } from 'pcui'; -import { ShortcutsPopup } from './shortcuts-popup'; import { Tooltip } from './tooltip'; import { Events } from '../events'; import logo from './playcanvas-logo.png'; From a78423e25f8e09700c8e69a04113207b0ea6b09c Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Tue, 30 Jul 2024 12:24:09 +0100 Subject: [PATCH 07/33] added view options pane --- src/camera.ts | 2 +- src/editor.ts | 31 ++- src/file-handler.ts | 19 +- src/main.ts | 2 +- src/splat.ts | 2 +- ...ide-selection.svg => show-hide-splats.svg} | 0 src/ui/bottom-toolbar.ts | 44 +--- src/ui/control-panel.ts | 82 ++++---- src/ui/editor.ts | 3 + src/ui/menu.ts | 25 ++- src/ui/panel.scss | 42 ++++ src/ui/right-toolbar.ts | 57 ++++- src/ui/scene-panel.scss | 40 ---- src/ui/scene-panel.ts | 24 +-- src/ui/style.scss | 21 +- src/ui/toolbar.ts | 8 +- src/ui/view-panel.scss | 51 +++++ src/ui/view-panel.ts | 194 ++++++++++++++++++ 18 files changed, 470 insertions(+), 177 deletions(-) rename src/svg/{show-hide-selection.svg => show-hide-splats.svg} (100%) create mode 100644 src/ui/panel.scss create mode 100644 src/ui/view-panel.scss create mode 100644 src/ui/view-panel.ts diff --git a/src/camera.ts b/src/camera.ts index c4147cbe..7ddef6c2 100644 --- a/src/camera.ts +++ b/src/camera.ts @@ -476,7 +476,7 @@ class Camera extends Element { const device = this.scene.graphicsDevice as WebglGraphicsDevice; const events = this.scene.events; - const alpha = events.invoke('camera.mode') === 'rings' ? 0.0 : 0.2; + const alpha = events.invoke('camera.debug') && events.invoke('camera.mode') === 'rings' ? 0.0 : 0.2; // hide non-selected elements const splats = this.scene.getElementsByType(ElementType.splat); diff --git a/src/editor.ts b/src/editor.ts index c9fb3c26..58f60c29 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -94,7 +94,7 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S // add unsaved changes warning message. window.addEventListener("beforeunload", function (e) { - if (editHistory.cursor === lastExportCursor) { + if (!events.invoke('scene.dirty')) { // if the undo cursor matches last export, then we have no unsaved changes return undefined; } @@ -104,6 +104,10 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S return msg; }); + events.function('scene.dirty', () => { + return editHistory.cursor !== lastExportCursor; + }); + events.on('scene.saved', () => { lastExportCursor = editHistory.cursor; }); @@ -112,24 +116,31 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S scene.forceRender = true; }); - events.on('splatSize', () => { + events.on('camera.debug', () => { scene.forceRender = true; }); - events.on('show.gridOn', () => { - scene.grid.visible = true; + events.on('splatSize', () => { + scene.forceRender = true; }); - events.on('show.gridOff', () => { - scene.grid.visible = false; + const setGridVisible = (visible: boolean) => { + if (visible !== scene.grid.visible) { + scene.grid.visible = visible; + events.fire('grid.visible', visible); + } + }; + + events.function('grid.visible', () => { + return scene.grid.visible; }); - events.on('show.gridToggle', () => { - scene.grid.visible = !scene.grid.visible; + events.on('grid.toggleVisible', () => { + setGridVisible(!scene.grid.visible); }); - events.function('show.grid', () => { - return scene.grid.visible; + events.on('grid.setVisible', (visible: boolean) => { + setGridVisible(visible); }); events.on('camera.focus', () => { diff --git a/src/file-handler.ts b/src/file-handler.ts index e5b1fbea..dacdf5c3 100644 --- a/src/file-handler.ts +++ b/src/file-handler.ts @@ -124,12 +124,25 @@ const initFileHandler = async (scene: Scene, events: Events, dropTarget: HTMLEle return (scene.getElementsByType(ElementType.splat) as Splat[]).filter(splat => splat.visible); }; - events.function('scene.canSave', () => { - return getSplats().length > 0; + events.function('scene.empty', () => { + return getSplats().length === 0; }); - events.on('scene.new', () => { + events.function('scene.new', async () => { + if (events.invoke('scene.dirty')) { + const result = await events.invoke('showPopup', { + type: 'yesno', + message: `You have unsaved changed. Are you sure you want to clear the scene?` + }); + + if (result.action !== 'yes') { + return false; + } + } + scene.clear(); + + return true; }); events.on('scene.open', async () => { diff --git a/src/main.ts b/src/main.ts index bfdeec10..538c5e88 100644 --- a/src/main.ts +++ b/src/main.ts @@ -65,7 +65,7 @@ const initShortcuts = (events: Events) => { shortcuts.register(['1'], { event: 'tool.move', sticky: true }); shortcuts.register(['2'], { event: 'tool.rotate', sticky: true }); shortcuts.register(['3'], { event: 'tool.scale', sticky: true }); - shortcuts.register(['G', 'g'], { event: 'show.gridToggle' }); + shortcuts.register(['G', 'g'], { event: 'grid.toggleVisible' }); shortcuts.register(['C', 'c'], { event: 'tool.toggleCoordSpace' }); shortcuts.register(['F', 'f'], { event: 'camera.focus' }); shortcuts.register(['B', 'b'], { event: 'tool.brushSelection', sticky: true }); diff --git a/src/splat.ts b/src/splat.ts index 88fb1189..dbd3c8da 100644 --- a/src/splat.ts +++ b/src/splat.ts @@ -325,7 +325,7 @@ class Splat extends Element { const events = this.scene.events; const selected = events.invoke('selection') === this; const cameraMode = events.invoke('camera.mode'); - const splatSize = events.invoke('splatSize'); + const splatSize = events.invoke('camera.debug') ? events.invoke('splatSize') : 0; // configure rings rendering const material = this.entity.gsplat.instance.material; diff --git a/src/svg/show-hide-selection.svg b/src/svg/show-hide-splats.svg similarity index 100% rename from src/svg/show-hide-selection.svg rename to src/svg/show-hide-splats.svg diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts index fdd72416..07fc6ff5 100644 --- a/src/ui/bottom-toolbar.ts +++ b/src/ui/bottom-toolbar.ts @@ -8,8 +8,11 @@ import pickerSvg from '../svg/select-picker.svg'; import lassoSvg from '../svg/select-lasso.svg'; import brushSvg from '../svg/select-brush.svg'; import cropSvg from '../svg/crop.svg'; -import frameSelectionSvg from '../svg/frame-selection.svg'; -import showHideSelectionSvg from '../svg/show-hide-selection.svg'; + +const createSvg = (svgString: string) => { + const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length)); + return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement; +}; class BottomToolbar extends Container { constructor(events: Events, tooltips: Tooltips, args = {}) { @@ -29,11 +32,6 @@ class BottomToolbar extends Container { handleDown(event); }); - const createSvg = (svgString: string) => { - const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length)); - return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement; - }; - const undo = new Button({ id: 'bottom-toolbar-undo', class: 'bottom-toolbar-button' @@ -64,24 +62,12 @@ class BottomToolbar extends Container { class: ['bottom-toolbar-tool', 'disabled'] }); - const frame = new Button({ - id: 'bottom-toolbar-frame', - class: 'bottom-toolbar-button' - }); - - const showHide = new Button({ - id: 'bottom-toolbar-show-hide', - class: ['bottom-toolbar-tool', 'active'] - }); - undo.dom.appendChild(createSvg(undoSvg)); redo.dom.appendChild(createSvg(redoSvg)); picker.dom.appendChild(createSvg(pickerSvg)); brush.dom.appendChild(createSvg(brushSvg)); lasso.dom.appendChild(createSvg(lassoSvg)); crop.dom.appendChild(createSvg(cropSvg)); - frame.dom.appendChild(createSvg(frameSelectionSvg)); - showHide.dom.appendChild(createSvg(showHideSelectionSvg)); this.append(undo); this.append(redo); @@ -91,8 +77,6 @@ class BottomToolbar extends Container { this.append(lasso); this.append(crop); this.append(new Element({ class: 'bottom-toolbar-separator' })); - this.append(frame); - this.append(showHide); undo.dom.addEventListener('click', () => { events.fire('edit.undo'); @@ -110,22 +94,6 @@ class BottomToolbar extends Container { events.fire('tool.brushSelection'); }); - frame.dom.addEventListener('click', () => { - events.fire('camera.focus'); - }); - - let splatSizeSave = 2; - events.on('splatSize', (size: number) => { - if (size !== 0) { - splatSizeSave = size; - } - showHide.class[size === 0 ? 'remove' : 'add']('active'); - }); - - showHide.dom.addEventListener('click', () => { - events.fire('splatSize', events.invoke('splatSize') === 0 ? splatSizeSave : 0); - }); - events.on('tool.activated', (toolName: string) => { picker.class[toolName === 'rectSelection' ? 'add' : 'remove']('active'); brush.class[toolName === 'brushSelection' ? 'add' : 'remove']('active'); @@ -138,8 +106,6 @@ class BottomToolbar extends Container { tooltips.register(brush, 'Select Brush'); tooltips.register(lasso, 'Select Lasso'); tooltips.register(crop, 'Crop'); - tooltips.register(frame, 'Frame Selection'); - tooltips.register(showHide, 'Show/Hide Selection'); } } diff --git a/src/ui/control-panel.ts b/src/ui/control-panel.ts index 32968951..2d549d24 100644 --- a/src/ui/control-panel.ts +++ b/src/ui/control-panel.ts @@ -1,7 +1,5 @@ import { BooleanInput, Button, Container, Label, NumericInput, Panel, RadioButton, SelectInput, SliderInput, VectorInput } from 'pcui'; import { Events } from '../events'; -import { SplatList } from './splat-list'; -import { Transform } from './transform'; import { version as appVersion } from '../../package.json'; class ControlPanel extends Panel { @@ -19,32 +17,6 @@ class ControlPanel extends Panel { super(args); - // scene panel - const scenePanel = new Panel({ - id: 'control-panel-scene-panel', - class: 'control-panel', - headerText: 'SCENE' - }); - - const splatListContainer = new Container({ - id: 'control-panel-scene-panel-splat-list-container', - resizable: 'bottom', - resizeMin: 50 - }); - - const splatList = new SplatList(events); - - splatListContainer.append(splatList); - scenePanel.content.append(splatListContainer); - - const transformPanel = new Panel({ - id: 'transform-panel', - class: 'control-panel', - headerText: 'TRANSFORM', - }); - - transformPanel.content.append(new Transform(events)); - // camera panel const cameraPanel = new Panel({ id: 'camera-panel', @@ -386,8 +358,6 @@ class ControlPanel extends Panel { id: 'control-panel-controls' }); - controlsContainer.append(scenePanel); - controlsContainer.append(transformPanel); controlsContainer.append(cameraPanel) controlsContainer.append(selectionPanel); controlsContainer.append(showPanel); @@ -475,20 +445,58 @@ class ControlPanel extends Panel { addButton.on('click', () => performSelect('add')); removeButton.on('click', () => performSelect('remove')); + // camera mode + + let activeMode = 'centers'; + + const setCameraMode = (mode: string) => { + if (mode !== activeMode) { + activeMode = mode; + events.fire('camera.mode', activeMode); + } + }; + events.function('camera.mode', () => { - return modeSelect.value; + return activeMode; + }); + + events.on('camera.setMode', (mode: string) => { + setCameraMode(mode); + }); + + events.on('camera.toggleMode', () => { + setCameraMode(events.invoke('camera.mode') === 'centers' ? 'rings' : 'centers'); }); events.on('camera.mode', (mode: string) => { modeSelect.value = mode; }); - events.on('camera.toggleMode', () => { - modeSelect.value = modeSelect.value === 'centers' ? 'rings' : 'centers'; + // camera debug + + let cameraDebug = true; + + const setCameraDebug = (enabled: boolean) => { + if (enabled !== cameraDebug) { + cameraDebug = enabled; + events.fire('camera.debug', cameraDebug); + } + }; + + events.function('camera.debug', () => { + return cameraDebug; + }); + + events.on('camera.setDebug', (value: boolean) => { + setCameraDebug(value); + }); + + events.on('camera.toggleDebug', () => { + setCameraDebug(!events.invoke('camera.debug')); }); modeSelect.on('change', (value: string) => { - events.fire('camera.mode', value); + setCameraMode(value); }); events.on('splatSize', (value: number) => { @@ -508,7 +516,11 @@ class ControlPanel extends Panel { }); showGridToggle.on('change', (enabled: boolean) => { - events.fire(enabled ? 'show.gridOn' : 'show.gridOff'); + events.fire('grid.setVisible', enabled); + }); + + events.on('grid.visible', (visible: boolean) => { + showGridToggle.value = visible; }); selectAllButton.on('click', () => { diff --git a/src/ui/editor.ts b/src/ui/editor.ts index 398c783e..53447250 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -8,6 +8,7 @@ import { Popup } from './popup'; import { ViewCube } from './view-cube'; import { Menu } from './menu'; import { ScenePanel } from './scene-panel'; +import { ViewPanel } from './view-panel'; import { BottomToolbar } from './bottom-toolbar'; import { RightToolbar } from './right-toolbar'; import { Tooltips } from './tooltips'; @@ -90,6 +91,7 @@ class EditorUI { // bottom toolbar const menu = new Menu(events); const scenePanel = new ScenePanel(events, tooltips); + const viewPanel = new ViewPanel(events, tooltips); const bottomToolbar = new BottomToolbar(events, tooltips); const rightToolbar = new RightToolbar(events, tooltips); @@ -98,6 +100,7 @@ class EditorUI { canvasContainer.append(toolsContainer); canvasContainer.append(menu); canvasContainer.append(scenePanel); + canvasContainer.append(viewPanel); canvasContainer.append(bottomToolbar); canvasContainer.append(rightToolbar); diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 23d37477..5467042b 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -52,29 +52,38 @@ class Menu extends Container { class: 'menu-dropdown-item', text: 'New', icon: 'E208', - onSelect: () => events.fire('scene.new') + onSelect: () => events.invoke('scene.new') }, { class: 'menu-dropdown-item', - text: 'Open...', + text: 'Open', icon: 'E226', + onSelect: async () => { + if (await events.invoke('scene.new')) { + events.fire('scene.open'); + } + } + }, { + class: 'menu-dropdown-item', + text: 'Import', + icon: 'E245', onSelect: () => events.fire('scene.open') }, { class: 'menu-dropdown-item', text: 'Save', icon: 'E216', onSelect: () => events.fire('scene.save'), - onIsEnabled: () => events.invoke('scene.canSave') + onIsEnabled: () => !events.invoke('scene.empty') }, { class: 'menu-dropdown-item', text: 'Save As...', icon: 'E216', onSelect: () => events.fire('scene.saveAs'), - onIsEnabled: () => events.invoke('scene.canSave') + onIsEnabled: () => !events.invoke('scene.empty') }, { class: 'menu-dropdown-item', text: 'Export', icon: 'E225', - onIsEnabled: () => events.invoke('scene.canSave'), + onIsEnabled: () => !events.invoke('scene.empty'), items: [{ class: 'menu-dropdown-item', text: 'Compressed Ply', @@ -98,17 +107,17 @@ class Menu extends Container { id: 'menu-dropdown', items: [{ class: 'menu-dropdown-item', - text: 'Select All', + text: 'All', icon: 'E0020', onSelect: () => events.fire('selection.all') }, { class: 'menu-dropdown-item', - text: 'Select None', + text: 'None', icon: 'E0020', onSelect: () => events.fire('selection.none') }, { class: 'menu-dropdown-item', - text: 'Invert Selection', + text: 'Invert', icon: 'E0020', onSelect: () => events.fire('selection.invert') }] diff --git a/src/ui/panel.scss b/src/ui/panel.scss new file mode 100644 index 00000000..74f2dc30 --- /dev/null +++ b/src/ui/panel.scss @@ -0,0 +1,42 @@ +.panel { + position: absolute; + border-radius: 8px; + overflow: hidden; + + background-color: $bcg-primary; + + pointer-events: all; + + & > .panel-header { + display: flex; + flex-direction: row; + padding: 2px; + + background-color: $bcg-dark; + + & > .panel-header-icon { + font-family: pc-icon; + font-weight: bold; + font-size: 13px; + color: #ff6600; + } + + & > .panel-header-label { + color: $text-primary; + font-weight: bold; + flex-grow: 1; + } + + & > .panel-header-button { + font-family: pc-icon; + font-weight: bold; + font-size: 13px; + color: #ff6600; + + &:hover { + color: #ff9900; + cursor: pointer; + } + } + } +} diff --git a/src/ui/right-toolbar.ts b/src/ui/right-toolbar.ts index 23e98a55..42d04e21 100644 --- a/src/ui/right-toolbar.ts +++ b/src/ui/right-toolbar.ts @@ -2,6 +2,14 @@ import { Button, Container } from 'pcui'; import { Events } from '../events'; import { Tooltips } from './tooltips'; +import showHideSplatsSvg from '../svg/show-hide-splats.svg'; +import frameSelectionSvg from '../svg/frame-selection.svg'; + +const createSvg = (svgString: string) => { + const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length)); + return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement; +}; + class RightToolbar extends Container { constructor(events: Events, tooltips: Tooltips, args = {}) { args = { @@ -20,6 +28,22 @@ class RightToolbar extends Container { handleDown(event); }); + const showHide = new Button({ + id: 'right-toolbar-show-hide', + class: ['right-toolbar-button', 'active'] + }); + + const frameSelection = new Button({ + id: 'right-toolbar-frame-selection', + class: 'right-toolbar-button' + }); + + const options = new Button({ + id: 'right-toolbar-options', + class: 'right-toolbar-button', + icon: 'E283' + }); + const translate = new Button({ id: 'right-toolbar-translate', class: 'right-toolbar-button', @@ -38,23 +62,50 @@ class RightToolbar extends Container { icon: 'E112' }); + showHide.dom.appendChild(createSvg(showHideSplatsSvg)); + frameSelection.dom.appendChild(createSvg(frameSelectionSvg)); + + this.append(showHide); + this.append(frameSelection); + this.append(options); this.append(translate); this.append(rotate); this.append(scale); + tooltips.register(showHide, 'Show/Hide Splats', 'left'); + tooltips.register(frameSelection, 'Frame Selection', 'left'); + tooltips.register(options, 'Options', 'left'); + tooltips.register(translate, 'Translate', 'left'); + tooltips.register(rotate, 'Rotate', 'left'); + tooltips.register(scale, 'Scale', 'left'); + + // add event handlers + + options.on('click', () => events.fire('viewPanel.toggleVisible')); + frameSelection.on('click', () => events.fire('camera.focus')); translate.on('click', () => events.fire('tool.move')); rotate.on('click', () => events.fire('tool.rotate')); scale.on('click', () => events.fire('tool.scale')); + events.on('viewPanel.visible', (visible: boolean) => { + options.class[visible ? 'add' : 'remove']('active'); + }); + events.on('tool.activated', (toolName: string) => { translate.class[toolName === 'move' ? 'add' : 'remove']('active'); rotate.class[toolName === 'rotate' ? 'add' : 'remove']('active'); scale.class[toolName === 'scale' ? 'add' : 'remove']('active'); }); - tooltips.register(translate, 'Translate', 'left'); - tooltips.register(rotate, 'Rotate', 'left'); - tooltips.register(scale, 'Scale', 'left'); + // show-hide splats + + events.on('camera.debug', (debug: boolean) => { + showHide.class[debug ? 'add' : 'remove']('active'); + }); + + showHide.dom.addEventListener('click', () => { + events.fire('camera.toggleDebug'); + }); } }; diff --git a/src/ui/scene-panel.scss b/src/ui/scene-panel.scss index d0d95ad7..75ae0940 100644 --- a/src/ui/scene-panel.scss +++ b/src/ui/scene-panel.scss @@ -1,45 +1,5 @@ #scene-panel { - position: absolute; top: 102px; left: 24px; width: 320px; - border-radius: 8px; - overflow: hidden; - - background-color: $bcg-primary; - - pointer-events: all; -} - -.scene-panel-header { - display: flex; - flex-direction: row; - padding: 2px; - - background-color: $bcg-dark; - - & > .scene-panel-header-icon { - font-family: pc-icon; - font-weight: bold; - font-size: 13px; - color: #ff6600; - } - - & > .scene-panel-header-label { - color: $text-primary; - font-weight: bold; - flex-grow: 1; - } - - & > .scene-panel-header-button { - font-family: pc-icon; - font-weight: bold; - font-size: 13px; - color: #ff6600; - - &:hover { - color: #ff9900; - cursor: pointer; - } - } } diff --git a/src/ui/scene-panel.ts b/src/ui/scene-panel.ts index 8a9a75c8..1d9a9cb3 100644 --- a/src/ui/scene-panel.ts +++ b/src/ui/scene-panel.ts @@ -4,14 +4,12 @@ import { Tooltips } from './tooltips'; import { SplatList } from './splat-list'; import { Transform } from './transform'; -const CLASS = 'scene-panel'; - class ScenePanel extends Container { constructor(events: Events, tooltips: Tooltips, args = {}) { args = { ...args, - id: CLASS, - headerText: 'SCENE MANAGER' + id: 'scene-panel', + class: 'panel' }; super(args); @@ -26,27 +24,27 @@ class ScenePanel extends Container { }); const sceneHeader = new Container({ - class: `${CLASS}-header` + class: `panel-header` }); const sceneIcon = new Label({ text: '\uE344', - class: `${CLASS}-header-icon` + class: `panel-header-icon` }); const sceneLabel = new Label({ text: 'SCENE MANAGER', - class: `${CLASS}-header-label` + class: `panel-header-label` }); const sceneImport = new Label({ text: '\uE245', - class: `${CLASS}-header-button` + class: `panel-header-button` }); const sceneNew = new Label({ text: '\uE208', - class: `${CLASS}-header-button` + class: `panel-header-button` }); sceneHeader.append(sceneIcon); @@ -59,7 +57,7 @@ class ScenePanel extends Container { }); sceneNew.on('click', () => { - events.fire('scene.new'); + events.invoke('scene.new'); }); tooltips.register(sceneImport, 'Import Scene', 'top'); @@ -68,17 +66,17 @@ class ScenePanel extends Container { const splatList = new SplatList(events); const transformHeader = new Container({ - class: `${CLASS}-header` + class: `panel-header` }); const transformIcon = new Label({ text: '\uE111', - class: `${CLASS}-header-icon` + class: `panel-header-icon` }); const transformLabel = new Label({ text: 'TRANSFORM', - class: `${CLASS}-header-label` + class: `panel-header-label` }); transformHeader.append(transformIcon); diff --git a/src/ui/style.scss b/src/ui/style.scss index 22e88e7c..51bfc7d0 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -7,8 +7,10 @@ $bgclr-light: #333; $bgclr-active: #f60; @import 'tooltips.scss'; +@import 'panel.scss'; @import 'menu.scss'; @import 'scene-panel.scss'; +@import 'view-panel.scss'; @import 'splat-list.scss'; @import 'transform.scss'; @import 'bottom-toolbar.scss'; @@ -156,34 +158,15 @@ body { margin-right: 10px; color: $text-secondary; - #transform-panel & { content: '\E111'; }; #camera-panel & { content: '\E212'; }; #show-panel & { content: '\E117'; }; #selection-panel & { content: '\E398'; }; #modify-panel & { content: '\E130'; }; - #scene-panel & { content: '\E142'; }; #options-panel & { content: '\E134'; }; #export-panel & { content: '\E226'; }; #keyboard-panel & { content: '\E136'}; } -#control-panel-scene-panel { - flex-shrink: 0; -} - -#control-panel-scene-panel-splat-list-container { - // padding: 10px; - overflow: scroll; - - width: 100%; - height: 100px; - display: flex; - flex-direction: column; - flex-grow: 1; - - background-color: $bcg-primary; -} - #file-selector { display: none; } diff --git a/src/ui/toolbar.ts b/src/ui/toolbar.ts index 181cb5ce..94191de4 100644 --- a/src/ui/toolbar.ts +++ b/src/ui/toolbar.ts @@ -36,7 +36,7 @@ class Toolbar extends Container { class: 'file-menu-item', text: 'New', icon: 'E208', - onSelect: () => events.fire('scene.new') + onSelect: () => events.invoke('scene.new') }, { class: 'file-menu-item', text: 'Open...', @@ -47,18 +47,18 @@ class Toolbar extends Container { text: 'Save', icon: 'E216', onSelect: () => events.fire('scene.save'), - onIsEnabled: () => events.invoke('scene.canSave') + onIsEnabled: () => events.invoke('scene.empty') }, { class: 'file-menu-item', text: 'Save As...', icon: 'E216', onSelect: () => events.fire('scene.saveAs'), - onIsEnabled: () => events.invoke('scene.canSave') + onIsEnabled: () => events.invoke('scene.empty') }, { class: 'file-menu-item', text: 'Export', icon: 'E225', - onIsEnabled: () => events.invoke('scene.canSave'), + onIsEnabled: () => events.invoke('scene.empty'), items: [{ class: 'file-menu-item', text: 'Compressed Ply', diff --git a/src/ui/view-panel.scss b/src/ui/view-panel.scss new file mode 100644 index 00000000..0591e096 --- /dev/null +++ b/src/ui/view-panel.scss @@ -0,0 +1,51 @@ +#view-panel { + top: 50%; + transform: translate(0, -50%); + right: 102px; + width: 320px; + + :not(.pcui-hidden) { + display: flex; + } + flex-direction: column; + + & > .view-panel-row { + display: flex; + flex-direction: row; + padding: 2px; + height: 28px; + + & > .view-panel-row-label { + flex-grow: 1; + } + + & > .view-panel-row-toggle { + background-color: $bcg-dark; + + &::after { + background-color: $clr-default; + } + + &.pcui-boolean-input-ticked { + background-color: $bgclr-active; + + &::after { + background-color: $clr-active; + } + } + } + + & > .view-panel-row-slider { + margin: 0px; + + & > .pcui-slider-container { + & > .pcui-slider-bar { + & > .pcui-slider-handle { + background-color: $clr-default; + border-radius: 3px; + } + } + } + } + } +} diff --git a/src/ui/view-panel.ts b/src/ui/view-panel.ts new file mode 100644 index 00000000..6b052029 --- /dev/null +++ b/src/ui/view-panel.ts @@ -0,0 +1,194 @@ +import { BooleanInput, Container, Label, SliderInput } from 'pcui'; +import { Events } from '../events'; +import { Tooltips } from './tooltips'; + +class ViewPanel extends Container { + constructor(events: Events, tooltips: Tooltips, args = {}) { + args = { + ...args, + id: 'view-panel', + class: 'panel', + hidden: true + }; + + super(args); + + const handleDown = (event: PointerEvent) => { + event.preventDefault(); + event.stopPropagation(); + }; + + this.dom.addEventListener('pointerdown', (event) => { + handleDown(event); + }); + + // header + + const header = new Container({ + class: `panel-header` + }); + + const icon = new Label({ + text: '\uE403', + class: `panel-header-icon` + }); + + const label = new Label({ + text: 'VIEW OPTIONS', + class: `panel-header-label` + }); + + header.append(icon); + header.append(label); + + // rings mode + + const ringsModeRow = new Container({ + class: 'view-panel-row' + }); + + const ringsModeLabel = new Label({ + text: 'Rings Mode', + class: 'view-panel-row-label' + }); + + const ringsModeToggle = new BooleanInput({ + type: 'toggle', + class: 'view-panel-row-toggle' + }); + + ringsModeRow.append(ringsModeLabel); + ringsModeRow.append(ringsModeToggle); + + // show splats + + const showSplatsRow = new Container({ + class: 'view-panel-row' + }); + + const showSplatsLabel = new Label({ + text: 'Show Splats', + class: 'view-panel-row-label' + }); + + const showSplatsToggle = new BooleanInput({ + type: 'toggle', + class: 'view-panel-row-toggle', + value: true + }); + + showSplatsRow.append(showSplatsLabel); + showSplatsRow.append(showSplatsToggle); + + // splat size + + const splatSizeRow = new Container({ + class: 'view-panel-row' + }); + + const splatSizeLabel = new Label({ + text: 'Splat Size', + class: 'view-panel-row-label' + }); + + const splatSizeSlider = new SliderInput({ + class: 'view-panel-row-slider', + min: 0, + max: 10, + precision: 1, + value: 2 + }); + + splatSizeRow.append(splatSizeLabel); + splatSizeRow.append(splatSizeSlider); + + // show grid + + const showGridRow = new Container({ + class: 'view-panel-row' + }); + + const showGridLabel = new Label({ + text: 'Show Grid', + class: 'view-panel-row-label' + }); + + const showGridToggle = new BooleanInput({ + type: 'toggle', + class: 'view-panel-row-toggle', + value: true + }); + + showGridRow.append(showGridLabel); + showGridRow.append(showGridToggle); + + this.append(header); + this.append(ringsModeRow); + this.append(showSplatsRow); + this.append(splatSizeRow); + this.append(showGridRow); + + // handle panel visibility + + const setVisible = (visible: boolean) => { + if (visible === this.hidden) { + this.hidden = !visible; + events.fire('viewPanel.visible', visible); + } + }; + + events.function('viewPanel.visible', () => { + return !this.hidden; + }); + + events.on('viewPanel.setVisible', (visible: boolean) => { + setVisible(visible); + }); + + events.on('viewPanel.toggleVisible', () => { + setVisible(this.hidden); + }); + + // rings mode + + events.on('camera.mode', (mode: string) => { + ringsModeToggle.value = mode === 'rings'; + }); + + ringsModeToggle.on('change', () => { + events.fire('camera.setMode', ringsModeToggle.value ? 'rings' : 'centers'); + }); + + // show splats + + events.on('camera.debug', (debug: boolean) => { + showSplatsToggle.value = debug; + }); + + showSplatsToggle.on('change', () => { + events.fire('camera.setDebug', showSplatsToggle.value); + }); + + // splat size + + events.on('splatSize', (value: number) => { + splatSizeSlider.value = value; + }); + + splatSizeSlider.on('change', (value: number) => { + events.fire('splatSize', value); + }); + + // show grid + + events.on('grid.visible', (visible: boolean) => { + showGridToggle.value = visible; + }); + + showGridToggle.on('change', () => { + events.fire('grid.setVisible', showGridToggle.value); + }); + } +} + +export { ViewPanel }; From 1f66fc4c0e868c427c3bdd720ba451b101d95dd7 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Tue, 30 Jul 2024 13:43:27 +0100 Subject: [PATCH 08/33] remove picker selection tool and already-migrated bits from control-panel --- src/editor.ts | 4 +- src/main.ts | 14 +- src/splat.ts | 2 +- src/tools/picker-selection.ts | 34 ----- src/ui/bottom-toolbar.ts | 7 +- src/ui/control-panel.ts | 232 ++-------------------------------- src/ui/menu.ts | 6 +- src/ui/style.scss | 6 - src/ui/toolbar.ts | 68 ---------- src/ui/view-panel.ts | 4 +- 10 files changed, 29 insertions(+), 348 deletions(-) delete mode 100644 src/tools/picker-selection.ts diff --git a/src/editor.ts b/src/editor.ts index 58f60c29..e26b3344 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -120,7 +120,7 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S scene.forceRender = true; }); - events.on('splatSize', () => { + events.on('camera.splatSize', () => { scene.forceRender = true; }); @@ -416,7 +416,7 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S const y = splatData.getProp('y'); const z = splatData.getProp('z'); - const splatSize = events.invoke('splatSize'); + const splatSize = events.invoke('camera.splatSize'); const camera = scene.camera.entity.camera; const sx = point.x * width; const sy = point.y * height; diff --git a/src/main.ts b/src/main.ts index 538c5e88..c14c9f7c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,7 +15,6 @@ import { RectSelection } from './tools/rect-selection'; import { BrushSelection } from './tools/brush-selection'; import { Shortcuts } from './shortcuts'; import { Events } from './events'; -import { PickerSelection } from './tools/picker-selection'; declare global { interface LaunchParams { @@ -70,7 +69,7 @@ const initShortcuts = (events: Events) => { shortcuts.register(['F', 'f'], { event: 'camera.focus' }); shortcuts.register(['B', 'b'], { event: 'tool.brushSelection', sticky: true }); shortcuts.register(['R', 'r'], { event: 'tool.rectSelection', sticky: true }); - shortcuts.register(['P', 'p'], { event: 'tool.pickerSelection', sticky: true }); + shortcuts.register(['P', 'p'], { event: 'tool.rectSelection', sticky: true }); shortcuts.register(['A', 'a'], { event: 'select.all' }); shortcuts.register(['A', 'a'], { event: 'select.none', shift: true }); shortcuts.register(['I', 'i'], { event: 'select.invert' }); @@ -83,18 +82,10 @@ const initShortcuts = (events: Events) => { shortcuts.register(['M', 'm'], { event: 'camera.toggleMode' }); shortcuts.register(['D', 'd'], { event: 'dataPanel.toggle' }); - // keep tabs on splat size changes - let splatSizeSave = 2; - events.on('splatSize', (size: number) => { - if (size !== 0) { - splatSizeSave = size; - } - }); - // space toggles between 0 and size shortcuts.register([' '], { func: () => { - events.fire('splatSize', events.invoke('splatSize') === 0 ? splatSizeSave : 0); + events.fire('camera.toggleDebug'); } }); @@ -156,7 +147,6 @@ const main = async () => { toolManager.register('scale', new ScaleTool(events, editHistory, scene)); toolManager.register('rectSelection', new RectSelection(events, editorUI.toolsContainer.dom)); toolManager.register('brushSelection', new BrushSelection(events, editorUI.toolsContainer.dom)); - toolManager.register('pickerSelection', new PickerSelection(events, editorUI.toolsContainer.dom)); window.scene = scene; diff --git a/src/splat.ts b/src/splat.ts index dbd3c8da..dc8b5f7c 100644 --- a/src/splat.ts +++ b/src/splat.ts @@ -325,7 +325,7 @@ class Splat extends Element { const events = this.scene.events; const selected = events.invoke('selection') === this; const cameraMode = events.invoke('camera.mode'); - const splatSize = events.invoke('camera.debug') ? events.invoke('splatSize') : 0; + const splatSize = events.invoke('camera.debug') ? events.invoke('camera.splatSize') : 0; // configure rings rendering const material = this.entity.gsplat.instance.material; diff --git a/src/tools/picker-selection.ts b/src/tools/picker-selection.ts deleted file mode 100644 index 6de0a2cc..00000000 --- a/src/tools/picker-selection.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Events } from "../events"; - -class PickerSelection { - activate: () => void; - deactivate: () => void; - - constructor(events: Events, parent: HTMLElement) { - const pointerdown = (e: PointerEvent) => { - if (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary) { - e.preventDefault(); - e.stopPropagation(); - - events.fire( - 'select.point', - e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'), - { x: e.offsetX / parent.clientWidth, y: e.offsetY / parent.clientHeight } - ); - } - }; - - this.activate = () => { - parent.style.display = 'block'; - parent.addEventListener('pointerdown', pointerdown); - } - - this.deactivate = () => { - parent.style.display = 'none'; - parent.removeEventListener('pointerdown', pointerdown); - } - } -} - -export { PickerSelection }; - diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts index 07fc6ff5..433ecec2 100644 --- a/src/ui/bottom-toolbar.ts +++ b/src/ui/bottom-toolbar.ts @@ -34,12 +34,14 @@ class BottomToolbar extends Container { const undo = new Button({ id: 'bottom-toolbar-undo', - class: 'bottom-toolbar-button' + class: 'bottom-toolbar-button', + enabled: false }); const redo = new Button({ id: 'bottom-toolbar-redo', class: 'bottom-toolbar-button', + enabled: false }); const picker = new Button({ @@ -90,6 +92,9 @@ class BottomToolbar extends Container { events.fire('tool.rectSelection'); }); + events.on('edit.canUndo', (value: boolean) => { undo.enabled = value; }); + events.on('edit.canRedo', (value: boolean) => { redo.enabled = value; }); + brush.dom.addEventListener('click', () => { events.fire('tool.brushSelection'); }); diff --git a/src/ui/control-panel.ts b/src/ui/control-panel.ts index 2d549d24..57e930cc 100644 --- a/src/ui/control-panel.ts +++ b/src/ui/control-panel.ts @@ -17,84 +17,6 @@ class ControlPanel extends Panel { super(args); - // camera panel - const cameraPanel = new Panel({ - id: 'camera-panel', - class: 'control-panel', - headerText: 'CAMERA' - }); - - // mode - const mode = new Container({ - class: 'control-parent' - }); - - const modeLabel = new Label({ - class: 'control-label', - text: 'Mode' - }); - - const modeSelect = new SelectInput({ - class: 'control-element-expand', - defaultValue: 'centers', - options: [ - { v: 'centers', t: 'Centers' }, - { v: 'rings', t: 'Rings' } - ] - }); - - mode.append(modeLabel); - mode.append(modeSelect); - - // splat size - const splatSize = new Container({ - class: 'control-parent' - }); - - const splatSizeLabel = new Label({ - class: 'control-label', - text: 'Splat Size' - }); - - const splatSizeSlider = new SliderInput({ - class: 'control-element-expand', - precision: 1, - min: 0, - max: 10, - value: 2 - }); - - splatSize.append(splatSizeLabel); - splatSize.append(splatSizeSlider); - - // show grid - const showGrid = new Container({ - class: 'control-parent' - }); - - const showGridLabel = new Label({ - class: 'control-label', - text: 'Show Grid' - }); - - const showGridToggle = new BooleanInput({ - class: 'control-element', - value: true - }); - - showGrid.append(showGridLabel); - showGrid.append(showGridToggle); - - const focusButton = new Button({ - class: 'control-element', - text: 'Frame Selection' - }); - - cameraPanel.append(mode); - cameraPanel.append(splatSize); - cameraPanel.append(showGrid); - cameraPanel.append(focusButton); - // selection panel const selectionPanel = new Panel({ id: 'selection-panel', @@ -102,33 +24,6 @@ class ControlPanel extends Panel { headerText: 'SELECTION' }); - // selection button parent - const selectGlobal = new Container({ - class: 'control-parent' - }); - - // all - const selectAllButton = new Button({ - class: 'control-element-expand', - text: 'All' - }); - - // none - const selectNoneButton = new Button({ - class: 'control-element-expand', - text: 'None' - }); - - // invert - const invertSelectionButton = new Button({ - class: 'control-element-expand', - text: 'Invert' - }); - - selectGlobal.append(selectAllButton); - selectGlobal.append(selectNoneButton); - selectGlobal.append(invertSelectionButton); - // select by sphere const selectBySphere = new Container({ class: 'control-parent' @@ -220,38 +115,9 @@ class ControlPanel extends Panel { setAddRemove.append(addButton); setAddRemove.append(removeButton); - // selection parent - const selectTools = new Container({ - class: 'control-parent' - }); - - const rectSelectButton = new Button({ - class: 'control-element-expand', - text: 'Rect', - enabled: true - }); - - const brushSelectButton = new Button({ - class: 'control-element-expand', - text: 'Brush', - enabled: true - }); - - const pickerSelectButton = new Button({ - class: 'control-element-expand', - text: 'Picker', - enabled: true - }); - - selectTools.append(rectSelectButton); - selectTools.append(brushSelectButton); - selectTools.append(pickerSelectButton); - - selectionPanel.append(selectGlobal); selectionPanel.append(selectBySphere); selectionPanel.append(selectByPlane); selectionPanel.append(setAddRemove); - selectionPanel.append(selectTools); // show panel const showPanel = new Panel({ @@ -297,36 +163,8 @@ class ControlPanel extends Panel { text: 'Reset Splats' }); - const undoRedo = new Container({ - class: 'control-parent' - }); - - const undoButton = new Button({ - class: 'control-element-expand', - text: 'Undo', - icon: 'E339', - enabled: false - }); - - const redoButton = new Button({ - class: 'control-element-expand', - text: 'Redo', - icon: 'E338', - enabled: false - }); - - undoRedo.append(undoButton); - undoRedo.append(redoButton); - modifyPanel.append(deleteSelectionButton); modifyPanel.append(resetButton); - modifyPanel.append(undoRedo); - - undoButton.on('click', () => { events.fire('edit.undo'); }); - redoButton.on('click', () => { events.fire('edit.redo'); }); - - events.on('edit.canUndo', (value: boolean) => { undoButton.enabled = value; }); - events.on('edit.canRedo', (value: boolean) => { redoButton.enabled = value; }); // options const optionsPanel = new Panel({ @@ -358,7 +196,6 @@ class ControlPanel extends Panel { id: 'control-panel-controls' }); - controlsContainer.append(cameraPanel) controlsContainer.append(selectionPanel); controlsContainer.append(showPanel); controlsContainer.append(modifyPanel); @@ -367,24 +204,6 @@ class ControlPanel extends Panel { // append this.content.append(controlsContainer); - rectSelectButton.on('click', () => { - events.fire('tool.rectSelection'); - }); - - brushSelectButton.on('click', () => { - events.fire('tool.brushSelection'); - }); - - pickerSelectButton.on('click', () => { - events.fire('tool.pickerSelection'); - }); - - events.on('tool.activated', (toolName: string) => { - rectSelectButton.class[toolName === 'rectSelection' ? 'add' : 'remove']('active'); - brushSelectButton.class[toolName === 'brushSelection' ? 'add' : 'remove']('active'); - pickerSelectButton.class[toolName === 'pickerSelection' ? 'add' : 'remove']('active'); - }); - // radio logic const radioGroup = [selectBySphereRadio, selectByPlaneRadio]; radioGroup.forEach((radio, index) => { @@ -468,10 +287,6 @@ class ControlPanel extends Panel { setCameraMode(events.invoke('camera.mode') === 'centers' ? 'rings' : 'centers'); }); - events.on('camera.mode', (mode: string) => { - modeSelect.value = mode; - }); - // camera debug let cameraDebug = true; @@ -495,44 +310,23 @@ class ControlPanel extends Panel { setCameraDebug(!events.invoke('camera.debug')); }); - modeSelect.on('change', (value: string) => { - setCameraMode(value); - }); - - events.on('splatSize', (value: number) => { - splatSizeSlider.value = value; - }); - - events.function('splatSize', () => { - return splatSizeSlider.value; - }); - - splatSizeSlider.on('change', (value: number) => { - events.fire('splatSize', value); - }); - - focusButton.on('click', () => { - events.fire('camera.focus'); - }); - - showGridToggle.on('change', (enabled: boolean) => { - events.fire('grid.setVisible', enabled); - }); + // splat size - events.on('grid.visible', (visible: boolean) => { - showGridToggle.value = visible; - }); + let splatSize = 2; - selectAllButton.on('click', () => { - events.fire('select.all'); - }); + const setSplatSize = (value: number) => { + if (value !== splatSize) { + splatSize = value; + events.fire('camera.splatSize', splatSize); + } + }; - selectNoneButton.on('click', () => { - events.fire('select.none'); + events.function('camera.splatSize', () => { + return splatSize; }); - - invertSelectionButton.on('click', () => { - events.fire('select.invert'); + + events.on('camera.setSplatSize', (value: number) => { + setSplatSize(value); }); selectBySphereCenter.on('change', () => { diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 5467042b..f4e7afb3 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -109,17 +109,17 @@ class Menu extends Container { class: 'menu-dropdown-item', text: 'All', icon: 'E0020', - onSelect: () => events.fire('selection.all') + onSelect: () => events.fire('select.all') }, { class: 'menu-dropdown-item', text: 'None', icon: 'E0020', - onSelect: () => events.fire('selection.none') + onSelect: () => events.fire('select.none') }, { class: 'menu-dropdown-item', text: 'Invert', icon: 'E0020', - onSelect: () => events.fire('selection.invert') + onSelect: () => events.fire('select.invert') }] }); diff --git a/src/ui/style.scss b/src/ui/style.scss index 51bfc7d0..35962bf8 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -158,7 +158,6 @@ body { margin-right: 10px; color: $text-secondary; - #camera-panel & { content: '\E212'; }; #show-panel & { content: '\E117'; }; #selection-panel & { content: '\E398'; }; #modify-panel & { content: '\E130'; }; @@ -244,11 +243,6 @@ body { flex-direction: column; } -#camera-panel > .pcui-panel-content { - display: flex; - flex-direction: column; -} - #modify-panel > .pcui-panel-content { display: flex; flex-direction: column; diff --git a/src/ui/toolbar.ts b/src/ui/toolbar.ts index 94191de4..68024984 100644 --- a/src/ui/toolbar.ts +++ b/src/ui/toolbar.ts @@ -87,36 +87,6 @@ class Toolbar extends Container { } }); - // move - const moveTool = new Button({ - id: 'move-tool', - class: 'toolbar-button', - icon: 'E111' - }); - moveTool.on('click', () => { - events.fire('tool.move'); - }); - - // rotate - const rotateTool = new Button({ - id: 'rotate-tool', - class: 'toolbar-button', - icon: 'E113' - }); - rotateTool.on('click', () => { - events.fire('tool.rotate'); - }); - - // scale - const scaleTool = new Button({ - id: 'scale-tool', - class: 'toolbar-button', - icon: 'E112' - }); - scaleTool.on('click', () => { - events.fire('tool.scale'); - }); - // world/local space toggle const coordSpaceToggle = new Button({ id: 'coord-space-toggle', @@ -132,45 +102,10 @@ class Toolbar extends Container { coordSpaceToggle.dom.classList[space === 'world' ? 'add' : 'remove']('active'); }); - /* disable rect and brush selection on the toolbar till we have a - // rect selection - const rectTool = new Button({ - id: 'rect-tool', - class: 'toolbar-button', - icon: 'E135' - }); - rectTool.on('click', () => { - events.fire('tool:activate', 'RectSelection'); - }); - - // brush selection - const brushTool = new Button({ - id: 'brush-tool', - class: 'toolbar-button', - icon: 'E195' - }); - brushTool.on('click', () => { - events.fire('tool:activate', 'BrushSelection'); - }); - */ - - events.on('tool.activated', (toolName: string) => { - moveTool.class[toolName === 'move' ? 'add' : 'remove']('active'); - rotateTool.class[toolName === 'rotate' ? 'add' : 'remove']('active'); - scaleTool.class[toolName === 'scale' ? 'add' : 'remove']('active'); - // rectTool.class[toolName === 'RectSelection' ? 'add' : 'remove']('active'); - // brushTool.class[toolName === 'BrushSelection' ? 'add' : 'remove']('active'); - }); - toolbarToolsContainer.dom.appendChild(appLogo); toolbarToolsContainer.append(fileButton); toolbarToolsContainer.append(fileMenu); - toolbarToolsContainer.append(moveTool); - toolbarToolsContainer.append(rotateTool); - toolbarToolsContainer.append(scaleTool); toolbarToolsContainer.append(coordSpaceToggle); - // toolbarToolsContainer.append(rectTool); - // toolbarToolsContainer.append(brushTool); // keyboard shortcuts const shortcuts = new Button({ @@ -207,9 +142,6 @@ class Toolbar extends Container { }; // add tooltips - addTooltip(moveTool, 'Move'); - addTooltip(rotateTool, 'Rotate'); - addTooltip(scaleTool, 'Scale'); const coordSpaceTooltip = addTooltip(coordSpaceToggle, 'Local Space'); addTooltip(shortcuts, 'Keyboard Shortcuts'); addTooltip(github, 'GitHub Repo'); diff --git a/src/ui/view-panel.ts b/src/ui/view-panel.ts index 6b052029..bc753b4e 100644 --- a/src/ui/view-panel.ts +++ b/src/ui/view-panel.ts @@ -171,12 +171,12 @@ class ViewPanel extends Container { // splat size - events.on('splatSize', (value: number) => { + events.on('camera.splatSize', (value: number) => { splatSizeSlider.value = value; }); splatSizeSlider.on('change', (value: number) => { - events.fire('splatSize', value); + events.fire('camera.setSplatSize', value); }); // show grid From 7a26ad3a1544a35b9af89728f5f9df401203bd39 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Tue, 30 Jul 2024 15:00:43 +0100 Subject: [PATCH 09/33] completely migrate and remove old toolbar --- src/tools/tool-manager.ts | 28 ++++--- src/ui/bottom-toolbar.scss | 51 +++++++++--- src/ui/bottom-toolbar.ts | 65 +++++++++++---- src/ui/editor.ts | 5 -- src/ui/panel.scss | 5 +- src/ui/right-toolbar.scss | 102 ++++++++++++++++------- src/ui/right-toolbar.ts | 52 +++--------- src/ui/splat-list.scss | 2 +- src/ui/style.scss | 46 +---------- src/ui/toolbar.ts | 160 ------------------------------------- src/ui/view-panel.scss | 2 +- 11 files changed, 190 insertions(+), 328 deletions(-) delete mode 100644 src/ui/toolbar.ts diff --git a/src/tools/tool-manager.ts b/src/tools/tool-manager.ts index 42102cac..070fbe4f 100644 --- a/src/tools/tool-manager.ts +++ b/src/tools/tool-manager.ts @@ -9,7 +9,6 @@ class ToolManager { tools = new Map(); events: Events; active: string | null = null; - coordSpace: 'local' | 'world' = 'world'; constructor(events: Events) { this.events = events; @@ -22,22 +21,25 @@ class ToolManager { return this.active; }); - this.events.on('tool.localCoordSpace', () => { - this.coordSpace = 'local'; - events.fire('tool.coordSpace', 'local'); - }); + let coordSpace: 'local' | 'world' = 'world'; - this.events.on('tool.worldCoordSpace', () => { - this.coordSpace = 'world'; - events.fire('tool.coordSpace', 'world'); - }); + const setCoordSpace = (space: 'local' | 'world') => { + if (space !== coordSpace) { + coordSpace = space; + events.fire('tool.coordSpace', coordSpace); + } + }; + + events.function('tool.coordSpace', () => { + return coordSpace; + }) - this.events.on('tool.toggleCoordSpace', () => { - this.events.fire(`tool.${this.coordSpace === 'local' ? 'world' : 'local'}CoordSpace`); + events.on('tool.setCoordSpace', (value: 'local' | 'world') => { + setCoordSpace(value); }); - this.events.function('tool.coordSpace', () => { - return this.coordSpace; + events.on('tool.toggleCoordSpace', () => { + setCoordSpace(coordSpace === 'local' ? 'world' : 'local'); }); } diff --git a/src/ui/bottom-toolbar.scss b/src/ui/bottom-toolbar.scss index 008d9035..3709940b 100644 --- a/src/ui/bottom-toolbar.scss +++ b/src/ui/bottom-toolbar.scss @@ -16,20 +16,31 @@ pointer-events: all; } -.bottom-toolbar-button, .bottom-toolbar-tool { +.bottom-toolbar-button, .bottom-toolbar-tool, .bottom-toolbar-toggle { width: 38px; height: 38px; margin: 0px 1px; padding: 0px; border: 0px; border-radius: 2px; + + &::before { + font-size: 16px !important; + line-height: 100%; + } + + svg { + path { + fill: $clr-default; + } + } } .bottom-toolbar-separator { width: 1px; height: 38px; - background-color: $bgclr-light; margin: 0px 8px; + background-color: $bgclr-light; } .bottom-toolbar-button { @@ -41,28 +52,44 @@ } .bottom-toolbar-tool { + background-color: $bgclr-light; + svg { path { fill: $clr-default; } } - background-color: $bgclr-light; -} -.bottom-toolbar-tool.active { - svg { - path { - fill: $clr-active; - stroke: $clr-active; + &.active { + background-color: $clr-hilight !important; + + svg { + path { + fill: $clr-active; + stroke: $clr-active; + } } } + + &.disabled { + svg { + path { + fill: $clr-disabled; + } + } + background-color: $bcg-dark; + } } -.bottom-toolbar-tool.disabled { +.bottom-toolbar-toggle.active { + &::before { + color: $clr-hilight; + } + svg { path { - fill: $clr-disabled; + fill: $clr-hilight; + stroke: $clr-hilight; } } - background-color: $bcg-dark; } diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts index 433ecec2..dba62a20 100644 --- a/src/ui/bottom-toolbar.ts +++ b/src/ui/bottom-toolbar.ts @@ -64,6 +64,30 @@ class BottomToolbar extends Container { class: ['bottom-toolbar-tool', 'disabled'] }); + const translate = new Button({ + id: 'bottom-toolbar-translate', + class: 'bottom-toolbar-tool', + icon: 'E111' + }); + + const rotate = new Button({ + id: 'bottom-toolbar-rotate', + class: 'bottom-toolbar-tool', + icon: 'E113' + }); + + const scale = new Button({ + id: 'bottom-toolbar-scale', + class: 'bottom-toolbar-tool', + icon: 'E112' + }); + + const coordSpace = new Button({ + id: 'bottom-toolbar-coord-space', + class: 'bottom-toolbar-toggle', + icon: 'E118' + }); + undo.dom.appendChild(createSvg(undoSvg)); redo.dom.appendChild(createSvg(redoSvg)); picker.dom.appendChild(createSvg(pickerSvg)); @@ -79,29 +103,33 @@ class BottomToolbar extends Container { this.append(lasso); this.append(crop); this.append(new Element({ class: 'bottom-toolbar-separator' })); - - undo.dom.addEventListener('click', () => { - events.fire('edit.undo'); - }); - - redo.dom.addEventListener('click', () => { - events.fire('edit.redo'); - }); - - picker.dom.addEventListener('click', () => { - events.fire('tool.rectSelection'); - }); + this.append(translate); + this.append(rotate); + this.append(scale); + this.append(coordSpace); + + undo.dom.addEventListener('click', () => events.fire('edit.undo')); + redo.dom.addEventListener('click', () => events.fire('edit.redo')); + brush.dom.addEventListener('click', () => events.fire('tool.brushSelection')); + picker.dom.addEventListener('click', () => events.fire('tool.rectSelection')); + translate.dom.addEventListener('click', () => events.fire('tool.move')); + rotate.dom.addEventListener('click', () => events.fire('tool.rotate')); + scale.dom.addEventListener('click', () => events.fire('tool.scale')); + coordSpace.dom.addEventListener('click', () => events.fire('tool.toggleCoordSpace')); events.on('edit.canUndo', (value: boolean) => { undo.enabled = value; }); events.on('edit.canRedo', (value: boolean) => { redo.enabled = value; }); - brush.dom.addEventListener('click', () => { - events.fire('tool.brushSelection'); - }); - events.on('tool.activated', (toolName: string) => { picker.class[toolName === 'rectSelection' ? 'add' : 'remove']('active'); brush.class[toolName === 'brushSelection' ? 'add' : 'remove']('active'); + translate.class[toolName === 'move' ? 'add' : 'remove']('active'); + rotate.class[toolName === 'rotate' ? 'add' : 'remove']('active'); + scale.class[toolName === 'scale' ? 'add' : 'remove']('active'); + }); + + events.on('tool.coordSpace', (space: 'local' | 'world') => { + coordSpace.dom.classList[space === 'local' ? 'add' : 'remove']('active'); }); // register tooltips @@ -111,6 +139,11 @@ class BottomToolbar extends Container { tooltips.register(brush, 'Select Brush'); tooltips.register(lasso, 'Select Lasso'); tooltips.register(crop, 'Crop'); + tooltips.register(translate, 'Translate'); + tooltips.register(rotate, 'Rotate'); + tooltips.register(scale, 'Scale'); + tooltips.register(coordSpace, 'Enable Local Space'); + } } diff --git a/src/ui/editor.ts b/src/ui/editor.ts index 53447250..077ce5e7 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -2,7 +2,6 @@ import { Container, Label } from 'pcui'; import { Mat4 } from 'playcanvas'; import { ControlPanel } from './control-panel'; import { DataPanel } from './data-panel'; -import { Toolbar } from './toolbar'; import { Events } from '../events'; import { Popup } from './popup'; import { ViewCube } from './view-cube'; @@ -62,9 +61,6 @@ class EditorUI { topContainer.dom.addEventListener('mousemove', (event: MouseEvent) => killit(event)); topContainer.on('click', (event: MouseEvent) => killit(event)); - // toolbar - const toolbar = new Toolbar(events, appContainer, tooltipsContainer); - // canvas const canvas = document.createElement('canvas'); canvas.id = 'canvas'; @@ -124,7 +120,6 @@ class EditorUI { mainContainer.append(canvasContainer); mainContainer.append(dataPanel); - editorContainer.append(toolbar); editorContainer.append(controlPanel); editorContainer.append(mainContainer); diff --git a/src/ui/panel.scss b/src/ui/panel.scss index 74f2dc30..28db4309 100644 --- a/src/ui/panel.scss +++ b/src/ui/panel.scss @@ -10,6 +10,7 @@ & > .panel-header { display: flex; flex-direction: row; + align-items: center; padding: 2px; background-color: $bcg-dark; @@ -18,7 +19,7 @@ font-family: pc-icon; font-weight: bold; font-size: 13px; - color: #ff6600; + color: $clr-hilight; } & > .panel-header-label { @@ -31,7 +32,7 @@ font-family: pc-icon; font-weight: bold; font-size: 13px; - color: #ff6600; + color: $clr-hilight; &:hover { color: #ff9900; diff --git a/src/ui/right-toolbar.scss b/src/ui/right-toolbar.scss index 3ea3e8d5..0206e128 100644 --- a/src/ui/right-toolbar.scss +++ b/src/ui/right-toolbar.scss @@ -1,4 +1,3 @@ - #right-toolbar { position: absolute; right: 70px; @@ -15,43 +14,84 @@ align-items: center; pointer-events: all; -} - -.right-toolbar-button { - width: 38px; - height: 38px; - margin: 0px 1px; - padding: 0px; - border: 0px; - border-radius: 2px; - - &::before { - font-size: 18px !important; - line-height: 100%; + + &>.right-toolbar-button, .right-toolbar-tool, .right-toolbar-toggle { + width: 38px; + height: 38px; + margin: 0px 1px; + padding: 0px; + border: 0px; + border-radius: 2px; + + &::before { + font-size: 16px !important; + line-height: 100%; + } + + svg { + path { + fill: $clr-default; + } + } + } + + &>.right-toolbar-separator { + width: 38px; + height: 1px; + margin: 8px 0px; + background-color: $bgclr-light; } - svg { - path { - fill: $clr-default; + &>.right-toolbar-button { + svg { + path { + fill: $clr-default; + } } } - background-color: $bgclr-light; -} -.right-toolbar-button.active { - svg { - path { - fill: $clr-active; - stroke: $clr-active; + &>.right-toolbar-tool { + background-color: $bgclr-light; + + svg { + path { + fill: $clr-default; + } + } + + &.active { + background-color: $clr-hilight !important; + + svg { + path { + fill: $clr-active; + stroke: $clr-active; + } + } + } + + &.disabled { + svg { + path { + fill: $clr-disabled; + } + } + background-color: $bcg-dark; } } -} -.right-toolbar-button.disabled { - svg { - path { - fill: $clr-disabled; + &>.right-toolbar-toggle.active { + // highlight icon + &::before { + color: $clr-hilight; + } + + // highlight svg + svg { + path { + fill: $clr-hilight; + stroke: $clr-hilight; + } } } - background-color: $bcg-dark; -} +} \ No newline at end of file diff --git a/src/ui/right-toolbar.ts b/src/ui/right-toolbar.ts index 42d04e21..c7d18310 100644 --- a/src/ui/right-toolbar.ts +++ b/src/ui/right-toolbar.ts @@ -1,4 +1,4 @@ -import { Button, Container } from 'pcui'; +import { Button, Container, Element } from 'pcui'; import { Events } from '../events'; import { Tooltips } from './tooltips'; @@ -28,9 +28,9 @@ class RightToolbar extends Container { handleDown(event); }); - const showHide = new Button({ + const showHideSplats = new Button({ id: 'right-toolbar-show-hide', - class: ['right-toolbar-button', 'active'] + class: ['right-toolbar-toggle', 'active'] }); const frameSelection = new Button({ @@ -40,70 +40,38 @@ class RightToolbar extends Container { const options = new Button({ id: 'right-toolbar-options', - class: 'right-toolbar-button', + class: 'right-toolbar-toggle', icon: 'E283' }); - const translate = new Button({ - id: 'right-toolbar-translate', - class: 'right-toolbar-button', - icon: 'E111' - }); - - const rotate = new Button({ - id: 'right-toolbar-rotate', - class: 'right-toolbar-button', - icon: 'E113' - }); - - const scale = new Button({ - id: 'right-toolbar-scale', - class: 'right-toolbar-button', - icon: 'E112' - }); - - showHide.dom.appendChild(createSvg(showHideSplatsSvg)); + showHideSplats.dom.appendChild(createSvg(showHideSplatsSvg)); frameSelection.dom.appendChild(createSvg(frameSelectionSvg)); - this.append(showHide); + this.append(showHideSplats); this.append(frameSelection); + this.append(new Element({ class: 'right-toolbar-separator' })); this.append(options); - this.append(translate); - this.append(rotate); - this.append(scale); - tooltips.register(showHide, 'Show/Hide Splats', 'left'); + tooltips.register(showHideSplats, 'Show/Hide Splats', 'left'); tooltips.register(frameSelection, 'Frame Selection', 'left'); tooltips.register(options, 'Options', 'left'); - tooltips.register(translate, 'Translate', 'left'); - tooltips.register(rotate, 'Rotate', 'left'); - tooltips.register(scale, 'Scale', 'left'); // add event handlers options.on('click', () => events.fire('viewPanel.toggleVisible')); frameSelection.on('click', () => events.fire('camera.focus')); - translate.on('click', () => events.fire('tool.move')); - rotate.on('click', () => events.fire('tool.rotate')); - scale.on('click', () => events.fire('tool.scale')); events.on('viewPanel.visible', (visible: boolean) => { options.class[visible ? 'add' : 'remove']('active'); }); - events.on('tool.activated', (toolName: string) => { - translate.class[toolName === 'move' ? 'add' : 'remove']('active'); - rotate.class[toolName === 'rotate' ? 'add' : 'remove']('active'); - scale.class[toolName === 'scale' ? 'add' : 'remove']('active'); - }); - // show-hide splats events.on('camera.debug', (debug: boolean) => { - showHide.class[debug ? 'add' : 'remove']('active'); + showHideSplats.dom.classList[debug ? 'add' : 'remove']('active'); }); - showHide.dom.addEventListener('click', () => { + showHideSplats.dom.addEventListener('click', () => { events.fire('camera.toggleDebug'); }); } diff --git a/src/ui/splat-list.scss b/src/ui/splat-list.scss index 53c3cda3..f10e2360 100644 --- a/src/ui/splat-list.scss +++ b/src/ui/splat-list.scss @@ -1,6 +1,6 @@ .splat-list { min-height: 80px; - padding: 1px 0px; + padding: 4px 0px; } .splat-item { diff --git a/src/ui/style.scss b/src/ui/style.scss index 35962bf8..7d52c548 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -3,8 +3,8 @@ $clr-default: #b3aaac; $clr-disabled: #7c7678; $clr-active: white; +$clr-hilight: #f60; $bgclr-light: #333; -$bgclr-active: #f60; @import 'tooltips.scss'; @import 'panel.scss'; @@ -48,16 +48,6 @@ body { flex-direction: row; } -#toolbar-container { - height: 100%; - flex-shrink: 0; - flex-grow: 0; - display: flex; - flex-direction: column; - justify-content: space-between; - border-right: 1px solid $bcg-darker; -} - #main-container { width: 100%; height: 100%; @@ -180,35 +170,6 @@ body { font-size: 14px; } -.toolbar-button { - width: 40px; - height: 40px; - margin: 0; - cursor: pointer; -} - -.toolbar-button::before { - font-size: 18px !important; - line-height: 100%; -} - -.tooltip { - position: absolute; - padding: 6px; - background-color: $bcg-darkest; -} - -.tooltip-content { - color: $text-primary; -} - -#toolbar-tools-container, #toolbar-help-container { - display: flex; - flex-direction: column; - flex-grow: 0; - flex-shrink: 0; -} - #shortcuts-panel { background-color: $bcg-dark; } @@ -280,11 +241,6 @@ body { margin-top: 10px; } -.active { - color: white; - background-color: #f60 !important; -} - .select-svg { display: none; position: absolute; diff --git a/src/ui/toolbar.ts b/src/ui/toolbar.ts deleted file mode 100644 index 68024984..00000000 --- a/src/ui/toolbar.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { Button, Container, Element, Menu } from 'pcui'; -import { Tooltip } from './tooltip'; -import { Events } from '../events'; -import logo from './playcanvas-logo.png'; - -class Toolbar extends Container { - constructor(events: Events, appContainer: Container, tooltipsContainer: Container, args = {}) { - args = { - ...args, - id: 'toolbar-container' - }; - - super(args); - - // toolbar-tools - const toolbarToolsContainer = new Container({ - id: 'toolbar-tools-container' - }); - - // logo - const appLogo = document.createElement('img'); - appLogo.classList.add('toolbar-button'); - appLogo.id = 'app-logo'; - appLogo.src = logo; - - // file - const fileButton = new Button({ - id: 'file-menu-button', - class: 'toolbar-button', - icon: 'E220' - }); - - const fileMenu = new Menu({ - id: 'file-menu', - items: [{ - class: 'file-menu-item', - text: 'New', - icon: 'E208', - onSelect: () => events.invoke('scene.new') - }, { - class: 'file-menu-item', - text: 'Open...', - icon: 'E226', - onSelect: () => events.fire('scene.open') - }, { - class: 'file-menu-item', - text: 'Save', - icon: 'E216', - onSelect: () => events.fire('scene.save'), - onIsEnabled: () => events.invoke('scene.empty') - }, { - class: 'file-menu-item', - text: 'Save As...', - icon: 'E216', - onSelect: () => events.fire('scene.saveAs'), - onIsEnabled: () => events.invoke('scene.empty') - }, { - class: 'file-menu-item', - text: 'Export', - icon: 'E225', - onIsEnabled: () => events.invoke('scene.empty'), - items: [{ - class: 'file-menu-item', - text: 'Compressed Ply', - icon: 'E245', - onSelect: () => events.invoke('scene.export', 'compressed-ply') - }, { - class: 'file-menu-item', - text: 'Splat file', - icon: 'E245', - onSelect: () => events.invoke('scene.export', 'splat') - }] - }] - }); - - fileButton.on('click', () => { - const r = fileButton.dom.getBoundingClientRect(); - fileMenu.position(r.right, r.top); - fileMenu.hidden = false; - }); - - window.addEventListener('click', (e: Event) => { - if (!fileMenu.hidden && - !fileMenu.dom.contains(e.target as Node) && - e.target !== fileButton.dom) { - fileMenu.hidden = true; - } - }); - - // world/local space toggle - const coordSpaceToggle = new Button({ - id: 'coord-space-toggle', - class: 'toolbar-button', - icon: 'E118' - }); - - coordSpaceToggle.on('click', () => { - events.fire('tool.toggleCoordSpace'); - }); - - events.on('tool.coordSpace', (space: 'local' | 'world') => { - coordSpaceToggle.dom.classList[space === 'world' ? 'add' : 'remove']('active'); - }); - - toolbarToolsContainer.dom.appendChild(appLogo); - toolbarToolsContainer.append(fileButton); - toolbarToolsContainer.append(fileMenu); - toolbarToolsContainer.append(coordSpaceToggle); - - // keyboard shortcuts - const shortcuts = new Button({ - class: 'toolbar-button', - icon: 'E136' - }); - shortcuts.on('click', () => { - events.fire('show.shortcuts'); - }); - - // github - const github = new Button({ - class: 'toolbar-button', - icon: 'E259' - }); - github.on('click', () => { - window.open('https://github.com/playcanvas/supersplat', '_blank').focus(); - }); - - // toolbar help toolbar - const toolbarHelpContainer = new Container({ - id: 'toolbar-help-container' - }); - toolbarHelpContainer.append(shortcuts); - toolbarHelpContainer.append(github); - - this.append(toolbarToolsContainer); - this.append(toolbarHelpContainer); - - const addTooltip = (target: Element, text: string) => { - const tooltip = new Tooltip({ target, text }); - tooltipsContainer.append(tooltip); - return tooltip; - }; - - // add tooltips - const coordSpaceTooltip = addTooltip(coordSpaceToggle, 'Local Space'); - addTooltip(shortcuts, 'Keyboard Shortcuts'); - addTooltip(github, 'GitHub Repo'); - - // update tooltip - events.on('tool.coordSpace', (space: 'local' | 'world') => { - const spaces = { - local: 'Local', - world: 'World' - }; - coordSpaceTooltip.content.text = `${spaces[space]} Space`; - }); - } -} - -export { Toolbar }; diff --git a/src/ui/view-panel.scss b/src/ui/view-panel.scss index 0591e096..57c4aaea 100644 --- a/src/ui/view-panel.scss +++ b/src/ui/view-panel.scss @@ -27,7 +27,7 @@ } &.pcui-boolean-input-ticked { - background-color: $bgclr-active; + background-color: $clr-hilight; &::after { background-color: $clr-active; From 4b9eda98f40afb5e012304fdaf15fa0018d7815c Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Tue, 30 Jul 2024 15:37:55 +0100 Subject: [PATCH 10/33] tweaks --- src/ui/bottom-toolbar.scss | 1 + src/ui/menu.ts | 3 ++- src/ui/right-toolbar.scss | 1 + src/ui/splat-list.ts | 4 ++-- src/ui/style.scss | 8 ++++++++ src/ui/tooltips.ts | 6 ++++-- 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/ui/bottom-toolbar.scss b/src/ui/bottom-toolbar.scss index 3709940b..a7880628 100644 --- a/src/ui/bottom-toolbar.scss +++ b/src/ui/bottom-toolbar.scss @@ -61,6 +61,7 @@ } &.active { + color: $clr-active; background-color: $clr-hilight !important; svg { diff --git a/src/ui/menu.ts b/src/ui/menu.ts index f4e7afb3..d2742669 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -8,7 +8,8 @@ class Menu extends Container { constructor(events: Events, args = {}) { args = { ...args, - id: 'menu' + id: 'menu', + class: 'unselectable' }; super(args); diff --git a/src/ui/right-toolbar.scss b/src/ui/right-toolbar.scss index 0206e128..c3b56f7b 100644 --- a/src/ui/right-toolbar.scss +++ b/src/ui/right-toolbar.scss @@ -60,6 +60,7 @@ } &.active { + color: $clr-active; background-color: $clr-hilight !important; svg { diff --git a/src/ui/splat-list.ts b/src/ui/splat-list.ts index e2cb32bc..50bbd060 100644 --- a/src/ui/splat-list.ts +++ b/src/ui/splat-list.ts @@ -191,7 +191,7 @@ class SplatList extends Container { }); } - protected _onAppendChild(element: Element): void { + protected _onAppendChild(element: PcuiElement): void { super._onAppendChild(element); if (element instanceof SplatItem) { @@ -205,7 +205,7 @@ class SplatList extends Container { } } - protected _onRemoveChild(element: Element): void { + protected _onRemoveChild(element: PcuiElement): void { if (element instanceof SplatItem) { element.unbind('click'); element.unbind('removeClicked'); diff --git a/src/ui/style.scss b/src/ui/style.scss index 7d52c548..0acec3c3 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -20,6 +20,14 @@ $bgclr-light: #333; font-size: 12px; } +*.unselectable { + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + html { height: 100%; } diff --git a/src/ui/tooltips.ts b/src/ui/tooltips.ts index 6024acd4..ac84b648 100644 --- a/src/ui/tooltips.ts +++ b/src/ui/tooltips.ts @@ -1,7 +1,9 @@ import { Container, Element, Label } from 'pcui'; +type Direction = 'left' | 'right' | 'top' | 'bottom'; + class Tooltips extends Container { - register: (target: Element, text: string) => void; + register: (target: Element, text: string, direction?: Direction) => void; unregister: (target: Element) => void; destroy: () => void; @@ -24,7 +26,7 @@ class Tooltips extends Container { const style = this.dom.style; let timer: number = 0; - this.register = (target: Element, textString: string, direction: 'left' | 'right' | 'top' | 'bottom' = 'bottom') => { + this.register = (target: Element, textString: string, direction: Direction = 'bottom') => { const activate = () => { const rect = target.dom.getBoundingClientRect(); From 5665f6faf34b8e52509cd0ec7f3fa61f37fe15cd Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Tue, 30 Jul 2024 16:21:20 +0100 Subject: [PATCH 11/33] get focus working --- src/main.ts | 2 +- src/ui/bottom-toolbar.scss | 2 -- src/ui/bottom-toolbar.ts | 7 +------ src/ui/editor.ts | 6 +++++- src/ui/menu.scss | 2 -- src/ui/menu.ts | 7 +------ src/ui/panel.scss | 2 -- src/ui/right-toolbar.scss | 2 -- src/ui/right-toolbar.ts | 7 +------ src/ui/scene-panel.ts | 7 +------ src/ui/view-panel.ts | 7 +------ 11 files changed, 11 insertions(+), 40 deletions(-) diff --git a/src/main.ts b/src/main.ts index c14c9f7c..97f39b34 100644 --- a/src/main.ts +++ b/src/main.ts @@ -153,7 +153,7 @@ const main = async () => { registerEditorEvents(events, editHistory, scene, editorUI); initSelection(events, scene); initShortcuts(events); - await initFileHandler(scene, events, editorUI.canvas, remoteStorageDetails); + await initFileHandler(scene, events, editorUI.appContainer.dom, remoteStorageDetails); // load async models await scene.load(); diff --git a/src/ui/bottom-toolbar.scss b/src/ui/bottom-toolbar.scss index a7880628..43af11d0 100644 --- a/src/ui/bottom-toolbar.scss +++ b/src/ui/bottom-toolbar.scss @@ -12,8 +12,6 @@ display: flex; flex-direction: row; align-items: center; - - pointer-events: all; } .bottom-toolbar-button, .bottom-toolbar-tool, .bottom-toolbar-toggle { diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts index dba62a20..05b1df9e 100644 --- a/src/ui/bottom-toolbar.ts +++ b/src/ui/bottom-toolbar.ts @@ -23,13 +23,8 @@ class BottomToolbar extends Container { super(args); - const handleDown = (event: PointerEvent) => { - event.preventDefault(); - event.stopPropagation(); - }; - this.dom.addEventListener('pointerdown', (event) => { - handleDown(event); + event.stopPropagation(); }); const undo = new Button({ diff --git a/src/ui/editor.ts b/src/ui/editor.ts index 077ce5e7..928a7186 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -173,7 +173,11 @@ class EditorUI { // whenever the canvas container is clicked, set keyboard focus on the body canvasContainer.dom.addEventListener('pointerdown', (event: PointerEvent) => { - document.body.focus(); + // set focus on the body if user is busy pressing on the canvas or a child of the tools + // element + if (event.target === canvas || toolsContainer.dom.contains(event.target as Node)) { + document.body.focus(); + } }, true); } diff --git a/src/ui/menu.scss b/src/ui/menu.scss index 844f105e..18530e08 100644 --- a/src/ui/menu.scss +++ b/src/ui/menu.scss @@ -13,8 +13,6 @@ display: flex; flex-direction: row; align-items: center; - - pointer-events: all; } #menu-icon { diff --git a/src/ui/menu.ts b/src/ui/menu.ts index d2742669..efc15da5 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -14,13 +14,8 @@ class Menu extends Container { super(args); - const handleDown = (event: PointerEvent) => { - event.preventDefault(); - event.stopPropagation(); - }; - this.dom.addEventListener('pointerdown', (event) => { - handleDown(event); + event.stopPropagation(); }); const icon = document.createElement('img'); diff --git a/src/ui/panel.scss b/src/ui/panel.scss index 28db4309..849e7f02 100644 --- a/src/ui/panel.scss +++ b/src/ui/panel.scss @@ -5,8 +5,6 @@ background-color: $bcg-primary; - pointer-events: all; - & > .panel-header { display: flex; flex-direction: row; diff --git a/src/ui/right-toolbar.scss b/src/ui/right-toolbar.scss index c3b56f7b..3669d362 100644 --- a/src/ui/right-toolbar.scss +++ b/src/ui/right-toolbar.scss @@ -13,8 +13,6 @@ flex-direction: column; align-items: center; - pointer-events: all; - &>.right-toolbar-button, .right-toolbar-tool, .right-toolbar-toggle { width: 38px; height: 38px; diff --git a/src/ui/right-toolbar.ts b/src/ui/right-toolbar.ts index c7d18310..ff0ad3c5 100644 --- a/src/ui/right-toolbar.ts +++ b/src/ui/right-toolbar.ts @@ -19,13 +19,8 @@ class RightToolbar extends Container { super(args); - const handleDown = (event: PointerEvent) => { - event.preventDefault(); - event.stopPropagation(); - }; - this.dom.addEventListener('pointerdown', (event) => { - handleDown(event); + event.stopPropagation(); }); const showHideSplats = new Button({ diff --git a/src/ui/scene-panel.ts b/src/ui/scene-panel.ts index 1d9a9cb3..d5a2a9ec 100644 --- a/src/ui/scene-panel.ts +++ b/src/ui/scene-panel.ts @@ -14,13 +14,8 @@ class ScenePanel extends Container { super(args); - const handleDown = (event: PointerEvent) => { - event.preventDefault(); - event.stopPropagation(); - }; - this.dom.addEventListener('pointerdown', (event) => { - handleDown(event); + event.stopPropagation(); }); const sceneHeader = new Container({ diff --git a/src/ui/view-panel.ts b/src/ui/view-panel.ts index bc753b4e..76160231 100644 --- a/src/ui/view-panel.ts +++ b/src/ui/view-panel.ts @@ -13,13 +13,8 @@ class ViewPanel extends Container { super(args); - const handleDown = (event: PointerEvent) => { - event.preventDefault(); - event.stopPropagation(); - }; - this.dom.addEventListener('pointerdown', (event) => { - handleDown(event); + event.stopPropagation(); }); // header From 6b7b837a189fb71457ec5fc3033bd072a92ab2f3 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Wed, 31 Jul 2024 17:01:03 +0100 Subject: [PATCH 12/33] added initial menu panel --- src/ui/bottom-toolbar.scss | 4 +- src/ui/editor.ts | 4 +- src/ui/menu-panel.scss | 46 ++++++ src/ui/menu-panel.ts | 156 ++++++++++++++++++++ src/ui/menu.scss | 64 ++++---- src/ui/menu.ts | 290 ++++++++++++++++++------------------- src/ui/right-toolbar.scss | 4 +- src/ui/style.scss | 4 +- src/ui/tooltip.ts | 51 ------- src/ui/transform.scss | 2 +- 10 files changed, 388 insertions(+), 237 deletions(-) create mode 100644 src/ui/menu-panel.scss create mode 100644 src/ui/menu-panel.ts delete mode 100644 src/ui/tooltip.ts diff --git a/src/ui/bottom-toolbar.scss b/src/ui/bottom-toolbar.scss index 43af11d0..d248f245 100644 --- a/src/ui/bottom-toolbar.scss +++ b/src/ui/bottom-toolbar.scss @@ -38,7 +38,7 @@ width: 1px; height: 38px; margin: 0px 8px; - background-color: $bgclr-light; + background-color: $bcg-primary; } .bottom-toolbar-button { @@ -50,7 +50,7 @@ } .bottom-toolbar-tool { - background-color: $bgclr-light; + background-color: $bcg-primary; svg { path { diff --git a/src/ui/editor.ts b/src/ui/editor.ts index 928a7186..f3d1d71f 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -85,20 +85,20 @@ class EditorUI { tooltipsContainer.append(tooltips); // bottom toolbar - const menu = new Menu(events); const scenePanel = new ScenePanel(events, tooltips); const viewPanel = new ViewPanel(events, tooltips); const bottomToolbar = new BottomToolbar(events, tooltips); const rightToolbar = new RightToolbar(events, tooltips); + const menu = new Menu(events); canvasContainer.dom.appendChild(canvas); canvasContainer.append(filenameLabel); canvasContainer.append(toolsContainer); - canvasContainer.append(menu); canvasContainer.append(scenePanel); canvasContainer.append(viewPanel); canvasContainer.append(bottomToolbar); canvasContainer.append(rightToolbar); + canvasContainer.append(menu); // view axes container const viewCube = new ViewCube(events); diff --git a/src/ui/menu-panel.scss b/src/ui/menu-panel.scss new file mode 100644 index 00000000..13030cba --- /dev/null +++ b/src/ui/menu-panel.scss @@ -0,0 +1,46 @@ +.menu-panel { + position: absolute; + + &::not(.pcui-hidden) { + display: flex; + } + flex-direction: column; + + border: 1px solid $bcg-light; + border-radius: 8px; + overflow: hidden; + + background-color: rgba(40, 40, 40, 0.8); + backdrop-filter: blur(2px); + + cursor: pointer; +} + +.menu-row { + display: flex; + flex-direction: row; + min-width: 180px; + + &:hover { + background-color: $bcg-darker; + } +} + +.menu-row-icon { + font-family: 'pc-icon'; +} + +.menu-row-text { + flex-grow: 1; +} + +.menu-row-postscript { + color: $text-dark; +} + +.menu-row-separator { + margin-left: 10%; + width: 80%; + height: 1px; + background-color: $bcg-light; +} diff --git a/src/ui/menu-panel.ts b/src/ui/menu-panel.ts new file mode 100644 index 00000000..07baa612 --- /dev/null +++ b/src/ui/menu-panel.ts @@ -0,0 +1,156 @@ +import { Container, Label } from 'pcui'; + +type MenuItemType = 'button' | 'menu' | 'separator'; + +type Direction = 'left' | 'right' | 'top' | 'bottom'; + +type MenuItem = { + type?: MenuItemType; + + text?: string; + icon?: string; + shortcut?: string; + menuPanel?: MenuPanel; + + onSelect?: () => void; + isEnabled?: () => boolean; +}; + +const offsetParent = (elem: HTMLElement) : HTMLElement => { + const parent = elem.parentNode as HTMLElement; + + return (parent.tagName === 'BODY' || window.getComputedStyle(parent).position !== 'static') ? + parent : + offsetParent(parent); +} + +const arrange = (element: HTMLElement, target: HTMLElement, direction: Direction, padding: number) => { + const rect = target.getBoundingClientRect(); + const parentRect = offsetParent(element).getBoundingClientRect(); + + const style = element.style; + switch (direction) { + case 'left': + break; + case 'right': + style.left = `${rect.right - parentRect.left + padding}px`; + style.top = `${rect.top - parentRect.top}px`; + break; + case 'top': + break; + case 'bottom': + style.left = `${rect.left - parentRect.left}px`; + style.top = `${rect.bottom - parentRect.top + padding}px`; + break; + } +}; + +class MenuPanel extends Container { + constructor(menuItems: MenuItem[], args = {}) { + args = { + ...args, + class: 'menu-panel', + hidden: true + }; + + super(args); + + this.on('hide', () => { + for (let menuItem of menuItems) { + if (menuItem.menuPanel) { + menuItem.menuPanel.hidden = true; + } + } + }); + + let deactivate: () => void | null = null; + + for (let menuItem of menuItems) { + const row = new Container({ class: 'menu-row' }); + const type = menuItem.type ?? (menuItem.menuPanel ? 'menu' : menuItem.text ? 'button' : 'separator'); + + let activate: () => void | null = null; + switch (type) { + case 'button': { + const icon = new Label({ class: 'menu-row-icon' }); + const text = new Label({ class: 'menu-row-text', text: menuItem.text }); + const postscript = new Label({ class: 'menu-row-postscript' }); + row.append(icon); + row.append(text); + row.append(postscript); + + row.dom.addEventListener('pointerdown', () => { + if (menuItem.onSelect) { + menuItem.onSelect(); + } + }); + + row.dom.addEventListener('pointerup', () => { + if (menuItem.onSelect) { + menuItem.onSelect(); + } + }); + + break; + } + + case 'menu': { + const icon = new Label({ class: 'menu-row-icon' }); + const text = new Label({ class: 'menu-row-text', text: menuItem.text }); + const postscript = new Label({ class: 'menu-row-postscript', text: '>' }); + row.append(icon); + row.append(text); + row.append(postscript); + + const childPanel = menuItem.menuPanel; + if (childPanel) { + activate = () => { + if (childPanel.hidden) { + childPanel.position(row.dom, 'right', 2); + childPanel.hidden = !childPanel.hidden; + } + + deactivate = () => { + childPanel.hidden = true; + }; + }; + } + + break; + } + + case 'separator': + row.append(new Container({ class: 'menu-row-separator' })); + break; + } + + let timer = -1; + + row.dom.addEventListener('pointerenter', () => { + timer = setTimeout(() => { + if (deactivate) { + deactivate(); + } + if (activate) { + activate(); + } + }, 250); + }); + + row.dom.addEventListener('pointerleave', () => { + if (timer !== -1) { + clearTimeout(timer); + timer = -1; + } + }); + + this.append(row); + } + } + + position(parent: HTMLElement, direction: Direction, padding = 2) { + arrange(this.dom, parent, direction, padding); + } +} + +export { MenuPanel }; diff --git a/src/ui/menu.scss b/src/ui/menu.scss index 18530e08..8053d608 100644 --- a/src/ui/menu.scss +++ b/src/ui/menu.scss @@ -1,5 +1,9 @@ #menu { position: absolute; +} + +#menu-bar { + position: absolute; top: 24px; left: 24px; width: 320px; @@ -19,45 +23,41 @@ width: 54px; } -.menu-button { - height: 38px; - margin: 0px 1px; - padding: 0px 20px; - border: 0px; - border-radius: 2px; -} +#menu-title { + position: absolute; + right: 2px; + top: 0px; + font-size: 10px; + color: $text-dark; -#menu-dropdown > div > div { - // WARNING: first and last child will hide any submenus - &:first-child { - border-radius: 10px 10px 0px 0px; - overflow: hidden; - } + margin: 2px; +} - &:last-child { - border-radius: 0px 0px 10px 10px; - overflow: hidden; - } +#menu-container { + display: flex; + flex-direction: column; } -#menu-dropdown > div > div > div { - margin: 1px 0px 0px 0px; +#menu-options-container { + display: flex; + flex-direction: row; + height: 100%; + flex-grow: 1; + align-items: flex-end; +} - background-color: $bcg-dark; - color: $clr-default; +.menu-option { + padding: 8px 20px; + margin: 0px; + flex-grow: 1; + text-align: center; - &:hover { - background-color: $bcg-darker; - color: $clr-active; - } -} + background-color: $bcg-primary; -#menu-dropdown > div > div > div > span { - height: 28px; - line-height: 28px; + cursor: pointer; - &::before { - margin: 0px 10px; - font-size: 14px; + &:hover { + // color: $text-primary; + background-color: $bcg-dark; } } diff --git a/src/ui/menu.ts b/src/ui/menu.ts index efc15da5..72730777 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -1,7 +1,8 @@ -import { Button, Container, Menu as PcuiMenu } from 'pcui'; +import { Container, Label } from 'pcui'; import { Events } from '../events'; -import { version } from '../../package.json'; +import { MenuPanel } from './menu-panel'; +import { version } from '../../package.json'; import logoSvg from '../svg/playcanvas-logo.svg'; class Menu extends Container { @@ -14,7 +15,11 @@ class Menu extends Container { super(args); - this.dom.addEventListener('pointerdown', (event) => { + const menubar = new Container({ + id: 'menu-bar' + }); + + menubar.dom.addEventListener('pointerdown', (event) => { event.stopPropagation(); }); @@ -22,164 +27,157 @@ class Menu extends Container { icon.setAttribute('id', 'menu-icon'); icon.src = logoSvg; - const scene = new Button({ + const title = new Label({ + text: `SUPERSPLAT v${version}`, + id: 'menu-title' + }); + + const scene = new Label({ text: 'Scene', - class: 'menu-button' + class: 'menu-option' }); - const selection = new Button({ + const selection = new Label({ text: 'Selection', - class: 'menu-button' + class: 'menu-option' }); - const help = new Button({ + const help = new Label({ text: 'Help', - class: 'menu-button' + class: 'menu-option' }); - this.dom.appendChild(icon); - this.append(scene); - this.append(selection); - this.append(help); - - const sceneMenu = new PcuiMenu({ - id: 'menu-dropdown', - items: [{ - class: 'menu-dropdown-item', - text: 'New', - icon: 'E208', - onSelect: () => events.invoke('scene.new') - }, { - class: 'menu-dropdown-item', - text: 'Open', - icon: 'E226', - onSelect: async () => { - if (await events.invoke('scene.new')) { - events.fire('scene.open'); - } - } - }, { - class: 'menu-dropdown-item', - text: 'Import', - icon: 'E245', - onSelect: () => events.fire('scene.open') - }, { - class: 'menu-dropdown-item', - text: 'Save', - icon: 'E216', - onSelect: () => events.fire('scene.save'), - onIsEnabled: () => !events.invoke('scene.empty') - }, { - class: 'menu-dropdown-item', - text: 'Save As...', - icon: 'E216', - onSelect: () => events.fire('scene.saveAs'), - onIsEnabled: () => !events.invoke('scene.empty') - }, { - class: 'menu-dropdown-item', - text: 'Export', - icon: 'E225', - onIsEnabled: () => !events.invoke('scene.empty'), - items: [{ - class: 'menu-dropdown-item', - text: 'Compressed Ply', - icon: 'E245', - onSelect: () => events.invoke('scene.export', 'compressed-ply') - }, { - class: 'menu-dropdown-item', - text: 'Splat file', - icon: 'E245', - onSelect: () => events.invoke('scene.export', 'splat') - }] - }, { - class: 'menu-dropdown-item', - text: `SUPERSPLAT v${version}`, - icon: 'E0020', - enabled: false - }] - }); - - const selectionMenu = new PcuiMenu({ - id: 'menu-dropdown', - items: [{ - class: 'menu-dropdown-item', - text: 'All', - icon: 'E0020', - onSelect: () => events.fire('select.all') - }, { - class: 'menu-dropdown-item', - text: 'None', - icon: 'E0020', - onSelect: () => events.fire('select.none') - }, { - class: 'menu-dropdown-item', - text: 'Invert', - icon: 'E0020', - onSelect: () => events.fire('select.invert') - }] - }); - - const helpMenu = new PcuiMenu({ - id: 'menu-dropdown', - items: [{ - class: 'menu-dropdown-item', - text: 'About SuperSplat', - icon: 'E138', - onSelect: () => events.fire('show.about') - }, { - class: 'menu-dropdown-item', - text: 'Keyboard Shortcuts', - icon: 'E136', - onSelect: () => events.fire('show.shortcuts') - }, { - class: 'menu-dropdown-item', - text: 'GitHub Repo', - icon: 'E259', - onSelect: () => window.open('https://github.com/playcanvas/supersplat', '_blank').focus() - }] - }); - - this.append(sceneMenu); - this.append(selectionMenu); - this.append(helpMenu); - - scene.on('click', () => { - const r = scene.dom.getBoundingClientRect(); - sceneMenu.position(r.left, r.bottom + 8); - sceneMenu.hidden = false; - }); - - selection.on('click', () => { - const r = selection.dom.getBoundingClientRect(); - selectionMenu.position(r.left, r.bottom + 8); - selectionMenu.hidden = false; + const buttonsContainer = new Container({ + id: 'menu-options-container' }); + buttonsContainer.append(scene); + buttonsContainer.append(selection); + buttonsContainer.append(help); + + menubar.dom.appendChild(icon); + menubar.append(buttonsContainer); + menubar.append(title); + + const exportMenuPanel = new MenuPanel([{ + text: 'Compressed Ply', + icon: 'E245', + onSelect: () => events.invoke('scene.export', 'compressed-ply') + }, { + text: 'Splat file', + icon: 'E245', + onSelect: () => events.invoke('scene.export', 'splat') + }]); + + const sceneMenuPanel = new MenuPanel([{ + text: 'New', + icon: 'E208', + onSelect: () => events.invoke('scene.new') + }, { + text: 'Open', + icon: 'E226', + onSelect: async () => { + if (await events.invoke('scene.new')) { + events.fire('scene.open'); + } + } + }, { + text: 'Import', + icon: 'E245', + onSelect: () => events.fire('scene.open') + }, { + // separator + }, { + text: 'Save', + icon: 'E216', + onSelect: () => events.fire('scene.save'), + isEnabled: () => !events.invoke('scene.empty') + }, { + text: 'Save As...', + icon: 'E216', + onSelect: () => events.fire('scene.saveAs'), + isEnabled: () => !events.invoke('scene.empty') + }, { + type: 'menu', + text: 'Export', + icon: 'E225', + menuPanel: exportMenuPanel, + isEnabled: () => !events.invoke('scene.empty'), + }]); + + const selectionMenuPanel = new MenuPanel([{ + text: 'All', + icon: 'E0020', + onSelect: () => events.fire('select.all') + }, { + text: 'None', + icon: 'E0020', + onSelect: () => events.fire('select.none') + }, { + text: 'Invert', + icon: 'E0020', + onSelect: () => events.fire('select.invert') + }]); + + const helpMenuPanel = new MenuPanel([{ + text: 'About SuperSplat', + icon: 'E138', + onSelect: () => events.fire('show.about') + }, { + text: 'Keyboard Shortcuts', + icon: 'E136', + onSelect: () => events.fire('show.shortcuts') + }, { + text: 'GitHub Repo', + icon: 'E259', + onSelect: () => window.open('https://github.com/playcanvas/supersplat', '_blank').focus() + }]); + + this.append(menubar); + this.append(sceneMenuPanel); + this.append(exportMenuPanel); + this.append(selectionMenuPanel); + this.append(helpMenuPanel); + + const options: { dom: HTMLElement, menuPanel: MenuPanel }[] = [{ + dom: scene.dom, + menuPanel: sceneMenuPanel + }, { + dom: selection.dom, + menuPanel: selectionMenuPanel + }, { + dom: help.dom, + menuPanel: helpMenuPanel + }]; + + options.forEach((option) => { + const activate = () => { + option.menuPanel.position(option.dom, 'bottom', 2); + options.forEach(opt => opt.menuPanel.hidden = opt !== option); + }; + + option.dom.addEventListener('pointerdown', (event: PointerEvent) => { + if (!option.menuPanel.hidden) { + option.menuPanel.hidden = true; + } else { + activate(); + } + }); - help.on('click', () => { - const r = help.dom.getBoundingClientRect(); - helpMenu.position(r.left, r.bottom + 8); - helpMenu.hidden = false; + option.dom.addEventListener('pointerenter', (event: PointerEvent) => { + if (!options.every(opt => opt.menuPanel.hidden)) { + activate(); + } + }); }); - window.addEventListener('click', (e: Event) => { - if (!sceneMenu.hidden && - !sceneMenu.dom.contains(e.target as Node) && - e.target !== scene.dom) { - sceneMenu.hidden = true; - } - - if (!selectionMenu.hidden && - !selectionMenu.dom.contains(e.target as Node) && - e.target !== selection.dom) { - selectionMenu.hidden = true; + const checkEvent = (event: PointerEvent) => { + if (!this.dom.contains(event.target as Node)) { + options.forEach(opt => opt.menuPanel.hidden = true); } + }; - if (!helpMenu.hidden && - !helpMenu.dom.contains(e.target as Node) && - e.target !== help.dom) { - helpMenu.hidden = true; - } - }); + window.addEventListener('pointerdown', checkEvent, true); + window.addEventListener('pointerup', checkEvent, true); } } diff --git a/src/ui/right-toolbar.scss b/src/ui/right-toolbar.scss index 3669d362..a776c55e 100644 --- a/src/ui/right-toolbar.scss +++ b/src/ui/right-toolbar.scss @@ -37,7 +37,7 @@ width: 38px; height: 1px; margin: 8px 0px; - background-color: $bgclr-light; + background-color: $bcg-primary; } &>.right-toolbar-button { @@ -49,7 +49,7 @@ } &>.right-toolbar-tool { - background-color: $bgclr-light; + background-color: $bcg-primary; svg { path { diff --git a/src/ui/style.scss b/src/ui/style.scss index 0acec3c3..a7c585ca 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -4,10 +4,12 @@ $clr-default: #b3aaac; $clr-disabled: #7c7678; $clr-active: white; $clr-hilight: #f60; -$bgclr-light: #333; + +$bcg-light: #555; @import 'tooltips.scss'; @import 'panel.scss'; +@import 'menu-panel.scss'; @import 'menu.scss'; @import 'scene-panel.scss'; @import 'view-panel.scss'; diff --git a/src/ui/tooltip.ts b/src/ui/tooltip.ts deleted file mode 100644 index 3fbd44b3..00000000 --- a/src/ui/tooltip.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Container, Element, Label } from 'pcui'; - -class Tooltip extends Container { - target: Element; - align: string; - content: Label; - - constructor(args: any = {}) { - args = { - ...args, - class: 'tooltip', - hidden: true - }; - - super(args); - - this.target = args.target; - this.align = args.align ?? 'right'; - this.content = new Label({ - class: 'tooltip-content', - text: args.text - }); - - this.append(this.content); - - args.target.dom.addEventListener('mouseover', () => { - this.show(); - }); - - args.target.dom.addEventListener('mouseout', () => { - this.hide(); - }); - } - - show() { - const style = this.dom.style; - const target = this.target.dom; - const rect = target.getBoundingClientRect(); - - style.left = `${rect.right}px`; - style.top = `${rect.top}px`; - - this.hidden = false; - } - - hide() { - this.hidden = true; - } -} - -export { Tooltip }; diff --git a/src/ui/transform.scss b/src/ui/transform.scss index d64cf854..0a2b0092 100644 --- a/src/ui/transform.scss +++ b/src/ui/transform.scss @@ -3,7 +3,7 @@ display: flex; flex-direction: column; - background-color: $bgclr-light; + background-color: $bcg-primary; padding: 6px; } From 0f3d333e7b4bd8556b145279a020a9c40715b83c Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Wed, 31 Jul 2024 17:25:00 +0100 Subject: [PATCH 13/33] bump package version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 422a94bc..f34837d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "supersplat", - "version": "0.25.2", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "supersplat", - "version": "0.25.2", + "version": "1.0.0", "license": "MIT", "devDependencies": { "@playcanvas/eslint-config": "^1.7.1", diff --git a/package.json b/package.json index 5ca9927a..77fcfd16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "supersplat", - "version": "0.25.2", + "version": "1.0.0", "author": "PlayCanvas", "homepage": "https://playcanvas.com/supersplat/editor", "description": "3D Gaussian Splat Editor", From 4f1646e99df959e54c0914b31a51ed3dbfac126a Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Wed, 31 Jul 2024 19:45:58 +0100 Subject: [PATCH 14/33] menu styling, move app lable --- src/ui/editor.ts | 12 ++++++------ src/ui/menu-panel.scss | 11 +++++++---- src/ui/menu.scss | 16 ++-------------- src/ui/menu.ts | 8 +------- src/ui/style.scss | 5 +++-- 5 files changed, 19 insertions(+), 33 deletions(-) diff --git a/src/ui/editor.ts b/src/ui/editor.ts index f3d1d71f..250f8a7e 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -13,6 +13,7 @@ import { RightToolbar } from './right-toolbar'; import { Tooltips } from './tooltips'; import { ShortcutsPopup } from './shortcuts-popup'; +import { version } from '../../package.json'; import logo from './playcanvas-logo.png'; class EditorUI { @@ -22,7 +23,6 @@ class EditorUI { canvasContainer: Container; toolsContainer: Container; canvas: HTMLCanvasElement; - filenameLabel: Label; popup: Popup; constructor(events: Events, remoteStorageMode: boolean) { @@ -66,8 +66,9 @@ class EditorUI { canvas.id = 'canvas'; // filename label - const filenameLabel = new Label({ - id: 'filename-label' + const appLabel = new Label({ + id: 'app-label', + text: `SUPERSPLAT v${version}` }); // canvas container @@ -92,7 +93,7 @@ class EditorUI { const menu = new Menu(events); canvasContainer.dom.appendChild(canvas); - canvasContainer.append(filenameLabel); + canvasContainer.append(appLabel); canvasContainer.append(toolsContainer); canvasContainer.append(scenePanel); canvasContainer.append(viewPanel); @@ -140,7 +141,6 @@ class EditorUI { this.canvasContainer = canvasContainer; this.toolsContainer = toolsContainer; this.canvas = canvas; - this.filenameLabel = filenameLabel; document.body.appendChild(appContainer.dom); document.body.setAttribute('tabIndex', '-1'); @@ -182,7 +182,7 @@ class EditorUI { } setFilename(filename: string) { - this.filenameLabel.text = filename; + // this.filenameLabel.text = filename; } } diff --git a/src/ui/menu-panel.scss b/src/ui/menu-panel.scss index 13030cba..9a25cf4a 100644 --- a/src/ui/menu-panel.scss +++ b/src/ui/menu-panel.scss @@ -6,12 +6,11 @@ } flex-direction: column; - border: 1px solid $bcg-light; border-radius: 8px; overflow: hidden; - background-color: rgba(40, 40, 40, 0.8); - backdrop-filter: blur(2px); + background-color: $bcg-dark; + filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.8)); cursor: pointer; } @@ -22,7 +21,11 @@ min-width: 180px; &:hover { - background-color: $bcg-darker; + background-color: $bcg-darkest; + + & > .menu-row-text { + color: $text-primary; + } } } diff --git a/src/ui/menu.scss b/src/ui/menu.scss index 8053d608..02e3aa85 100644 --- a/src/ui/menu.scss +++ b/src/ui/menu.scss @@ -23,16 +23,6 @@ width: 54px; } -#menu-title { - position: absolute; - right: 2px; - top: 0px; - font-size: 10px; - color: $text-dark; - - margin: 2px; -} - #menu-container { display: flex; flex-direction: column; @@ -41,13 +31,11 @@ #menu-options-container { display: flex; flex-direction: row; - height: 100%; flex-grow: 1; - align-items: flex-end; } .menu-option { - padding: 8px 20px; + padding: 16px 20px; margin: 0px; flex-grow: 1; text-align: center; @@ -57,7 +45,7 @@ cursor: pointer; &:hover { - // color: $text-primary; + color: $text-primary; background-color: $bcg-dark; } } diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 72730777..a33adf7a 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -2,7 +2,6 @@ import { Container, Label } from 'pcui'; import { Events } from '../events'; import { MenuPanel } from './menu-panel'; -import { version } from '../../package.json'; import logoSvg from '../svg/playcanvas-logo.svg'; class Menu extends Container { @@ -27,11 +26,6 @@ class Menu extends Container { icon.setAttribute('id', 'menu-icon'); icon.src = logoSvg; - const title = new Label({ - text: `SUPERSPLAT v${version}`, - id: 'menu-title' - }); - const scene = new Label({ text: 'Scene', class: 'menu-option' @@ -56,7 +50,7 @@ class Menu extends Container { menubar.dom.appendChild(icon); menubar.append(buttonsContainer); - menubar.append(title); + // menubar.append(title); const exportMenuPanel = new MenuPanel([{ text: 'Compressed Ply', diff --git a/src/ui/style.scss b/src/ui/style.scss index a7c585ca..d291ba08 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -6,6 +6,7 @@ $clr-active: white; $clr-hilight: #f60; $bcg-light: #555; +$bcg-darkest: #181818; @import 'tooltips.scss'; @import 'panel.scss'; @@ -197,12 +198,12 @@ body { background-color: $bcg-darker; } -#filename-label { +#app-label { position: absolute; right: 20px; bottom: 20px; color: $text-primary; - text-shadow: 0 0 4px black; + text-shadow: 1px 1px 4px black; } #control-panel > .pcui-panel-header { From 01e87dbbcf731fefc3c79fbcd5241d05ad393456 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Thu, 1 Aug 2024 09:13:58 +0100 Subject: [PATCH 15/33] move selection items from control panel to menu --- src/ui/control-panel.ts | 65 ----------------------------------------- src/ui/menu-panel.scss | 7 +++-- src/ui/menu-panel.ts | 52 ++++++++++++++++++--------------- src/ui/menu.scss | 1 + src/ui/menu.ts | 21 ++++++++++++- 5 files changed, 54 insertions(+), 92 deletions(-) diff --git a/src/ui/control-panel.ts b/src/ui/control-panel.ts index 57e930cc..d085f45b 100644 --- a/src/ui/control-panel.ts +++ b/src/ui/control-panel.ts @@ -119,53 +119,6 @@ class ControlPanel extends Panel { selectionPanel.append(selectByPlane); selectionPanel.append(setAddRemove); - // show panel - const showPanel = new Panel({ - id: 'show-panel', - class: 'control-panel', - headerText: 'SHOW' - }); - - const showButtons = new Container({ - class: 'control-parent' - }); - - const hideSelection = new Button({ - class: 'control-element-expand', - text: 'Hide Selection' - }); - - const unhideAll = new Button({ - class: 'control-element-expand', - text: 'Unhide All' - }); - - showButtons.append(hideSelection); - showButtons.append(unhideAll); - - showPanel.append(showButtons); - - // modify - const modifyPanel = new Panel({ - id: 'modify-panel', - class: 'control-panel', - headerText: 'MODIFY' - }); - - const deleteSelectionButton = new Button({ - class: 'control-element-expand', - text: 'Delete Selected Splats', - icon: 'E124' - }); - - const resetButton = new Button({ - class: 'control-element-expand', - text: 'Reset Splats' - }); - - modifyPanel.append(deleteSelectionButton); - modifyPanel.append(resetButton); - // options const optionsPanel = new Panel({ id: 'options-panel', @@ -197,8 +150,6 @@ class ControlPanel extends Panel { }); controlsContainer.append(selectionPanel); - controlsContainer.append(showPanel); - controlsContainer.append(modifyPanel); controlsContainer.append(optionsPanel); // append @@ -341,22 +292,6 @@ class ControlPanel extends Panel { events.fire('select.byPlanePlacement', axes[selectByPlaneAxis.value], selectByPlaneOffset.value); }); - hideSelection.on('click', () => { - events.fire('select.hide'); - }); - - unhideAll.on('click', () => { - events.fire('select.unhide'); - }); - - deleteSelectionButton.on('click', () => { - events.fire('select.delete'); - }); - - resetButton.on('click', () => { - events.fire('scene.reset'); - }); - allDataToggle.on('change', (enabled: boolean) => { events.fire('allData', enabled); }); diff --git a/src/ui/menu-panel.scss b/src/ui/menu-panel.scss index 9a25cf4a..56db3968 100644 --- a/src/ui/menu-panel.scss +++ b/src/ui/menu-panel.scss @@ -19,18 +19,21 @@ display: flex; flex-direction: row; min-width: 180px; + align-items: center; + height: 32px; + padding: 0px 8px; &:hover { background-color: $bcg-darkest; - & > .menu-row-text { + & > .menu-row-text, .menu-row-postscript, .menu-row-icon { color: $text-primary; } } } .menu-row-icon { - font-family: 'pc-icon'; + font-family: 'pc-icon' !important; } .menu-row-text { diff --git a/src/ui/menu-panel.ts b/src/ui/menu-panel.ts index 07baa612..d72f107d 100644 --- a/src/ui/menu-panel.ts +++ b/src/ui/menu-panel.ts @@ -66,15 +66,16 @@ class MenuPanel extends Container { let deactivate: () => void | null = null; for (let menuItem of menuItems) { - const row = new Container({ class: 'menu-row' }); const type = menuItem.type ?? (menuItem.menuPanel ? 'menu' : menuItem.text ? 'button' : 'separator'); + let row: Container | null = null; let activate: () => void | null = null; switch (type) { case 'button': { - const icon = new Label({ class: 'menu-row-icon' }); + row = new Container({ class: 'menu-row' }); + const icon = new Label({ class: 'menu-row-icon', text: menuItem.icon && String.fromCodePoint(parseInt(menuItem.icon, 16)) }); const text = new Label({ class: 'menu-row-text', text: menuItem.text }); - const postscript = new Label({ class: 'menu-row-postscript' }); + const postscript = new Label({ class: 'menu-row-postscript', text: menuItem.shortcut }); row.append(icon); row.append(text); row.append(postscript); @@ -95,9 +96,10 @@ class MenuPanel extends Container { } case 'menu': { - const icon = new Label({ class: 'menu-row-icon' }); + row = new Container({ class: 'menu-row' }); + const icon = new Label({ class: 'menu-row-icon', text: menuItem.icon &&String.fromCodePoint(parseInt(menuItem.icon, 16)) }); const text = new Label({ class: 'menu-row-text', text: menuItem.text }); - const postscript = new Label({ class: 'menu-row-postscript', text: '>' }); + const postscript = new Label({ class: 'menu-row-postscript', text: '\u232A' }); row.append(icon); row.append(text); row.append(postscript); @@ -120,31 +122,33 @@ class MenuPanel extends Container { } case 'separator': - row.append(new Container({ class: 'menu-row-separator' })); + this.append(new Container({ class: 'menu-row-separator' })); break; } - let timer = -1; + if (row) { + let timer = -1; - row.dom.addEventListener('pointerenter', () => { - timer = setTimeout(() => { - if (deactivate) { - deactivate(); - } - if (activate) { - activate(); - } - }, 250); - }); + row.dom.addEventListener('pointerenter', () => { + timer = setTimeout(() => { + if (deactivate) { + deactivate(); + } + if (activate) { + activate(); + } + }, 250); + }); - row.dom.addEventListener('pointerleave', () => { - if (timer !== -1) { - clearTimeout(timer); - timer = -1; - } - }); + row.dom.addEventListener('pointerleave', () => { + if (timer !== -1) { + clearTimeout(timer); + timer = -1; + } + }); - this.append(row); + this.append(row); + } } } diff --git a/src/ui/menu.scss b/src/ui/menu.scss index 02e3aa85..20ea5f59 100644 --- a/src/ui/menu.scss +++ b/src/ui/menu.scss @@ -21,6 +21,7 @@ #menu-icon { width: 54px; + font-family: 'pc-icon'; } #menu-container { diff --git a/src/ui/menu.ts b/src/ui/menu.ts index a33adf7a..6a06e9ce 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -50,7 +50,6 @@ class Menu extends Container { menubar.dom.appendChild(icon); menubar.append(buttonsContainer); - // menubar.append(title); const exportMenuPanel = new MenuPanel([{ text: 'Compressed Ply', @@ -101,15 +100,35 @@ class Menu extends Container { const selectionMenuPanel = new MenuPanel([{ text: 'All', icon: 'E0020', + shortcut: 'A', onSelect: () => events.fire('select.all') }, { text: 'None', icon: 'E0020', + shortcut: 'Shift + A', onSelect: () => events.fire('select.none') }, { text: 'Invert', icon: 'E0020', + shortcut: 'I', onSelect: () => events.fire('select.invert') + }, { + // separator + }, { + text: 'Lock Selection', + shortcut: 'H', + onSelect: () => events.fire('select.hide') + }, { + text: 'Unlock All', + shortcut: 'U', + onSelect: () => events.fire('select.unhide') + }, { + text: 'Delete Selection', + shortcut: 'Delete', + onSelect: () => events.fire('select.delete') + }, { + text: 'Reset Splat', + onSelect: () => events.fire('scene.reset') }]); const helpMenuPanel = new MenuPanel([{ From 48919a17eada09f435b398014d879d22c225fd17 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Thu, 1 Aug 2024 09:53:31 +0100 Subject: [PATCH 16/33] move all data toggle to menu --- src/editor.ts | 15 +++++++++++++-- src/ui/control-panel.ts | 35 ----------------------------------- src/ui/menu-panel.scss | 14 ++++++++++++-- src/ui/menu-panel.ts | 20 ++++++++++++-------- src/ui/menu.ts | 32 ++++++++++++++++++++++++-------- 5 files changed, 61 insertions(+), 55 deletions(-) diff --git a/src/editor.ts b/src/editor.ts index e26b3344..59f1e432 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -489,8 +489,19 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S }); }); - events.on('allData', (value: boolean) => { - scene.assetLoader.loadAllData = value; + const setAllData = (value: boolean) => { + if (value !== scene.assetLoader.loadAllData) { + scene.assetLoader.loadAllData = value; + events.fire('allData', scene.assetLoader.loadAllData); + } + }; + + events.function('allData', () => { + return scene.assetLoader.loadAllData; + }); + + events.on('toggleAllData', (value: boolean) => { + setAllData(!events.invoke('allData')); }); } diff --git a/src/ui/control-panel.ts b/src/ui/control-panel.ts index d085f45b..473f5fd3 100644 --- a/src/ui/control-panel.ts +++ b/src/ui/control-panel.ts @@ -119,38 +119,11 @@ class ControlPanel extends Panel { selectionPanel.append(selectByPlane); selectionPanel.append(setAddRemove); - // options - const optionsPanel = new Panel({ - id: 'options-panel', - class: 'control-panel', - headerText: 'OPTIONS' - }); - - const allData = new Container({ - class: 'control-parent' - }); - - const allDataLabel = new Label({ - class: 'control-label', - text: 'Load all PLY data' - }); - - const allDataToggle = new BooleanInput({ - class: 'control-element', - value: true - }); - - allData.append(allDataLabel); - allData.append(allDataToggle); - - optionsPanel.append(allData); - const controlsContainer = new Container({ id: 'control-panel-controls' }); controlsContainer.append(selectionPanel); - controlsContainer.append(optionsPanel); // append this.content.append(controlsContainer); @@ -291,14 +264,6 @@ class ControlPanel extends Panel { selectByPlaneOffset.on('change', () => { events.fire('select.byPlanePlacement', axes[selectByPlaneAxis.value], selectByPlaneOffset.value); }); - - allDataToggle.on('change', (enabled: boolean) => { - events.fire('allData', enabled); - }); - - events.on('splat.count', (count: number) => { - selectionPanel.headerText = `SELECTION${count === 0 ? '' : ' (' + count.toString() + ')'}`; - }); } } diff --git a/src/ui/menu-panel.scss b/src/ui/menu-panel.scss index 56db3968..3370173c 100644 --- a/src/ui/menu-panel.scss +++ b/src/ui/menu-panel.scss @@ -30,6 +30,18 @@ color: $text-primary; } } + + // tweaks for attached boolean toggles + > .pcui-boolean-input { + background-color: $bcg-darkest; + border-radius: 2px; + &.pcui-boolean-input-ticked { + background-color: $clr-hilight; + &::after { + color: $text-primary; + } + } + } } .menu-row-icon { @@ -45,8 +57,6 @@ } .menu-row-separator { - margin-left: 10%; - width: 80%; height: 1px; background-color: $bcg-light; } diff --git a/src/ui/menu-panel.ts b/src/ui/menu-panel.ts index d72f107d..4cb6e8da 100644 --- a/src/ui/menu-panel.ts +++ b/src/ui/menu-panel.ts @@ -1,4 +1,4 @@ -import { Container, Label } from 'pcui'; +import { Container, Element, Label } from 'pcui'; type MenuItemType = 'button' | 'menu' | 'separator'; @@ -9,8 +9,8 @@ type MenuItem = { text?: string; icon?: string; - shortcut?: string; - menuPanel?: MenuPanel; + additional?: string | Element; + subMenu?: MenuPanel; onSelect?: () => void; isEnabled?: () => boolean; @@ -45,6 +45,10 @@ const arrange = (element: HTMLElement, target: HTMLElement, direction: Direction } }; +const isString = (value: any) => { + return !value || typeof value === 'string' || value instanceof String; +}; + class MenuPanel extends Container { constructor(menuItems: MenuItem[], args = {}) { args = { @@ -57,8 +61,8 @@ class MenuPanel extends Container { this.on('hide', () => { for (let menuItem of menuItems) { - if (menuItem.menuPanel) { - menuItem.menuPanel.hidden = true; + if (menuItem.subMenu) { + menuItem.subMenu.hidden = true; } } }); @@ -66,7 +70,7 @@ class MenuPanel extends Container { let deactivate: () => void | null = null; for (let menuItem of menuItems) { - const type = menuItem.type ?? (menuItem.menuPanel ? 'menu' : menuItem.text ? 'button' : 'separator'); + const type = menuItem.type ?? (menuItem.subMenu ? 'menu' : menuItem.text ? 'button' : 'separator'); let row: Container | null = null; let activate: () => void | null = null; @@ -75,7 +79,7 @@ class MenuPanel extends Container { row = new Container({ class: 'menu-row' }); const icon = new Label({ class: 'menu-row-icon', text: menuItem.icon && String.fromCodePoint(parseInt(menuItem.icon, 16)) }); const text = new Label({ class: 'menu-row-text', text: menuItem.text }); - const postscript = new Label({ class: 'menu-row-postscript', text: menuItem.shortcut }); + const postscript = isString(menuItem.additional) ? new Label({ class: 'menu-row-postscript', text: menuItem.additional as string }) : menuItem.additional; row.append(icon); row.append(text); row.append(postscript); @@ -104,7 +108,7 @@ class MenuPanel extends Container { row.append(text); row.append(postscript); - const childPanel = menuItem.menuPanel; + const childPanel = menuItem.subMenu; if (childPanel) { activate = () => { if (childPanel.hidden) { diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 6a06e9ce..05e4e342 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -1,4 +1,4 @@ -import { Container, Label } from 'pcui'; +import { Container, Label, BooleanInput } from 'pcui'; import { Events } from '../events'; import { MenuPanel } from './menu-panel'; @@ -61,6 +61,14 @@ class Menu extends Container { onSelect: () => events.invoke('scene.export', 'splat') }]); + const allDataToggle = new BooleanInput({ + value: true + }); + + events.on('allData', (value) => { + allDataToggle.value = value; + }); + const sceneMenuPanel = new MenuPanel([{ text: 'New', icon: 'E208', @@ -79,6 +87,14 @@ class Menu extends Container { onSelect: () => events.fire('scene.open') }, { // separator + }, { + text: 'Load all PLY data', + additional: allDataToggle, + onSelect: () => { + events.fire('toggleAllData'); + } + }, { + // separator }, { text: 'Save', icon: 'E216', @@ -93,38 +109,38 @@ class Menu extends Container { type: 'menu', text: 'Export', icon: 'E225', - menuPanel: exportMenuPanel, + subMenu: exportMenuPanel, isEnabled: () => !events.invoke('scene.empty'), }]); const selectionMenuPanel = new MenuPanel([{ text: 'All', icon: 'E0020', - shortcut: 'A', + additional: 'A', onSelect: () => events.fire('select.all') }, { text: 'None', icon: 'E0020', - shortcut: 'Shift + A', + additional: 'Shift + A', onSelect: () => events.fire('select.none') }, { text: 'Invert', icon: 'E0020', - shortcut: 'I', + additional: 'I', onSelect: () => events.fire('select.invert') }, { // separator }, { text: 'Lock Selection', - shortcut: 'H', + additional: 'H', onSelect: () => events.fire('select.hide') }, { text: 'Unlock All', - shortcut: 'U', + additional: 'U', onSelect: () => events.fire('select.unhide') }, { text: 'Delete Selection', - shortcut: 'Delete', + additional: 'Delete', onSelect: () => events.fire('select.delete') }, { text: 'Reset Splat', From 0e98ad05d943609a0cc4f9bce9f8db8985657ed6 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Thu, 1 Aug 2024 11:51:26 +0100 Subject: [PATCH 17/33] support disabled menu items --- src/svg/select-plane.svg | 3 ++ src/svg/select-sphere.svg | 3 ++ src/ui/bottom-toolbar.ts | 22 +++++++++++++- src/ui/menu-panel.scss | 11 +++++-- src/ui/menu-panel.ts | 61 +++++++++++++++++++++++++-------------- src/ui/menu.ts | 26 +++++++++-------- 6 files changed, 89 insertions(+), 37 deletions(-) create mode 100644 src/svg/select-plane.svg create mode 100644 src/svg/select-sphere.svg diff --git a/src/svg/select-plane.svg b/src/svg/select-plane.svg new file mode 100644 index 00000000..d1f875c4 --- /dev/null +++ b/src/svg/select-plane.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/select-sphere.svg b/src/svg/select-sphere.svg new file mode 100644 index 00000000..73628dd2 --- /dev/null +++ b/src/svg/select-sphere.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts index 05b1df9e..e2555484 100644 --- a/src/ui/bottom-toolbar.ts +++ b/src/ui/bottom-toolbar.ts @@ -5,8 +5,10 @@ import { Tooltips } from './tooltips'; import undoSvg from '../svg/undo.svg'; import redoSvg from '../svg/redo.svg'; import pickerSvg from '../svg/select-picker.svg'; -import lassoSvg from '../svg/select-lasso.svg'; import brushSvg from '../svg/select-brush.svg'; +import planeSvg from '../svg/select-plane.svg'; +import sphereSvg from '../svg/select-sphere.svg'; +import lassoSvg from '../svg/select-lasso.svg'; import cropSvg from '../svg/crop.svg'; const createSvg = (svgString: string) => { @@ -54,6 +56,16 @@ class BottomToolbar extends Container { class: ['bottom-toolbar-tool', 'disabled'] }); + const plane = new Button({ + id: 'bottom-toolbar-plane', + class: 'bottom-toolbar-tool' + }); + + const sphere = new Button({ + id: 'bottom-toolbar-sphere', + class: 'bottom-toolbar-tool' + }); + const crop = new Button({ id: 'bottom-toolbar-crop', class: ['bottom-toolbar-tool', 'disabled'] @@ -87,6 +99,8 @@ class BottomToolbar extends Container { redo.dom.appendChild(createSvg(redoSvg)); picker.dom.appendChild(createSvg(pickerSvg)); brush.dom.appendChild(createSvg(brushSvg)); + plane.dom.appendChild(createSvg(planeSvg)); + sphere.dom.appendChild(createSvg(sphereSvg)); lasso.dom.appendChild(createSvg(lassoSvg)); crop.dom.appendChild(createSvg(cropSvg)); @@ -96,6 +110,9 @@ class BottomToolbar extends Container { this.append(picker); this.append(brush); this.append(lasso); + this.append(new Element({ class: 'bottom-toolbar-separator' })); + this.append(plane); + this.append(sphere); this.append(crop); this.append(new Element({ class: 'bottom-toolbar-separator' })); this.append(translate); @@ -118,6 +135,7 @@ class BottomToolbar extends Container { events.on('tool.activated', (toolName: string) => { picker.class[toolName === 'rectSelection' ? 'add' : 'remove']('active'); brush.class[toolName === 'brushSelection' ? 'add' : 'remove']('active'); + plane.class[toolName === 'planeSelection' ? 'add' : 'remove']('active'); translate.class[toolName === 'move' ? 'add' : 'remove']('active'); rotate.class[toolName === 'rotate' ? 'add' : 'remove']('active'); scale.class[toolName === 'scale' ? 'add' : 'remove']('active'); @@ -133,6 +151,8 @@ class BottomToolbar extends Container { tooltips.register(picker, 'Select Picker'); tooltips.register(brush, 'Select Brush'); tooltips.register(lasso, 'Select Lasso'); + tooltips.register(plane, 'Plane Selector'); + tooltips.register(sphere, 'Sphere Selector'); tooltips.register(crop, 'Crop'); tooltips.register(translate, 'Translate'); tooltips.register(rotate, 'Rotate'); diff --git a/src/ui/menu-panel.scss b/src/ui/menu-panel.scss index 3370173c..fccc3f9f 100644 --- a/src/ui/menu-panel.scss +++ b/src/ui/menu-panel.scss @@ -11,8 +11,6 @@ background-color: $bcg-dark; filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.8)); - - cursor: pointer; } .menu-row { @@ -23,14 +21,21 @@ height: 32px; padding: 0px 8px; - &:hover { + &:hover:not(.pcui-disabled) { background-color: $bcg-darkest; + cursor: pointer; & > .menu-row-text, .menu-row-postscript, .menu-row-icon { color: $text-primary; } } + &.pcui-disabled { + & > .menu-row-text, .menu-row-postscript, .menu-row-icon { + color: $text-darkest; + } + } + // tweaks for attached boolean toggles > .pcui-boolean-input { background-color: $bcg-darkest; diff --git a/src/ui/menu-panel.ts b/src/ui/menu-panel.ts index 4cb6e8da..f4f30e3e 100644 --- a/src/ui/menu-panel.ts +++ b/src/ui/menu-panel.ts @@ -1,18 +1,14 @@ -import { Container, Element, Label } from 'pcui'; - -type MenuItemType = 'button' | 'menu' | 'separator'; +import { Container, Element, Label, Menu } from 'pcui'; type Direction = 'left' | 'right' | 'top' | 'bottom'; type MenuItem = { - type?: MenuItemType; - text?: string; icon?: string; - additional?: string | Element; + extra?: string | Element; subMenu?: MenuPanel; - onSelect?: () => void; + onSelect?: () => any; isEnabled?: () => boolean; }; @@ -50,6 +46,8 @@ const isString = (value: any) => { }; class MenuPanel extends Container { + parentPanel: MenuPanel | null = null; + constructor(menuItems: MenuItem[], args = {}) { args = { ...args, @@ -67,10 +65,19 @@ class MenuPanel extends Container { } }); + this.on('show', () => { + for (let i = 0; i < menuItems.length; i++) { + const menuItem = menuItems[i]; + if (menuItem.isEnabled) { + this.dom.children.item(i).ui.enabled = menuItem.isEnabled(); + } + } + }); + let deactivate: () => void | null = null; for (let menuItem of menuItems) { - const type = menuItem.type ?? (menuItem.subMenu ? 'menu' : menuItem.text ? 'button' : 'separator'); + const type = menuItem.subMenu ? 'menu' : menuItem.text ? 'button' : 'separator'; let row: Container | null = null; let activate: () => void | null = null; @@ -79,23 +86,11 @@ class MenuPanel extends Container { row = new Container({ class: 'menu-row' }); const icon = new Label({ class: 'menu-row-icon', text: menuItem.icon && String.fromCodePoint(parseInt(menuItem.icon, 16)) }); const text = new Label({ class: 'menu-row-text', text: menuItem.text }); - const postscript = isString(menuItem.additional) ? new Label({ class: 'menu-row-postscript', text: menuItem.additional as string }) : menuItem.additional; + const postscript = isString(menuItem.extra) ? new Label({ class: 'menu-row-postscript', text: menuItem.extra as string }) : menuItem.extra; row.append(icon); row.append(text); row.append(postscript); - row.dom.addEventListener('pointerdown', () => { - if (menuItem.onSelect) { - menuItem.onSelect(); - } - }); - - row.dom.addEventListener('pointerup', () => { - if (menuItem.onSelect) { - menuItem.onSelect(); - } - }); - break; } @@ -108,6 +103,9 @@ class MenuPanel extends Container { row.append(text); row.append(postscript); + // set parent panel + menuItem.subMenu.parentPanel = this; + const childPanel = menuItem.subMenu; if (childPanel) { activate = () => { @@ -151,11 +149,32 @@ class MenuPanel extends Container { } }); + row.dom.addEventListener('pointerdown', (event: PointerEvent) => { + event.stopPropagation(); + }); + + row.dom.addEventListener('pointerup', (event: PointerEvent) => { + event.stopPropagation(); + + if (!row.disabled && menuItem.onSelect) { + this.rootPanel.hidden = true; + menuItem.onSelect(); + } + }); + this.append(row); } } } + get rootPanel() { + let panel: MenuPanel = this; + while (panel.parentPanel) { + panel = panel.parentPanel; + } + return panel; + } + position(parent: HTMLElement, direction: Direction, padding = 2) { arrange(this.dom, parent, direction, padding); } diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 05e4e342..24fd42ed 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -54,11 +54,13 @@ class Menu extends Container { const exportMenuPanel = new MenuPanel([{ text: 'Compressed Ply', icon: 'E245', - onSelect: () => events.invoke('scene.export', 'compressed-ply') + onSelect: () => events.invoke('scene.export', 'compressed-ply'), + isEnabled: () => !events.invoke('scene.empty'), }, { text: 'Splat file', icon: 'E245', - onSelect: () => events.invoke('scene.export', 'splat') + onSelect: () => events.invoke('scene.export', 'splat'), + isEnabled: () => !events.invoke('scene.empty'), }]); const allDataToggle = new BooleanInput({ @@ -89,9 +91,11 @@ class Menu extends Container { // separator }, { text: 'Load all PLY data', - additional: allDataToggle, + extra: allDataToggle, onSelect: () => { events.fire('toggleAllData'); + // panel is hidden by default - unhide it again + sceneMenuPanel.hidden = false; } }, { // separator @@ -106,41 +110,39 @@ class Menu extends Container { onSelect: () => events.fire('scene.saveAs'), isEnabled: () => !events.invoke('scene.empty') }, { - type: 'menu', text: 'Export', icon: 'E225', - subMenu: exportMenuPanel, - isEnabled: () => !events.invoke('scene.empty'), + subMenu: exportMenuPanel }]); const selectionMenuPanel = new MenuPanel([{ text: 'All', icon: 'E0020', - additional: 'A', + extra: 'A', onSelect: () => events.fire('select.all') }, { text: 'None', icon: 'E0020', - additional: 'Shift + A', + extra: 'Shift + A', onSelect: () => events.fire('select.none') }, { text: 'Invert', icon: 'E0020', - additional: 'I', + extra: 'I', onSelect: () => events.fire('select.invert') }, { // separator }, { text: 'Lock Selection', - additional: 'H', + extra: 'H', onSelect: () => events.fire('select.hide') }, { text: 'Unlock All', - additional: 'U', + extra: 'U', onSelect: () => events.fire('select.unhide') }, { text: 'Delete Selection', - additional: 'Delete', + extra: 'Delete', onSelect: () => events.fire('select.delete') }, { text: 'Reset Splat', From 28664dc7e6b099cdb5c1943a7234a719a436065a Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Fri, 2 Aug 2024 11:33:55 +0100 Subject: [PATCH 18/33] added sphere tool --- src/main.ts | 10 ++-- src/sphere-shape.ts | 66 ++++++++++++++++++++++++++ src/svg/select-plane.svg | 3 -- src/tools/sphere-selection.ts | 87 +++++++++++++++++++++++++++++++++++ src/tools/tool-manager.ts | 2 +- src/tools/transform-tool.ts | 5 +- src/ui/bottom-toolbar.ts | 12 +---- src/ui/select-toolbar.scss | 23 +++++++++ src/ui/style.scss | 1 + 9 files changed, 187 insertions(+), 22 deletions(-) create mode 100644 src/sphere-shape.ts delete mode 100644 src/svg/select-plane.svg create mode 100644 src/tools/sphere-selection.ts create mode 100644 src/ui/select-toolbar.scss diff --git a/src/main.ts b/src/main.ts index 97f39b34..718fa1f1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,11 +8,12 @@ import { registerEditorEvents } from './editor'; import { initFileHandler } from './file-handler'; import { initSelection } from './selection'; import { ToolManager } from './tools/tool-manager'; +import { RectSelection } from './tools/rect-selection'; +import { BrushSelection } from './tools/brush-selection'; +import { SphereSelection } from './tools/sphere-selection'; import { MoveTool } from './tools/move-tool'; import { RotateTool } from './tools/rotate-tool'; import { ScaleTool } from './tools/scale-tool'; -import { RectSelection } from './tools/rect-selection'; -import { BrushSelection } from './tools/brush-selection'; import { Shortcuts } from './shortcuts'; import { Events } from './events'; @@ -142,11 +143,12 @@ const main = async () => { // tool manager const toolManager = new ToolManager(events); + toolManager.register('rectSelection', new RectSelection(events, editorUI.toolsContainer.dom)); + toolManager.register('brushSelection', new BrushSelection(events, editorUI.toolsContainer.dom)); + toolManager.register('sphereSelection', new SphereSelection(events, scene, editorUI.canvasContainer)); toolManager.register('move', new MoveTool(events, editHistory, scene)); toolManager.register('rotate', new RotateTool(events, editHistory, scene)); toolManager.register('scale', new ScaleTool(events, editHistory, scene)); - toolManager.register('rectSelection', new RectSelection(events, editorUI.toolsContainer.dom)); - toolManager.register('brushSelection', new BrushSelection(events, editorUI.toolsContainer.dom)); window.scene = scene; diff --git a/src/sphere-shape.ts b/src/sphere-shape.ts new file mode 100644 index 00000000..6b1bcd71 --- /dev/null +++ b/src/sphere-shape.ts @@ -0,0 +1,66 @@ +import { BoundingBox, Color, Entity, Vec3 } from 'playcanvas'; +import { Element, ElementType } from './element'; +import { Serializer } from './serializer'; + +const v = new Vec3(); +const bound = new BoundingBox(); + +class SphereShape extends Element { + pivot: Entity; + _radius = 1; + + constructor() { + super(ElementType.debug); + + this.pivot = new Entity('spherePivot'); + } + + add() { + this.scene.contentRoot.addChild(this.pivot); + this.updateBound(); + } + + remove() { + this.scene.contentRoot.removeChild(this.pivot); + this.scene.boundDirty = true; + } + + destroy() { + + } + + serialize(serializer: Serializer): void { + serializer.packa(this.pivot.getWorldTransform().data); + serializer.pack(this.radius); + } + + onPreRender() { + this.pivot.getWorldTransform().getTranslation(v) + this.scene.app.drawWireSphere(v, this.radius, Color.RED, 40); + } + + moved() { + this.updateBound(); + } + + updateBound() { + bound.center.copy(this.pivot.getPosition()); + bound.halfExtents.set(this.radius, this.radius, this.radius); + this.scene.boundDirty = true; + } + + get worldBound(): BoundingBox | null { + return bound; + } + + set radius(radius: number) { + this._radius = radius; + this.updateBound(); + } + + get radius() { + return this._radius; + } +}; + +export { SphereShape }; diff --git a/src/svg/select-plane.svg b/src/svg/select-plane.svg deleted file mode 100644 index d1f875c4..00000000 --- a/src/svg/select-plane.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/tools/sphere-selection.ts b/src/tools/sphere-selection.ts new file mode 100644 index 00000000..94632f10 --- /dev/null +++ b/src/tools/sphere-selection.ts @@ -0,0 +1,87 @@ +import { TranslateGizmo } from 'playcanvas'; +import { Button, Container, NumericInput } from 'pcui'; +import { Events } from '../events'; +import { Scene } from '../scene'; +import { SphereShape } from '../sphere-shape'; + +class SphereSelection { + activate: () => void; + deactivate: () => void; + + active = false; + + constructor(events: Events, scene: Scene, canvasContainer: Container) { + const sphere = new SphereShape(); + + const gizmo = new TranslateGizmo(scene.app, scene.camera.entity.camera, scene.gizmoLayer); + + gizmo.on('render:update', () => { + scene.forceRender = true; + }); + + gizmo.on('transform:move', () => { + sphere.moved(); + }); + + // ui + const selectToolbar = new Container({ + id: 'select-toolbar', + hidden: true + }); + + const setButton = new Button({ text: 'Set', class: 'select-toolbar-button' }); + const addButton = new Button({ text: 'Add', class: 'select-toolbar-button' }); + const removeButton = new Button({ text: 'Remove', class: 'select-toolbar-button' }); + const radius = new NumericInput({ + precision: 4, + value: sphere.radius, + placeholder: 'Radius' + }); + + selectToolbar.append(setButton); + selectToolbar.append(addButton); + selectToolbar.append(removeButton); + selectToolbar.append(radius); + + canvasContainer.append(selectToolbar); + + const apply = (op: 'set' | 'add' | 'remove') => { + const p = sphere.pivot.getPosition(); + events.fire('select.bySphere', op, [p.x, p.y, p.z, sphere.radius]); + }; + + setButton.dom.addEventListener('pointerdown', (e) => { e.stopPropagation(); apply('set'); }); + addButton.dom.addEventListener('pointerdown', (e) => { e.stopPropagation(); apply('add'); }); + removeButton.dom.addEventListener('pointerdown', (e) => { e.stopPropagation(); apply('remove'); }); + radius.on('change', () => { sphere.radius = radius.value; }); + + this.activate = () => { + this.active = true; + scene.add(sphere); + gizmo.attach([sphere.pivot]); + selectToolbar.hidden = false; + + const canvas = document.getElementById('canvas'); + if (canvas) { + const w = canvas.clientWidth; + const h = canvas.clientHeight; + gizmo.size = 1200 / Math.max(w, h); + + // FIXME: + // this is a temporary workaround to undo gizmo's own auto scaling. + // once gizmo's autoscaling code is removed, this line can go too. + // @ts-ignore + gizmo._deviceStartSize = Math.min(scene.app.graphicsDevice.width, scene.app.graphicsDevice.height); + } + }; + + this.deactivate = () => { + selectToolbar.hidden = true; + gizmo.detach(); + scene.remove(sphere); + this.active = false; + }; + } +} + +export { SphereSelection }; diff --git a/src/tools/tool-manager.ts b/src/tools/tool-manager.ts index 070fbe4f..c9ba00dd 100644 --- a/src/tools/tool-manager.ts +++ b/src/tools/tool-manager.ts @@ -32,7 +32,7 @@ class ToolManager { events.function('tool.coordSpace', () => { return coordSpace; - }) + }); events.on('tool.setCoordSpace', (value: 'local' | 'world') => { setCoordSpace(value); diff --git a/src/tools/transform-tool.ts b/src/tools/transform-tool.ts index b30aaa6c..683d6a93 100644 --- a/src/tools/transform-tool.ts +++ b/src/tools/transform-tool.ts @@ -147,11 +147,8 @@ class TransformTool { return; } - this.splats = [selection]; + this.splats = [ selection ]; this.gizmo.attach(this.splats.map((splats) => splats.pivot)); - - // @ts-ignore - temporary workaround for gizmo size bug, to be removed once https://github.com/playcanvas/engine/issues/6671 is fixed. - this.gizmo._deviceStartSize = Math.min(this.gizmo._device.width, this.gizmo._device.height); } activate() { diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts index e2555484..81382283 100644 --- a/src/ui/bottom-toolbar.ts +++ b/src/ui/bottom-toolbar.ts @@ -6,7 +6,6 @@ import undoSvg from '../svg/undo.svg'; import redoSvg from '../svg/redo.svg'; import pickerSvg from '../svg/select-picker.svg'; import brushSvg from '../svg/select-brush.svg'; -import planeSvg from '../svg/select-plane.svg'; import sphereSvg from '../svg/select-sphere.svg'; import lassoSvg from '../svg/select-lasso.svg'; import cropSvg from '../svg/crop.svg'; @@ -56,11 +55,6 @@ class BottomToolbar extends Container { class: ['bottom-toolbar-tool', 'disabled'] }); - const plane = new Button({ - id: 'bottom-toolbar-plane', - class: 'bottom-toolbar-tool' - }); - const sphere = new Button({ id: 'bottom-toolbar-sphere', class: 'bottom-toolbar-tool' @@ -99,7 +93,6 @@ class BottomToolbar extends Container { redo.dom.appendChild(createSvg(redoSvg)); picker.dom.appendChild(createSvg(pickerSvg)); brush.dom.appendChild(createSvg(brushSvg)); - plane.dom.appendChild(createSvg(planeSvg)); sphere.dom.appendChild(createSvg(sphereSvg)); lasso.dom.appendChild(createSvg(lassoSvg)); crop.dom.appendChild(createSvg(cropSvg)); @@ -111,7 +104,6 @@ class BottomToolbar extends Container { this.append(brush); this.append(lasso); this.append(new Element({ class: 'bottom-toolbar-separator' })); - this.append(plane); this.append(sphere); this.append(crop); this.append(new Element({ class: 'bottom-toolbar-separator' })); @@ -124,6 +116,7 @@ class BottomToolbar extends Container { redo.dom.addEventListener('click', () => events.fire('edit.redo')); brush.dom.addEventListener('click', () => events.fire('tool.brushSelection')); picker.dom.addEventListener('click', () => events.fire('tool.rectSelection')); + sphere.dom.addEventListener('click', () => events.fire('tool.sphereSelection')); translate.dom.addEventListener('click', () => events.fire('tool.move')); rotate.dom.addEventListener('click', () => events.fire('tool.rotate')); scale.dom.addEventListener('click', () => events.fire('tool.scale')); @@ -135,7 +128,7 @@ class BottomToolbar extends Container { events.on('tool.activated', (toolName: string) => { picker.class[toolName === 'rectSelection' ? 'add' : 'remove']('active'); brush.class[toolName === 'brushSelection' ? 'add' : 'remove']('active'); - plane.class[toolName === 'planeSelection' ? 'add' : 'remove']('active'); + sphere.class[toolName === 'sphereSelection' ? 'add' : 'remove']('active'); translate.class[toolName === 'move' ? 'add' : 'remove']('active'); rotate.class[toolName === 'rotate' ? 'add' : 'remove']('active'); scale.class[toolName === 'scale' ? 'add' : 'remove']('active'); @@ -151,7 +144,6 @@ class BottomToolbar extends Container { tooltips.register(picker, 'Select Picker'); tooltips.register(brush, 'Select Brush'); tooltips.register(lasso, 'Select Lasso'); - tooltips.register(plane, 'Plane Selector'); tooltips.register(sphere, 'Sphere Selector'); tooltips.register(crop, 'Crop'); tooltips.register(translate, 'Translate'); diff --git a/src/ui/select-toolbar.scss b/src/ui/select-toolbar.scss new file mode 100644 index 00000000..ad12d949 --- /dev/null +++ b/src/ui/select-toolbar.scss @@ -0,0 +1,23 @@ +#select-toolbar { + position: absolute; + left: 50%; + bottom: 100px; + height: 54px; + transform: translate(-50%, 0); + padding: 0px 8px; + border-radius: 8px; + + background-color: $bcg-primary; + + &:not(.pcui-hidden) { + display: flex; + } + flex-direction: row; + align-items: center; + + .select-toolbar-button { + height: 38px; + padding: 0px 16px; + border-radius: 2px; + } +} \ No newline at end of file diff --git a/src/ui/style.scss b/src/ui/style.scss index d291ba08..8502f1f7 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -18,6 +18,7 @@ $bcg-darkest: #181818; @import 'transform.scss'; @import 'bottom-toolbar.scss'; @import 'right-toolbar.scss'; +@import 'select-toolbar.scss'; * { font-size: 12px; From 65cb584e447c4a69a789dbafedbd5cf3e53a78c9 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Fri, 2 Aug 2024 11:53:16 +0100 Subject: [PATCH 19/33] remove control panel --- src/editor.ts | 115 ++++++++------- src/tools/sphere-selection.ts | 8 +- src/ui/bottom-toolbar.ts | 10 +- src/ui/control-panel.ts | 270 ---------------------------------- src/ui/editor.ts | 7 - src/ui/style.scss | 54 ------- 6 files changed, 76 insertions(+), 388 deletions(-) delete mode 100644 src/ui/control-panel.ts diff --git a/src/editor.ts b/src/editor.ts index 59f1e432..6a8758bf 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -1,6 +1,5 @@ import { BoundingBox, - Color, Mat4, Vec3, Vec4, @@ -30,41 +29,6 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S return selected?.visible ? [selected] : []; }; - const debugSphereCenter = new Vec3(); - let debugSphereRadius = 0; - - const debugPlane = new Vec3(); - let debugPlaneDistance = 0; - - // draw debug mesh instances - events.on('prerender', () => { - const app = scene.app; - - if (debugSphereRadius > 0) { - app.drawWireSphere(debugSphereCenter, debugSphereRadius, Color.RED, 40); - } - - if (debugPlane.length() > 0) { - vec.copy(debugPlane).mulScalar(debugPlaneDistance); - vec2.add2(vec, debugPlane); - - mat.setLookAt(vec, vec2, Math.abs(Vec3.UP.dot(debugPlane)) > 0.99 ? Vec3.FORWARD : Vec3.UP); - - const lines = [ - new Vec3(-1,-1, 0), new Vec3( 1,-1, 0), - new Vec3( 1,-1, 0), new Vec3( 1, 1, 0), - new Vec3( 1, 1, 0), new Vec3(-1, 1, 0), - new Vec3(-1, 1, 0), new Vec3(-1,-1, 0), - new Vec3( 0, 0, 0), new Vec3( 0, 0,-1) - ]; - for (let i = 0; i < lines.length; ++i) { - mat.transformPoint(lines[i], lines[i]); - } - - app.drawLines(lines, Color.RED); - } - }); - const processSelection = (state: Uint8Array, op: string, pred: (i: number) => boolean) => { for (let i = 0; i < state.length; ++i) { if (state[i] & (State.deleted | State.hidden)) { @@ -219,20 +183,6 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S }); }); - events.on('select.bySpherePlacement', (sphere: number[]) => { - debugSphereCenter.set(sphere[0], sphere[1], sphere[2]); - debugSphereRadius = sphere[3]; - - scene.forceRender = true; - }); - - events.on('select.byPlanePlacement', (axis: number[], distance: number) => { - debugPlane.set(axis[0], axis[1], axis[2]); - debugPlaneDistance = distance; - - scene.forceRender = true; - }); - events.on('select.bySphere', (op: string, sphere: number[]) => { selectedSplats().forEach((splat) => { const splatData = splat.splatData; @@ -503,6 +453,71 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S events.on('toggleAllData', (value: boolean) => { setAllData(!events.invoke('allData')); }); + + // camera mode + + let activeMode = 'centers'; + + const setCameraMode = (mode: string) => { + if (mode !== activeMode) { + activeMode = mode; + events.fire('camera.mode', activeMode); + } + }; + + events.function('camera.mode', () => { + return activeMode; + }); + + events.on('camera.setMode', (mode: string) => { + setCameraMode(mode); + }); + + events.on('camera.toggleMode', () => { + setCameraMode(events.invoke('camera.mode') === 'centers' ? 'rings' : 'centers'); + }); + + // camera debug + + let cameraDebug = true; + + const setCameraDebug = (enabled: boolean) => { + if (enabled !== cameraDebug) { + cameraDebug = enabled; + events.fire('camera.debug', cameraDebug); + } + }; + + events.function('camera.debug', () => { + return cameraDebug; + }); + + events.on('camera.setDebug', (value: boolean) => { + setCameraDebug(value); + }); + + events.on('camera.toggleDebug', () => { + setCameraDebug(!events.invoke('camera.debug')); + }); + + // splat size + + let splatSize = 2; + + const setSplatSize = (value: number) => { + if (value !== splatSize) { + splatSize = value; + events.fire('camera.splatSize', splatSize); + } + }; + + events.function('camera.splatSize', () => { + return splatSize; + }); + + events.on('camera.setSplatSize', (value: number) => { + setSplatSize(value); + }); } export { registerEditorEvents }; diff --git a/src/tools/sphere-selection.ts b/src/tools/sphere-selection.ts index 94632f10..9cb1130e 100644 --- a/src/tools/sphere-selection.ts +++ b/src/tools/sphere-selection.ts @@ -29,13 +29,17 @@ class SphereSelection { hidden: true }); + selectToolbar.dom.addEventListener('pointerdown', (e) => { e.stopPropagation(); }); + const setButton = new Button({ text: 'Set', class: 'select-toolbar-button' }); const addButton = new Button({ text: 'Add', class: 'select-toolbar-button' }); const removeButton = new Button({ text: 'Remove', class: 'select-toolbar-button' }); const radius = new NumericInput({ - precision: 4, + precision: 2, value: sphere.radius, - placeholder: 'Radius' + placeholder: 'Radius', + width: 80, + min: 0.01 }); selectToolbar.append(setButton); diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts index 81382283..703722ff 100644 --- a/src/ui/bottom-toolbar.ts +++ b/src/ui/bottom-toolbar.ts @@ -141,15 +141,15 @@ class BottomToolbar extends Container { // register tooltips tooltips.register(undo, 'Undo'); tooltips.register(redo, 'Redo'); - tooltips.register(picker, 'Select Picker'); - tooltips.register(brush, 'Select Brush'); - tooltips.register(lasso, 'Select Lasso'); - tooltips.register(sphere, 'Sphere Selector'); + tooltips.register(picker, 'Picker Select'); + tooltips.register(brush, 'Brush Select'); + tooltips.register(lasso, 'Lasso Select'); + tooltips.register(sphere, 'Sphere Select'); tooltips.register(crop, 'Crop'); tooltips.register(translate, 'Translate'); tooltips.register(rotate, 'Rotate'); tooltips.register(scale, 'Scale'); - tooltips.register(coordSpace, 'Enable Local Space'); + tooltips.register(coordSpace, 'Local Space Gizmo'); } } diff --git a/src/ui/control-panel.ts b/src/ui/control-panel.ts deleted file mode 100644 index 473f5fd3..00000000 --- a/src/ui/control-panel.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { BooleanInput, Button, Container, Label, NumericInput, Panel, RadioButton, SelectInput, SliderInput, VectorInput } from 'pcui'; -import { Events } from '../events'; -import { version as appVersion } from '../../package.json'; - -class ControlPanel extends Panel { - constructor(events: Events, remoteStorageMode: boolean, args = { }) { - args = { - ...args, - headerText: `SUPERSPLAT v${appVersion}`, - id: 'control-panel', - resizable: 'right', - resizeMax: 1000, - collapsible: true, - collapseHorizontally: true, - scrollable: true - }; - - super(args); - - // selection panel - const selectionPanel = new Panel({ - id: 'selection-panel', - class: 'control-panel', - headerText: 'SELECTION' - }); - - // select by sphere - const selectBySphere = new Container({ - class: 'control-parent' - }); - - const selectBySphereRadio = new RadioButton({ - class: 'control-element' - }); - - const selectBySphereLabel = new Label({ - class: 'control-label', - text: 'Sphere' - }); - - const selectBySphereCenter = new VectorInput({ - class: 'control-element-expand', - precision: 4, - dimensions: 4, - value: [0, 0, 0, 0.5], - // @ts-ignore - placeholder: ['X', 'Y', 'Z', 'R'], - enabled: false - }); - - selectBySphere.append(selectBySphereRadio); - selectBySphere.append(selectBySphereLabel); - selectBySphere.append(selectBySphereCenter); - - // select by plane - const selectByPlane = new Container({ - class: 'control-parent' - }); - - const selectByPlaneRadio = new RadioButton({ - class: 'control-element' - }); - - const selectByPlaneLabel = new Label({ - class: 'control-label', - text: 'Plane' - }); - - const selectByPlaneAxis = new SelectInput({ - class: 'control-element', - defaultValue: 'y', - options: [ - { v: 'x', t: 'x' }, - { v: 'y', t: 'y' }, - { v: 'z', t: 'z' } - ], - enabled: false - }); - - const selectByPlaneOffset = new NumericInput({ - class: 'control-element-expand', - precision: 2, - enabled: false - }); - - selectByPlane.append(selectByPlaneRadio); - selectByPlane.append(selectByPlaneLabel); - selectByPlane.append(selectByPlaneAxis); - selectByPlane.append(selectByPlaneOffset); - - // set/add/remove - const setAddRemove = new Container({ - class: 'control-parent' - }); - - const setButton = new Button({ - class: 'control-element-expand', - text: 'Set', - enabled: false - }); - - const addButton = new Button({ - class: 'control-element-expand', - text: 'Add', - enabled: false - }); - - const removeButton = new Button({ - class: 'control-element-expand', - text: 'Remove', - enabled: false - }); - - setAddRemove.append(setButton); - setAddRemove.append(addButton); - setAddRemove.append(removeButton); - - selectionPanel.append(selectBySphere); - selectionPanel.append(selectByPlane); - selectionPanel.append(setAddRemove); - - const controlsContainer = new Container({ - id: 'control-panel-controls' - }); - - controlsContainer.append(selectionPanel); - - // append - this.content.append(controlsContainer); - - // radio logic - const radioGroup = [selectBySphereRadio, selectByPlaneRadio]; - radioGroup.forEach((radio, index) => { - radio.on('change', () => { - if (radio.value) { - radioGroup.forEach((other) => { - if (other !== radio) { - other.value = false; - } - }); - - // update select by - events.fire('selectBy', index); - } else { - // update select by - events.fire('selectBy', null); - } - }); - }); - - const axes: any = { - x: [1, 0, 0], - y: [0, 1, 0], - z: [0, 0, 1] - }; - - let radioSelection: number | null = null; - events.on('selectBy', (index: number | null) => { - radioSelection = index; - - setButton.enabled = index !== null; - addButton.enabled = index !== null; - removeButton.enabled = index !== null; - - const controlSet = [ - [selectBySphereCenter], - [selectByPlaneAxis, selectByPlaneOffset] - ]; - - controlSet.forEach((controls, controlsIndex) => { - controls.forEach((control) => { - control.enabled = index === controlsIndex; - }); - }); - - events.fire('select.bySpherePlacement', index === 0 ? selectBySphereCenter.value : [0, 0, 0, 0]); - events.fire('select.byPlanePlacement', index === 1 ? axes[selectByPlaneAxis.value] : [0, 0, 0], selectByPlaneOffset.value); - }); - - const performSelect = (op: string) => { - switch (radioSelection) { - case 0: events.fire('select.bySphere', op, selectBySphereCenter.value); break; - case 1: events.fire('select.byPlane', op, axes[selectByPlaneAxis.value], selectByPlaneOffset.value); break; - } - }; - - setButton.on('click', () => performSelect('set')); - addButton.on('click', () => performSelect('add')); - removeButton.on('click', () => performSelect('remove')); - - // camera mode - - let activeMode = 'centers'; - - const setCameraMode = (mode: string) => { - if (mode !== activeMode) { - activeMode = mode; - events.fire('camera.mode', activeMode); - } - }; - - events.function('camera.mode', () => { - return activeMode; - }); - - events.on('camera.setMode', (mode: string) => { - setCameraMode(mode); - }); - - events.on('camera.toggleMode', () => { - setCameraMode(events.invoke('camera.mode') === 'centers' ? 'rings' : 'centers'); - }); - - // camera debug - - let cameraDebug = true; - - const setCameraDebug = (enabled: boolean) => { - if (enabled !== cameraDebug) { - cameraDebug = enabled; - events.fire('camera.debug', cameraDebug); - } - }; - - events.function('camera.debug', () => { - return cameraDebug; - }); - - events.on('camera.setDebug', (value: boolean) => { - setCameraDebug(value); - }); - - events.on('camera.toggleDebug', () => { - setCameraDebug(!events.invoke('camera.debug')); - }); - - // splat size - - let splatSize = 2; - - const setSplatSize = (value: number) => { - if (value !== splatSize) { - splatSize = value; - events.fire('camera.splatSize', splatSize); - } - }; - - events.function('camera.splatSize', () => { - return splatSize; - }); - - events.on('camera.setSplatSize', (value: number) => { - setSplatSize(value); - }); - - selectBySphereCenter.on('change', () => { - events.fire('select.bySpherePlacement', selectBySphereCenter.value); - }); - - selectByPlaneAxis.on('change', () => { - events.fire('select.byPlanePlacement', axes[selectByPlaneAxis.value], selectByPlaneOffset.value); - }); - - selectByPlaneOffset.on('change', () => { - events.fire('select.byPlanePlacement', axes[selectByPlaneAxis.value], selectByPlaneOffset.value); - }); - } -} - -export { ControlPanel }; diff --git a/src/ui/editor.ts b/src/ui/editor.ts index 250f8a7e..6e818929 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -1,6 +1,5 @@ import { Container, Label } from 'pcui'; import { Mat4 } from 'playcanvas'; -import { ControlPanel } from './control-panel'; import { DataPanel } from './data-panel'; import { Events } from '../events'; import { Popup } from './popup'; @@ -19,7 +18,6 @@ import logo from './playcanvas-logo.png'; class EditorUI { appContainer: Container; topContainer: Container; - controlPanel: ControlPanel; canvasContainer: Container; toolsContainer: Container; canvas: HTMLCanvasElement; @@ -108,9 +106,6 @@ class EditorUI { viewCube.update(cameraMatrix); }); - // control panel - const controlPanel = new ControlPanel(events, remoteStorageMode); - // main container const mainContainer = new Container({ id: 'main-container' @@ -121,7 +116,6 @@ class EditorUI { mainContainer.append(canvasContainer); mainContainer.append(dataPanel); - editorContainer.append(controlPanel); editorContainer.append(mainContainer); // message popup @@ -137,7 +131,6 @@ class EditorUI { this.appContainer = appContainer; this.topContainer = topContainer; - this.controlPanel = controlPanel; this.canvasContainer = canvasContainer; this.toolsContainer = toolsContainer; this.canvas = canvas; diff --git a/src/ui/style.scss b/src/ui/style.scss index 8502f1f7..c0f0730c 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -115,10 +115,6 @@ body { left: 0; } -#histogram-rect { - // fill: rgba() -} - #data-panel-popup-container { position: absolute; left: 50px; @@ -136,38 +132,6 @@ body { color: #f60; } -#control-panel { - width: 340px; - height: 100%; - border-right: 1px solid #20292b; - display: flex; - flex-direction: column; - flex-grow: 0; - flex-shrink: 0; -} - -#control-panel-controls { - display: flex; - flex-direction: column; - flex-grow: 1; - flex-shrink: 1; - - overflow-y: auto; -} - -.pcui-panel-header-title::before { - font-family: pc-icon; - margin-right: 10px; - color: $text-secondary; - - #show-panel & { content: '\E117'; }; - #selection-panel & { content: '\E398'; }; - #modify-panel & { content: '\E130'; }; - #options-panel & { content: '\E134'; }; - #export-panel & { content: '\E226'; }; - #keyboard-panel & { content: '\E136'}; -} - #file-selector { display: none; } @@ -207,24 +171,6 @@ body { text-shadow: 1px 1px 4px black; } -#control-panel > .pcui-panel-header { - background-color: $bcg-dark; -} - -#control-panel > .pcui-panel-content { - display: flex; - flex-direction: column; -} - -#modify-panel > .pcui-panel-content { - display: flex; - flex-direction: column; -} - -.control-panel { - flex-shrink: 0; -} - .control-parent { width: 100%; display: flex; From 3a1dae4513fc8115ca2492facc71f7cb27b9e543 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Mon, 5 Aug 2024 10:01:18 +0100 Subject: [PATCH 20/33] fix popup stying, support loading multiple ply --- src/file-handler.ts | 38 +++++++---- src/scene.ts | 6 +- src/ui/data-panel.scss | 75 ++++++++++++++++++++++ src/ui/data-panel.ts | 6 +- src/ui/editor.ts | 18 ++---- src/ui/popup.scss | 90 ++++++++++++++++++++++++++ src/ui/popup.ts | 86 +++++++++++++------------ src/ui/splat-list.ts | 3 +- src/ui/style.scss | 142 +---------------------------------------- 9 files changed, 254 insertions(+), 210 deletions(-) create mode 100644 src/ui/data-panel.scss create mode 100644 src/ui/popup.scss diff --git a/src/file-handler.ts b/src/file-handler.ts index dacdf5c3..ddc9f896 100644 --- a/src/file-handler.ts +++ b/src/file-handler.ts @@ -96,10 +96,12 @@ const initFileHandler = async (scene: Scene, events: Events, dropTarget: HTMLEle fileSelector.setAttribute('id', 'file-selector'); fileSelector.setAttribute('type', 'file'); fileSelector.setAttribute('accept', '.ply'); + fileSelector.setAttribute('multiple', 'true'); + fileSelector.onchange = async () => { const files = fileSelector.files; - if (files.length > 0) { - const file = fileSelector.files[0]; + for (let i = 0; i < files.length; i++) { + const file = fileSelector.files[i]; const url = URL.createObjectURL(file); await scene.loadModel(url, file.name); URL.revokeObjectURL(url); @@ -132,7 +134,8 @@ const initFileHandler = async (scene: Scene, events: Events, dropTarget: HTMLEle if (events.invoke('scene.dirty')) { const result = await events.invoke('showPopup', { type: 'yesno', - message: `You have unsaved changed. Are you sure you want to clear the scene?` + header: 'RESET SCENE', + message: `You have unsaved changes. Are you sure you want to reset the scene?` }); if (result.action !== 'yes') { @@ -150,16 +153,22 @@ const initFileHandler = async (scene: Scene, events: Events, dropTarget: HTMLEle fileSelector.click(); } else { try { - const handle = (await window.showOpenFilePicker({ + const handles = await window.showOpenFilePicker({ id: 'SuperSplatFileOpen', + multiple: true, types: filePickerTypes.ply as FilePickerAcceptType[] - }))[0]; - const file = await handle.getFile(); - const url = URL.createObjectURL(file); - await scene.loadModel(url, file.name); - URL.revokeObjectURL(url); - - fileHandle = handle; + }); + for (let i = 0; i < handles.length; i++) { + const handle = handles[i]; + const file = await handle.getFile(); + const url = URL.createObjectURL(file); + await scene.loadModel(url, file.name); + URL.revokeObjectURL(url); + + if (i === 0) { + fileHandle = handle; + } + } } catch (error) { if (error.name !== 'AbortError') { console.error(error); @@ -209,12 +218,12 @@ const initFileHandler = async (scene: Scene, events: Events, dropTarget: HTMLEle } } } else { - await events.invoke('scene.export', 'ply', splat.filename); + await events.invoke('scene.export', 'ply', splat.filename, 'saveAs'); events.fire('scene.saved'); } }); - events.function('scene.export', async (type: ExportType, outputFilename: string = null) => { + events.function('scene.export', async (type: ExportType, outputFilename: string = null, exportType: 'export' | 'saveAs' = 'export') => { const extensions = { 'ply': '.ply', 'compressed-ply': '.compressed.ply', @@ -251,7 +260,8 @@ const initFileHandler = async (scene: Scene, events: Events, dropTarget: HTMLEle } else { const result = await events.invoke('showPopup', { type: 'okcancel', - message: 'Enter filename:', + header: exportType === 'saveAs' ? 'SAVE AS' : 'EXPORT', + message: 'Please enter a filename', value: filename }); diff --git a/src/scene.ts b/src/scene.ts index 6036fb19..d4e6604c 100644 --- a/src/scene.ts +++ b/src/scene.ts @@ -215,7 +215,11 @@ class Scene { this.camera.focus(); this.events.fire('loaded', filename); } catch (err) { - this.events.fire('error', err); + this.events.invoke('showPopup', { + type: 'error', + header: 'ERROR LOADING FILE', + message: `${err.message} while loading '${filename}'` + }); } } diff --git a/src/ui/data-panel.scss b/src/ui/data-panel.scss new file mode 100644 index 00000000..e762d8d0 --- /dev/null +++ b/src/ui/data-panel.scss @@ -0,0 +1,75 @@ +#data-panel { + width: 100%; + height: 320px; +} + +#data-panel-popup-container { + position: absolute; + left: 50px; + top: 50px; + pointer-events: none; +} + +#data-panel-popup-label { + background-color: $bcg-dark; + color: $text-primary; +} + +#data-controls-container { + width: 256px; + flex-grow: 0; + flex-shrink: 0; + overflow-y: auto; +} + +#data-controls { + width: 100%; +} + +.control-parent { + width: 100%; + display: flex; + flex-direction: row; + flex-grow: 1; +} + +.control-label { + width: 100px; + flex-shrink: 0; + flex-grow: 0; + line-height: 32px; + margin: 0px 6px 0px 6px; +} + +.control-element { + flex-shrink: 0; + flex-grow: 0; +} + +.control-element-expand { + flex-grow: 1; +} + +.control-element.pcui-boolean-input { + margin-top: 10px; +} + +#histogram-container { + flex-grow: 1; + flex-shrink: 1; +} + +#histogram-canvas { + image-rendering: pixelated; +} + +#histogram-svg { + pointer-events: none; + position: absolute; + width: 100%; + height: 100%; + top: 0; + right: 0; + bottom: 0; + left: 0; +} diff --git a/src/ui/data-panel.ts b/src/ui/data-panel.ts index 29231566..bd8b32b4 100644 --- a/src/ui/data-panel.ts +++ b/src/ui/data-panel.ts @@ -42,7 +42,7 @@ const sepLabel = (labelText: string) => { container.class.add('sep-container'); const label = new Label({ - class: 'contol-element-expand', + class: 'control-element-expand', text: labelText }); @@ -78,7 +78,7 @@ class DataPanel extends Panel { constructor(events: Events, args = { }) { args = { ...args, - headerText: 'DATA', + headerText: 'SPLAT DATA', id: 'data-panel', resizable: 'top', resizeMax: 1000, @@ -163,8 +163,8 @@ class DataPanel extends Panel { histogramContainer.dom.appendChild(histogram.canvas); - this.content.append(histogramContainer); this.content.append(controlsContainer); + this.content.append(histogramContainer); // current splat let splat: Splat; diff --git a/src/ui/editor.ts b/src/ui/editor.ts index 6e818929..b6238d35 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -2,7 +2,7 @@ import { Container, Label } from 'pcui'; import { Mat4 } from 'playcanvas'; import { DataPanel } from './data-panel'; import { Events } from '../events'; -import { Popup } from './popup'; +import { Popup, ShowOptions } from './popup'; import { ViewCube } from './view-cube'; import { Menu } from './menu'; import { ScenePanel } from './scene-panel'; @@ -119,7 +119,8 @@ class EditorUI { editorContainer.append(mainContainer); // message popup - this.popup = new Popup(topContainer); + const popup = new Popup(topContainer); + topContainer.append(popup); // shortcuts popup const shortcutsPopup = new ShortcutsPopup(); @@ -134,6 +135,7 @@ class EditorUI { this.canvasContainer = canvasContainer; this.toolsContainer = toolsContainer; this.canvas = canvas; + this.popup = popup; document.body.appendChild(appContainer.dom); document.body.setAttribute('tabIndex', '-1'); @@ -142,16 +144,8 @@ class EditorUI { shortcutsPopup.hidden = false; }); - events.function('showPopup', (options: { type: 'error' | 'info' | 'yesno' | 'okcancel', message: string, value: string}) => { - return this.popup.show(options.type, options.message, options.value); - }); - - events.function('error', (err: any) => { - return this.popup.show('error', err); - }); - - events.function('info', (info: string) => { - return this.popup.show('info', info); + events.function('showPopup', (options: ShowOptions) => { + return this.popup.show(options); }); // initialize canvas to correct size before creating graphics device etc diff --git a/src/ui/popup.scss b/src/ui/popup.scss new file mode 100644 index 00000000..f5b43c15 --- /dev/null +++ b/src/ui/popup.scss @@ -0,0 +1,90 @@ +#popup { + width: 100%; + height: 100%; + + // background-color: rgba(0, 0, 0, 0.5); + pointer-events: all; + + #popup-dialog { + position: absolute; + left: 50%; + top: 50%; + width: 320px; + transform: translate(-50%, -50%); + + display: flex; + flex-direction: column; + overflow: hidden; + + border-radius: 8px; + background-color: $bcg-primary; + + filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.8)); + + #popup-header { + height: 32px; + line-height: 32px; + margin: 0px; + padding: 0px 8px; + + font-weight: bold; + color: $text-primary; + background-color: $bcg-darker; + } + + #popup-text { + text-wrap: wrap; + text-align: center; + padding: 20px 0px; + + color: $text-secondary; + + &::before { + font-family: 'pc-icon'; + margin: 0px 10px; + } + + &.error::before { + content: '\E218'; + color: $error; + } + + &.info::before { + content: '\E400'; + color: $text-primary; + } + + &.yesno::before { + content: '\E138'; + color: $text-primary; + } + + &.okcancel::before { + content: '\E138'; + color: $text-primary; + } + } + + #popup-text-input { + margin-bottom: 24px; + } + + #popup-buttons { + display: flex; + flex-direction: row; + justify-content: center; + margin: 6px; + + .popup-button { + height: 40px; + width: 120px; + border-radius: 4px; + background-color: $bcg-darker; + + &:hover { + background-color: $clr-hilight; + } + } + } + } +} diff --git a/src/ui/popup.ts b/src/ui/popup.ts index ca7676e9..65c8b6d7 100644 --- a/src/ui/popup.ts +++ b/src/ui/popup.ts @@ -1,11 +1,34 @@ import { Button, Container, Label, TextInput } from 'pcui'; -class Popup { - show: (type: 'error' | 'info' | 'yesno' | 'okcancel', message: string, value?: string) => void; +interface ShowOptions { + type: 'error' | 'info' | 'yesno' | 'okcancel'; + message: string; + header?: string; + value?: string; +} + +class Popup extends Container { + show: (options: ShowOptions) => void; hide: () => void; destroy: () => void; - constructor(parent: Container) { + constructor(args = {}) { + args = { + id: 'popup', + hidden: true, + tabIndex: -1, + ...args + }; + + super(args); + + const dialog = new Container({ + id: 'popup-dialog' + }); + + const header = new Label({ + id: 'popup-header' + }); const text = new Label({ id: 'popup-text' @@ -44,23 +67,12 @@ class Popup { buttons.append(yesButton); buttons.append(noButton); - const background = new Container({ - id: 'popup-background' - }); - - background.append(text); - background.append(inputValue); - background.append(buttons); - - const container = new Container({ - id: 'popup', - hidden: true, - tabIndex: -1 - }); - - container.append(background); + dialog.append(header); + dialog.append(text); + dialog.append(inputValue); + dialog.append(buttons); - parent.append(container); + this.append(dialog); let okFn: () => void; let cancelFn: () => void; @@ -84,36 +96,30 @@ class Popup { noFn(); }); - container.on('click', () => { + this.on('click', () => { containerFn(); }); - background.on('click', (event) => { + dialog.on('click', (event) => { event.stopPropagation(); }); - this.show = async (type: 'error' | 'info' | 'yesno' | 'okcancel', message: string, value?: string) => { - // set the message - text.text = message; - - if (type === 'error') { - text.class.add('error'); - text.class.remove('info'); - } else if (type === 'info') { - text.class.remove('error'); - text.class.add('info'); - } else { - text.class.remove('error'); - text.class.remove('info'); - } + this.show = async (options: ShowOptions) => { + header.text = options.header; + text.text = options.message; + const { type, value } = options; + + ['error', 'info', 'yesno', 'okcancel'].forEach((t) => { + text.class[t === type ? 'add' : 'remove'](t); + }); // configure based on message type okButton.hidden = type === 'yesno'; cancelButton.hidden = type !== 'okcancel'; yesButton.hidden = type !== 'yesno'; noButton.hidden = type !== 'yesno'; - container.hidden = false; + this.hidden = false; inputValue.hidden = value === undefined; if (value !== undefined) { @@ -122,7 +128,7 @@ class Popup { } // take keyboard focus so shortcuts stop working - container.dom.focus(); + this.dom.focus(); return new Promise<{action: string, value?: string}>((resolve) => { okFn = () => { @@ -153,14 +159,14 @@ class Popup { }; this.hide = () => { - container.hidden = true; + this.hidden = true; }; this.destroy = () => { this.hide(); - container.destroy(); + this.destroy(); }; } } -export { Popup }; +export { Popup, ShowOptions }; diff --git a/src/ui/splat-list.ts b/src/ui/splat-list.ts index 50bbd060..cd0f85fe 100644 --- a/src/ui/splat-list.ts +++ b/src/ui/splat-list.ts @@ -182,7 +182,8 @@ class SplatList extends Container { const result = await events.invoke('showPopup', { type: 'yesno', - message: `Would you like to remove '${splat.filename}' from the scene?` + header: 'Remove Splat', + message: `Are you sure you want to remove '${splat.filename}' from the scene? This operation can not be undone.` }); if (result?.action === 'yes') { diff --git a/src/ui/style.scss b/src/ui/style.scss index c0f0730c..e231c893 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -19,6 +19,8 @@ $bcg-darkest: #181818; @import 'bottom-toolbar.scss'; @import 'right-toolbar.scss'; @import 'select-toolbar.scss'; +@import 'data-panel.scss'; +@import 'popup.scss'; * { font-size: 12px; @@ -71,11 +73,6 @@ body { flex-grow: 1; } -#data-panel { - width: 100%; - height: 320px; -} - #sep-container { background-color: $bcg-darker; } @@ -84,49 +81,6 @@ body { color: white; } -#data-controls-container { - width: 256px; - flex-grow: 0; - flex-shrink: 0; - overflow-y: auto; -} - -#data-controls { - width: 100%; -} - -#histogram-container { - flex-grow: 1; - flex-shrink: 1; -} - -#histogram-canvas { - image-rendering: pixelated; -} - -#histogram-svg { - pointer-events: none; - position: absolute; - width: 100%; - height: 100%; - top: 0; - right: 0; - bottom: 0; - left: 0; -} - -#data-panel-popup-container { - position: absolute; - left: 50px; - top: 50px; - pointer-events: none; -} - -#data-panel-popup-label { - background-color: $bcg-dark; - color: $text-primary; -} - #coord-space-toggle.active { background-color: $bcg-dark !important; color: #f60; @@ -171,34 +125,6 @@ body { text-shadow: 1px 1px 4px black; } -.control-parent { - width: 100%; - display: flex; - flex-direction: row; - flex-grow: 1; -} - -.control-label { - width: 100px; - flex-shrink: 0; - flex-grow: 0; - line-height: 32px; - margin: 0px 6px 0px 6px; -} - -.control-element { - flex-shrink: 0; - flex-grow: 0; -} - -.control-element-expand { - flex-grow: 1; -} - -.control-element.pcui-boolean-input { - margin-top: 10px; -} - .select-svg { display: none; position: absolute; @@ -236,6 +162,7 @@ body { position: absolute; width: 100%; height: 100%; + cursor: crosshair; } #canvas { @@ -274,69 +201,6 @@ body { margin-right: 6px; } -//-- Popup - -#popup { - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - pointer-events: all; -} - -#popup-background { - display: flex; - flex-direction: column; - position: absolute; - left: 50%; - top: 50%; - width: 600px; - transform: translate(-50%, -50%); - background-color: $bcg-primary; - padding: 20px; - border-radius: 10px; - border: 1px solid $bcg-darker; - pointer-events: all; -} - -#popup-buttons { - display: flex; - flex-direction: row; - justify-content: center; -} - -.popup-button { - background-color: $bcg-darker; - color: white; - height: 40px; - width: 120px -} - -#popup-text { - font-size: 18px; - color: $text-primary; - text-wrap: wrap; - text-align: center; - margin: 20px 0px; -} - -#popup-text-input { - margin-bottom: 24px; -} - -#popup-text.error::before { - content: '\E218'; - font-family: 'pc-icon'; - margin: 0px 10px; - color: $error; -} - -#popup-text.info::before { - content: '\E400'; - font-family: 'pc-icon'; - margin: 0px 10px; - color: $text-primary; -} - /* scrollbar styling */ ::-webkit-scrollbar { From fd23da6efae39e619d9b399e147497ddeedcf627 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Mon, 5 Aug 2024 16:15:18 +0100 Subject: [PATCH 21/33] grid sphere shape --- package-lock.json | 4 +- package.json | 2 +- src/infinite-grid.ts | 5 +- src/sphere-shape.ts | 147 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 149 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index f34837d2..f84039fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "supersplat", - "version": "1.0.0", + "version": "1.0.0-alpha.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "supersplat", - "version": "1.0.0", + "version": "1.0.0-alpha.0", "license": "MIT", "devDependencies": { "@playcanvas/eslint-config": "^1.7.1", diff --git a/package.json b/package.json index 77fcfd16..81bdc789 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "supersplat", - "version": "1.0.0", + "version": "1.0.0-alpha.0", "author": "PlayCanvas", "homepage": "https://playcanvas.com/supersplat/editor", "description": "3D Gaussian Splat Editor", diff --git a/src/infinite-grid.ts b/src/infinite-grid.ts index 048bbe4e..acd81caf 100644 --- a/src/infinite-grid.ts +++ b/src/infinite-grid.ts @@ -7,7 +7,8 @@ import { Mat4, QuadRender, Shader, - createShaderFromCode + createShaderFromCode, + FUNC_LESSEQUAL } from 'playcanvas'; import { Element, ElementType } from './element'; import { Serializer } from './serializer'; @@ -161,7 +162,7 @@ class InfiniteGrid extends Element { shader: Shader; quadRender: QuadRender; blendState = new BlendState(false); - depthState = new DepthState(FUNC_ALWAYS, true); + depthState = new DepthState(FUNC_LESSEQUAL, true); visible = true; diff --git a/src/sphere-shape.ts b/src/sphere-shape.ts index 6b1bcd71..b0cef1e5 100644 --- a/src/sphere-shape.ts +++ b/src/sphere-shape.ts @@ -1,22 +1,157 @@ -import { BoundingBox, Color, Entity, Vec3 } from 'playcanvas'; +import { + createShaderFromCode, + CULLFACE_NONE, + BoundingBox, + Entity, + Material, + Vec3 +} from 'playcanvas'; import { Element, ElementType } from './element'; import { Serializer } from './serializer'; +const vsCode = /* glsl */ ` + attribute vec3 vertex_position; + + uniform mat4 matrix_model; + uniform mat4 matrix_viewProjection; + + varying vec3 fragWorld; + + void main() { + vec4 world = matrix_model * vec4(vertex_position, 1.0); + gl_Position = matrix_viewProjection * world; + fragWorld = world.xyz; + } +`; + +const fsCode = /* glsl */ ` + bool intersectSphere(out float t0, out float t1, vec3 pos, vec3 dir, vec4 sphere) { + vec3 L = sphere.xyz - pos; + float tca = dot(L, dir); + + float d2 = sphere.w * sphere.w - (dot(L, L) - tca * tca); + if (d2 < 0.0) { + return false; + } + + float thc = sqrt(d2); + t0 = tca - thc; + t1 = tca + thc; + if (t1 < 0.0) { + return false; + } + + return true; + } + + float calcDepth(in vec3 pos, in mat4 viewProjection) { + vec4 v = viewProjection * vec4(pos, 1.0); + return (v.z / v.w) * 0.5 + 0.5; + } + + float noise(vec2 fragCoord, sampler2D noiseTex) { + vec2 uv = fract(fragCoord / 32.0); + return texture2DLodEXT(noiseTex, uv, 0.0).y; + } + + vec2 calcAzimuthElev(in vec3 dir) { + float azimuth = atan(dir.z, dir.x); + float elev = asin(dir.y); + return vec2(azimuth, elev) * 180.0 / 3.14159; + } + + bool strips(vec3 lp) { + vec2 ae = calcAzimuthElev(normalize(lp)); + + float spacing = 10.0; + float size = 0.25 / spacing; + return fract(ae.x / spacing) > size && + fract(ae.y / spacing) > size; + } + + uniform sampler2D blueNoiseTex32; + uniform vec3 view_position; + uniform mat4 matrix_viewProjection; + uniform vec4 sphere; + + varying vec3 fragWorld; + + void behind(vec3 ray, float t) { + vec3 wp = view_position + ray * t; + if (strips(wp - sphere.xyz) || noise(gl_FragCoord.yx, blueNoiseTex32) < 0.125) { + discard; + } + + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + gl_FragDepth = calcDepth(wp, matrix_viewProjection); + } + + void front(vec3 ray, float t0, float t1) { + if (t0 < 0.0) { + behind(ray, t1); + } else { + vec3 wp = view_position + ray * t0; + if (strips(wp - sphere.xyz) || noise(gl_FragCoord.xy, blueNoiseTex32) < 0.6) { + behind(ray, t1); + } else { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + gl_FragDepth = calcDepth(wp, matrix_viewProjection); + } + } + } + + void main() { + vec3 ray = normalize(fragWorld - view_position); + + float t0, t1; + if (!intersectSphere(t0, t1, view_position, ray, sphere)) { + discard; + } + + front(ray, t0, t1); + } +`; + const v = new Vec3(); const bound = new BoundingBox(); class SphereShape extends Element { - pivot: Entity; _radius = 1; + pivot: Entity; + material: Material; constructor() { super(ElementType.debug); this.pivot = new Entity('spherePivot'); + this.pivot.addComponent('render', { + type: 'box' + }); + const r = this._radius * 2; + this.pivot.setLocalScale(r, r, r); } add() { + const device = this.scene.app.graphicsDevice; + + const shader = createShaderFromCode( + device, + vsCode, + fsCode, + 'sphere-shape' + ); + + const material = new Material(); + material.shader = shader; + material.cull = CULLFACE_NONE; + material.update(); + + this.pivot.render.meshInstances[0].material = material; + + this.material = material; + this.scene.contentRoot.addChild(this.pivot); + this.updateBound(); } @@ -35,8 +170,8 @@ class SphereShape extends Element { } onPreRender() { - this.pivot.getWorldTransform().getTranslation(v) - this.scene.app.drawWireSphere(v, this.radius, Color.RED, 40); + this.pivot.getWorldTransform().getTranslation(v); + this.material.setParameter('sphere', [v.x, v.y, v.z, this.radius]); } moved() { @@ -55,6 +190,10 @@ class SphereShape extends Element { set radius(radius: number) { this._radius = radius; + + const r = this._radius * 2; + this.pivot.setLocalScale(r, r, r); + this.updateBound(); } From 44e0be43bd0c10303f445af5a82aea0f9b9b2e52 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Mon, 5 Aug 2024 16:56:20 +0100 Subject: [PATCH 22/33] lint --- src/sphere-shape.ts | 2 +- src/ui/menu-panel.ts | 5 +++-- src/ui/right-toolbar.ts | 2 +- src/ui/scene-panel.ts | 2 +- src/ui/tooltips.ts | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sphere-shape.ts b/src/sphere-shape.ts index b0cef1e5..653cedb0 100644 --- a/src/sphere-shape.ts +++ b/src/sphere-shape.ts @@ -200,6 +200,6 @@ class SphereShape extends Element { get radius() { return this._radius; } -}; +} export { SphereShape }; diff --git a/src/ui/menu-panel.ts b/src/ui/menu-panel.ts index f4f30e3e..90177ea2 100644 --- a/src/ui/menu-panel.ts +++ b/src/ui/menu-panel.ts @@ -58,7 +58,7 @@ class MenuPanel extends Container { super(args); this.on('hide', () => { - for (let menuItem of menuItems) { + for (const menuItem of menuItems) { if (menuItem.subMenu) { menuItem.subMenu.hidden = true; } @@ -76,7 +76,7 @@ class MenuPanel extends Container { let deactivate: () => void | null = null; - for (let menuItem of menuItems) { + for (const menuItem of menuItems) { const type = menuItem.subMenu ? 'menu' : menuItem.text ? 'button' : 'separator'; let row: Container | null = null; @@ -168,6 +168,7 @@ class MenuPanel extends Container { } get rootPanel() { + // eslint-disable-next-line @typescript-eslint/no-this-alias let panel: MenuPanel = this; while (panel.parentPanel) { panel = panel.parentPanel; diff --git a/src/ui/right-toolbar.ts b/src/ui/right-toolbar.ts index ff0ad3c5..a4bb078c 100644 --- a/src/ui/right-toolbar.ts +++ b/src/ui/right-toolbar.ts @@ -70,6 +70,6 @@ class RightToolbar extends Container { events.fire('camera.toggleDebug'); }); } -}; +} export { RightToolbar }; diff --git a/src/ui/scene-panel.ts b/src/ui/scene-panel.ts index d5a2a9ec..9f220815 100644 --- a/src/ui/scene-panel.ts +++ b/src/ui/scene-panel.ts @@ -82,6 +82,6 @@ class ScenePanel extends Container { this.append(transformHeader); this.append(new Transform(events)); } -}; +} export { ScenePanel }; diff --git a/src/ui/tooltips.ts b/src/ui/tooltips.ts index ac84b648..b4395e06 100644 --- a/src/ui/tooltips.ts +++ b/src/ui/tooltips.ts @@ -114,7 +114,7 @@ class Tooltips extends Container { }; this.destroy = () => { - for (let target of targets.keys()) { + for (const target of targets.keys()) { this.unregister(target); } }; From 0618125ca6aefc42a709cd946e975ab1562bb8ce Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Tue, 6 Aug 2024 10:50:40 +0100 Subject: [PATCH 23/33] disable select globally, update sphere visual, add css autoprefixer --- package-lock.json | 911 ++++++++++++++++++++++++----------------- package.json | 14 +- rollup.config.mjs | 12 +- src/sphere-shape.ts | 18 +- src/ui/menu-panel.scss | 3 + src/ui/menu.ts | 3 +- src/ui/popup.scss | 3 + src/ui/style.scss | 7 - 8 files changed, 570 insertions(+), 401 deletions(-) diff --git a/package-lock.json b/package-lock.json index f84039fe..24b5f579 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0-alpha.0", "license": "MIT", "devDependencies": { - "@playcanvas/eslint-config": "^1.7.1", + "@playcanvas/eslint-config": "^1.7.4", "@playcanvas/pcui": "^4.4.0", "@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-image": "^3.0.3", @@ -19,15 +19,17 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@types/wicg-file-system-access": "^2023.10.5", - "@typescript-eslint/eslint-plugin": "^7.15.0", - "@typescript-eslint/parser": "^7.15.0", + "@typescript-eslint/eslint-plugin": "^8.0.1", + "@typescript-eslint/parser": "^8.0.1", + "autoprefixer": "^10.4.20", "concurrently": "^8.2.2", "cors": "^2.8.5", "cross-env": "^7.0.3", - "eslint": "^8.56.0", + "eslint": "^9.8.0", "jest": "^29.7.0", - "playcanvas": "^1.73.1", - "rollup": "^4.18.0", + "playcanvas": "^1.73.4", + "postcss": "^8.4.41", + "rollup": "^4.20.0", "rollup-plugin-sass": "^1.13.0", "rollup-plugin-visualizer": "^5.12.0", "serve": "^14.2.3", @@ -668,38 +670,29 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@eslint/config-array": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.1.tgz", + "integrity": "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==", "dev": true, "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "node_modules/@eslint/config-array/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", @@ -709,7 +702,7 @@ "concat-map": "0.0.1" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "node_modules/@eslint/config-array/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", @@ -721,30 +714,30 @@ "node": "*" } }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=10.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", @@ -754,7 +747,7 @@ "concat-map": "0.0.1" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", @@ -766,6 +759,24 @@ "node": "*" } }, + "node_modules/@eslint/js": { + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.8.0.tgz", + "integrity": "sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -779,11 +790,18 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -1310,18 +1328,90 @@ } }, "node_modules/@playcanvas/eslint-config": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@playcanvas/eslint-config/-/eslint-config-1.7.1.tgz", - "integrity": "sha512-/mLrTrWjEMkrY9N8DzhMkwPNBue88RsdVzmxh+Y0rmBScaUrTu1Cj8FA/r1Tw5tXlAvqic7hhvnpL/wxuQcq6A==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@playcanvas/eslint-config/-/eslint-config-1.7.4.tgz", + "integrity": "sha512-vsjFX3aQ0Zn8LvVyoOvTAnT44qoXYmpbwPtOMuxhCJxMPaM4mWUVgDWBgt7/x32lhD+zKxHnZbHvYBs6d3FZ9g==", "dev": true, "dependencies": { "eslint-plugin-import": "^2.28.0", - "eslint-plugin-jsdoc": "^46.4.6" + "eslint-plugin-jsdoc": "^46.4.6", + "eslint-plugin-regexp": "^2.6.0" }, "peerDependencies": { "eslint": ">= 4" } }, + "node_modules/@playcanvas/eslint-config/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@playcanvas/eslint-config/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@playcanvas/eslint-config/node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/@playcanvas/eslint-config/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@playcanvas/eslint-config/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@playcanvas/observer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@playcanvas/observer/-/observer-1.4.0.tgz", @@ -1516,9 +1606,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", - "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", + "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", "cpu": [ "arm" ], @@ -1529,9 +1619,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", - "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", + "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", "cpu": [ "arm64" ], @@ -1542,9 +1632,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", - "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", + "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", "cpu": [ "arm64" ], @@ -1555,9 +1645,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", + "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", "cpu": [ "x64" ], @@ -1568,9 +1658,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", - "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", + "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", "cpu": [ "arm" ], @@ -1581,9 +1671,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", - "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", + "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", "cpu": [ "arm" ], @@ -1594,9 +1684,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", - "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", + "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", "cpu": [ "arm64" ], @@ -1607,9 +1697,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", - "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", + "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", "cpu": [ "arm64" ], @@ -1620,9 +1710,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", - "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", + "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", "cpu": [ "ppc64" ], @@ -1633,9 +1723,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", - "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", + "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", "cpu": [ "riscv64" ], @@ -1646,9 +1736,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", - "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", + "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", "cpu": [ "s390x" ], @@ -1659,9 +1749,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", - "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", + "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", "cpu": [ "x64" ], @@ -1672,9 +1762,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", - "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", + "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", "cpu": [ "x64" ], @@ -1685,9 +1775,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", - "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", + "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", "cpu": [ "arm64" ], @@ -1698,9 +1788,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", - "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", + "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", "cpu": [ "ia32" ], @@ -1711,9 +1801,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", - "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", + "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", "cpu": [ "x64" ], @@ -1882,31 +1972,31 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.15.0.tgz", - "integrity": "sha512-uiNHpyjZtFrLwLDpHnzaDlP3Tt6sGMqTCiqmxaN4n4RP0EfYZDODJyddiFDF44Hjwxr5xAcaYxVKm9QKQFJFLA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.1.tgz", + "integrity": "sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.15.0", - "@typescript-eslint/type-utils": "7.15.0", - "@typescript-eslint/utils": "7.15.0", - "@typescript-eslint/visitor-keys": "7.15.0", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/type-utils": "8.0.1", + "@typescript-eslint/utils": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1915,26 +2005,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.15.0.tgz", - "integrity": "sha512-k9fYuQNnypLFcqORNClRykkGOMOj+pV6V91R4GO/l1FDGwpqmSwoOQrOHo3cGaH63e+D3ZiCAOsuS/D2c99j/A==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.1.tgz", + "integrity": "sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.15.0", - "@typescript-eslint/types": "7.15.0", - "@typescript-eslint/typescript-estree": "7.15.0", - "@typescript-eslint/visitor-keys": "7.15.0", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/typescript-estree": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1943,16 +2033,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz", - "integrity": "sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz", + "integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.15.0", - "@typescript-eslint/visitor-keys": "7.15.0" + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -1960,26 +2050,23 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.15.0.tgz", - "integrity": "sha512-SkgriaeV6PDvpA6253PDVep0qCqgbO1IOBiycjnXsszNTVQe5flN5wR5jiczoEoDEnAqYFSFFc9al9BSGVltkg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.1.tgz", + "integrity": "sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.15.0", - "@typescript-eslint/utils": "7.15.0", + "@typescript-eslint/typescript-estree": "8.0.1", + "@typescript-eslint/utils": "8.0.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "^8.56.0" - }, "peerDependenciesMeta": { "typescript": { "optional": true @@ -1987,12 +2074,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.15.0.tgz", - "integrity": "sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz", + "integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==", "dev": true, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2000,13 +2087,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz", - "integrity": "sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", + "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.15.0", - "@typescript-eslint/visitor-keys": "7.15.0", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2015,7 +2102,7 @@ "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2028,50 +2115,44 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.15.0.tgz", - "integrity": "sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.1.tgz", + "integrity": "sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.15.0", - "@typescript-eslint/types": "7.15.0", - "@typescript-eslint/typescript-estree": "7.15.0" + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/typescript-estree": "8.0.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz", - "integrity": "sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz", + "integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/types": "8.0.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, "node_modules/@webgpu/types": { "version": "0.1.40", "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.40.tgz", @@ -2098,9 +2179,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -2391,6 +2472,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2629,9 +2747,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -2648,10 +2766,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -2734,9 +2852,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001620", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz", - "integrity": "sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==", + "version": "1.0.30001649", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001649.tgz", + "integrity": "sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ==", "dev": true, "funding": [ { @@ -3352,15 +3470,15 @@ } }, "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "dependencies": { "esutils": "^2.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, "node_modules/eastasianwidth": { @@ -3370,9 +3488,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.776", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.776.tgz", - "integrity": "sha512-s694bi3+gUzlliqxjPHpa9NRTlhzTgB34aan+pVKZmOTGy2xoZXl+8E1B8i5p5rtev3PKMK/H4asgNejC+YHNg==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz", + "integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==", "dev": true }, "node_modules/emittery": { @@ -3557,41 +3675,37 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.8.0.tgz", + "integrity": "sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.17.1", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.8.0", "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -3605,10 +3719,10 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" } }, "node_modules/eslint-import-resolver-node": { @@ -3657,89 +3771,6 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", - "semver": "^6.3.1", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/eslint-plugin-jsdoc": { "version": "46.10.1", "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.10.1.tgz", @@ -3763,17 +3794,38 @@ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, + "node_modules/eslint-plugin-regexp": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.6.0.tgz", + "integrity": "sha512-FCL851+kislsTEQEMioAlpDuK5+E5vs0hi1bF8cFlPlHcEjeRhuAzEsGikXRreE+0j4WhW2uO54MqTjXtYOi3A==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.9.1", + "comment-parser": "^1.4.0", + "jsdoc-type-pratt-parser": "^4.0.0", + "refa": "^0.12.1", + "regexp-ast-analysis": "^0.7.1", + "scslre": "^0.3.0" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "eslint": ">=8.44.0" + } + }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3801,6 +3853,18 @@ "concat-map": "0.0.1" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3814,17 +3878,29 @@ } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", "dev": true, "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4013,15 +4089,15 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -4053,17 +4129,16 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { @@ -4081,6 +4156,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4267,15 +4355,12 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5875,6 +5960,24 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5897,9 +6000,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, "node_modules/normalize-path": { @@ -5911,6 +6014,15 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -5933,10 +6045,13 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6299,9 +6414,9 @@ } }, "node_modules/playcanvas": { - "version": "1.73.1", - "resolved": "https://registry.npmjs.org/playcanvas/-/playcanvas-1.73.1.tgz", - "integrity": "sha512-/1FZLtsbEkVR36ZNlonbVowDJHSnKy68adg0nsvf9CVS2y+5CR29S4mr8bnU1PmwYb+yvVY3wvNEkrgwadiLQg==", + "version": "1.73.4", + "resolved": "https://registry.npmjs.org/playcanvas/-/playcanvas-1.73.4.tgz", + "integrity": "sha512-HgnnVlPbCC0jDOABW66fbnsnosTQ/pWyXXVVrY0t8AGJ5BINwD8ATp0gPYaS9CcPiGaxr6KfL7IFirl62V9f4Q==", "dev": true, "dependencies": { "@types/webxr": "^0.5.16", @@ -6320,6 +6435,40 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6470,12 +6619,37 @@ "node": ">=8.10.0" } }, + "node_modules/refa": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", + "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.8.0" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, + "node_modules/regexp-ast-analysis": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", + "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.1" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -6600,25 +6774,10 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", + "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -6631,22 +6790,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.0", - "@rollup/rollup-android-arm64": "4.18.0", - "@rollup/rollup-darwin-arm64": "4.18.0", - "@rollup/rollup-darwin-x64": "4.18.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", - "@rollup/rollup-linux-arm-musleabihf": "4.18.0", - "@rollup/rollup-linux-arm64-gnu": "4.18.0", - "@rollup/rollup-linux-arm64-musl": "4.18.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", - "@rollup/rollup-linux-riscv64-gnu": "4.18.0", - "@rollup/rollup-linux-s390x-gnu": "4.18.0", - "@rollup/rollup-linux-x64-gnu": "4.18.0", - "@rollup/rollup-linux-x64-musl": "4.18.0", - "@rollup/rollup-win32-arm64-msvc": "4.18.0", - "@rollup/rollup-win32-ia32-msvc": "4.18.0", - "@rollup/rollup-win32-x64-msvc": "4.18.0", + "@rollup/rollup-android-arm-eabi": "4.20.0", + "@rollup/rollup-android-arm64": "4.20.0", + "@rollup/rollup-darwin-arm64": "4.20.0", + "@rollup/rollup-darwin-x64": "4.20.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", + "@rollup/rollup-linux-arm-musleabihf": "4.20.0", + "@rollup/rollup-linux-arm64-gnu": "4.20.0", + "@rollup/rollup-linux-arm64-musl": "4.20.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", + "@rollup/rollup-linux-riscv64-gnu": "4.20.0", + "@rollup/rollup-linux-s390x-gnu": "4.20.0", + "@rollup/rollup-linux-x64-gnu": "4.20.0", + "@rollup/rollup-linux-x64-musl": "4.20.0", + "@rollup/rollup-win32-arm64-msvc": "4.20.0", + "@rollup/rollup-win32-ia32-msvc": "4.20.0", + "@rollup/rollup-win32-x64-msvc": "4.20.0", "fsevents": "~2.3.2" } }, @@ -6803,6 +6962,20 @@ "node": ">=14.0.0" } }, + "node_modules/scslre": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", + "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.0", + "regexp-ast-analysis": "^0.7.0" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + } + }, "node_modules/semver": { "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", @@ -7488,18 +7661,6 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", @@ -7609,9 +7770,9 @@ "dev": true }, "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 81bdc789..af6f5aad 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "test": "jest" }, "devDependencies": { - "@playcanvas/eslint-config": "^1.7.1", + "@playcanvas/eslint-config": "^1.7.4", "@playcanvas/pcui": "^4.4.0", "@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-image": "^3.0.3", @@ -61,15 +61,17 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@types/wicg-file-system-access": "^2023.10.5", - "@typescript-eslint/eslint-plugin": "^7.15.0", - "@typescript-eslint/parser": "^7.15.0", + "@typescript-eslint/eslint-plugin": "^8.0.1", + "@typescript-eslint/parser": "^8.0.1", + "autoprefixer": "^10.4.20", "concurrently": "^8.2.2", "cors": "^2.8.5", "cross-env": "^7.0.3", - "eslint": "^8.56.0", + "eslint": "^9.8.0", "jest": "^29.7.0", - "playcanvas": "^1.73.1", - "rollup": "^4.18.0", + "playcanvas": "^1.73.4", + "postcss": "^8.4.41", + "rollup": "^4.20.0", "rollup-plugin-sass": "^1.13.0", "rollup-plugin-visualizer": "^5.12.0", "serve": "^14.2.3", diff --git a/rollup.config.mjs b/rollup.config.mjs index 052518b9..1d31d4ab 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -7,9 +7,12 @@ import resolve from '@rollup/plugin-node-resolve'; import strip from '@rollup/plugin-strip'; import typescript from '@rollup/plugin-typescript'; import json from '@rollup/plugin-json'; -import sass from 'rollup-plugin-sass'; // import { visualizer } from 'rollup-plugin-visualizer'; +import autoprefixer from 'autoprefixer'; +import postcss from 'postcss'; +import sass from 'rollup-plugin-sass'; + // prod is release build if (process.env.BUILD_TYPE === 'prod') { process.env.BUILD_TYPE = 'release'; @@ -66,7 +69,12 @@ const application = { image({dom: false}), sass({ output: false, - insert: true + insert: true, + processor: (css) => { + return postcss([autoprefixer]) + .process(css) + .then(result => result.css); + } }), json(), typescript({ diff --git a/src/sphere-shape.ts b/src/sphere-shape.ts index 653cedb0..012120e1 100644 --- a/src/sphere-shape.ts +++ b/src/sphere-shape.ts @@ -60,15 +60,6 @@ const fsCode = /* glsl */ ` return vec2(azimuth, elev) * 180.0 / 3.14159; } - bool strips(vec3 lp) { - vec2 ae = calcAzimuthElev(normalize(lp)); - - float spacing = 10.0; - float size = 0.25 / spacing; - return fract(ae.x / spacing) > size && - fract(ae.y / spacing) > size; - } - uniform sampler2D blueNoiseTex32; uniform vec3 view_position; uniform mat4 matrix_viewProjection; @@ -76,6 +67,15 @@ const fsCode = /* glsl */ ` varying vec3 fragWorld; + bool strips(vec3 lp) { + vec2 ae = calcAzimuthElev(normalize(lp)); + + float spacing = 180.0 / (2.0 * 3.14159 * sphere.w); + float size = 0.03; + return fract(ae.x / spacing) > size && + fract(ae.y / spacing) > size; + } + void behind(vec3 ray, float t) { vec3 wp = view_position + ray * t; if (strips(wp - sphere.xyz) || noise(gl_FragCoord.yx, blueNoiseTex32) < 0.125) { diff --git a/src/ui/menu-panel.scss b/src/ui/menu-panel.scss index fccc3f9f..8d717c48 100644 --- a/src/ui/menu-panel.scss +++ b/src/ui/menu-panel.scss @@ -11,6 +11,9 @@ background-color: $bcg-dark; filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.8)); + + // the following is needed to get drop-shadow working on safari + will-change: transform; } .menu-row { diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 24fd42ed..0757cdd0 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -8,8 +8,7 @@ class Menu extends Container { constructor(events: Events, args = {}) { args = { ...args, - id: 'menu', - class: 'unselectable' + id: 'menu' }; super(args); diff --git a/src/ui/popup.scss b/src/ui/popup.scss index f5b43c15..0efe382a 100644 --- a/src/ui/popup.scss +++ b/src/ui/popup.scss @@ -21,6 +21,9 @@ filter: drop-shadow(5px 5px 10px rgba(0, 0, 0, 0.8)); + // the following is needed to get drop-shadow working on safari + will-change: transform; + #popup-header { height: 32px; line-height: 32px; diff --git a/src/ui/style.scss b/src/ui/style.scss index e231c893..46b1dab5 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -24,13 +24,6 @@ $bcg-darkest: #181818; * { font-size: 12px; -} - -*.unselectable { - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; user-select: none; } From 418f86d54370f378cc964413a0c05939bb308a5a Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Tue, 6 Aug 2024 11:49:06 +0100 Subject: [PATCH 24/33] revert to eslint 8 --- package-lock.json | 971 +++++++++++++++++++--------------------------- package.json | 6 +- 2 files changed, 410 insertions(+), 567 deletions(-) diff --git a/package-lock.json b/package-lock.json index 24b5f579..1ec9f962 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,13 +19,13 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@types/wicg-file-system-access": "^2023.10.5", - "@typescript-eslint/eslint-plugin": "^8.0.1", - "@typescript-eslint/parser": "^8.0.1", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", "autoprefixer": "^10.4.20", "concurrently": "^8.2.2", "cors": "^2.8.5", "cross-env": "^7.0.3", - "eslint": "^9.8.0", + "eslint": "^8.56.0", "jest": "^29.7.0", "playcanvas": "^1.73.4", "postcss": "^8.4.41", @@ -50,12 +50,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -63,30 +63,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", - "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.24.5", - "@babel/helpers": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -111,12 +111,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.25.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -126,14 +126,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -150,63 +150,29 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -216,86 +182,74 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", "dev": true, "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -376,10 +330,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -448,12 +405,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -550,12 +507,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", - "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -565,9 +522,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -577,33 +534,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -621,13 +575,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -678,52 +632,16 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.1.tgz", - "integrity": "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==", - "dev": true, - "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -731,50 +649,34 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/js": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.8.0.tgz", - "integrity": "sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10.10.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -790,18 +692,12 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", - "dev": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -1277,9 +1173,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { @@ -1341,16 +1237,6 @@ "eslint": ">= 4" } }, - "node_modules/@playcanvas/eslint-config/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@playcanvas/eslint-config/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -1391,18 +1277,6 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, - "node_modules/@playcanvas/eslint-config/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@playcanvas/eslint-config/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -1413,9 +1287,9 @@ } }, "node_modules/@playcanvas/observer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@playcanvas/observer/-/observer-1.4.0.tgz", - "integrity": "sha512-aeyAbH+CPWs39DS3vNcIfKODF3lTqd0vn0xrLeZoaylGVw3XTCKjxyc9z0SGC9qivTZpQj0SksU8wT/0HbgkWw==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@playcanvas/observer/-/observer-1.5.1.tgz", + "integrity": "sha512-0y4OQOtwhgCvFaNlhLgi4ycSeOdnp0Frf4R+8eewxtfZQW1Muox2lkUGoMywoRRLhaRjUMdaxQN1o53lNjlB9w==", "dev": true }, "node_modules/@playcanvas/pcui": { @@ -1870,9 +1744,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" @@ -1924,12 +1798,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", + "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.13.0" } }, "node_modules/@types/resolve": { @@ -1945,9 +1819,9 @@ "dev": true }, "node_modules/@types/webxr": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.16.tgz", - "integrity": "sha512-0E0Cl84FECtzrB4qG19TNTqpunw0F1YF0QZZnFMF6pDw1kNKJtrlTKlVB34stGIsHbZsYQ7H0tNjPfZftkHHoA==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.19.tgz", + "integrity": "sha512-4hxA+NwohSgImdTSlPXEqDqqFktNgmTXQ05ff1uWam05tNGroCMp4G+4XVl6qWm1p7GQ/9oD41kAYsSssF6Mzw==", "dev": true }, "node_modules/@types/wicg-file-system-access": { @@ -1972,31 +1846,31 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.1.tgz", - "integrity": "sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.0.1", - "@typescript-eslint/type-utils": "8.0.1", - "@typescript-eslint/utils": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -2005,26 +1879,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.1.tgz", - "integrity": "sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.0.1", - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/typescript-estree": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -2033,16 +1907,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz", - "integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2050,23 +1924,26 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.1.tgz", - "integrity": "sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.0.1", - "@typescript-eslint/utils": "8.0.1", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, + "peerDependencies": { + "eslint": "^8.56.0" + }, "peerDependenciesMeta": { "typescript": { "optional": true @@ -2074,12 +1951,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz", - "integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2087,13 +1964,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", - "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/visitor-keys": "8.0.1", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2102,7 +1979,7 @@ "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2114,49 +1991,79 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/utils": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.1.tgz", - "integrity": "sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.0.1", - "@typescript-eslint/types": "8.0.1", - "@typescript-eslint/typescript-estree": "8.0.1" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz", - "integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@webgpu/types": { - "version": "0.1.40", - "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.40.tgz", - "integrity": "sha512-/BBkHLS6/eQjyWhY2H7Dx5DHcVrS2ICj9owvSRdgtQT6KcafLZA86tPze0xAOsd4FbsYKCUBUQyNi87q7gV7kw==", + "version": "0.1.44", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.44.tgz", + "integrity": "sha512-JDpYJN5E/asw84LTYhKyvPpxGnD+bAKPtpW9Ilurf7cZpxaTbxkQcGwOd7jgB9BPBrTYQ+32ufo4HiuomTjHNQ==", "dev": true }, "node_modules/@zeit/schemas": { @@ -2259,18 +2166,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2726,12 +2621,13 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -3342,9 +3238,9 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -3675,37 +3571,41 @@ } }, "node_modules/eslint": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.8.0.tgz", - "integrity": "sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.17.1", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.8.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", + "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.2", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", - "esquery": "^1.5.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", + "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -3719,10 +3619,10 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://eslint.org/donate" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-import-resolver-node": { @@ -3816,16 +3716,16 @@ } }, "node_modules/eslint-scope": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", - "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3843,64 +3743,30 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/eslint/node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "esutils": "^2.0.2" }, "engines": { - "node": "*" + "node": ">=6.0.0" } }, "node_modules/espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3920,9 +3786,9 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -4089,15 +3955,15 @@ } }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { - "flat-cache": "^4.0.0" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=16.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/fill-range": { @@ -4129,16 +3995,17 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.4" + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=16" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { @@ -4304,6 +4171,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -4332,35 +4200,28 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "type-fest": "^0.20.2" }, "engines": { - "node": "*" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4541,9 +4402,9 @@ } }, "node_modules/immutable": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", - "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", "dev": true }, "node_modules/import-fresh": { @@ -4563,9 +4424,9 @@ } }, "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "dependencies": { "pkg-dir": "^4.2.0", @@ -4594,6 +4455,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -4716,12 +4578,15 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5008,9 +4873,9 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", - "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "dependencies": { "@babel/core": "^7.23.9", @@ -5819,12 +5684,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.10", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", - "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -5867,34 +5732,22 @@ } }, "node_modules/micromatch": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.6.tgz", - "integrity": "sha512-Y4Ypn3oujJYxJcMacVgcs92wofTHxp9FzfDpQON4msDefoC0lb3ETvQLOdLcbhSwU1bz8HrL/1sygfBIHudrkQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { "braces": "^3.0.3", - "picomatch": "^4.0.2" + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", "dev": true, "engines": { "node": ">= 0.6" @@ -5912,6 +5765,15 @@ "node": ">= 0.6" } }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -5931,18 +5793,15 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/minimist": { @@ -6774,6 +6633,22 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "4.20.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", @@ -6810,9 +6685,9 @@ } }, "node_modules/rollup-plugin-sass": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-sass/-/rollup-plugin-sass-1.13.0.tgz", - "integrity": "sha512-TL/pBbuqN3Qftiub1rLWiPnUGyL5PC7/+4x1ZgFJWzu1Y8n2vwYILt1kPR83AUzPOwqgFfD+B/LqgV6ee0+CYQ==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-sass/-/rollup-plugin-sass-1.13.1.tgz", + "integrity": "sha512-ZppWT9mHha0KT2mYOCqRujFP7BMavOsKcWX1X7fz3foAFpjcEA50704oJWf/BI2zx3wTt8gC0+DqlPvV2a1maA==", "dev": true, "dependencies": { "@rollup/pluginutils": "^3 || ^4 || ^5", @@ -6946,9 +6821,9 @@ } }, "node_modules/sass": { - "version": "1.77.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.2.tgz", - "integrity": "sha512-eb4GZt1C3avsX3heBNlrc7I09nyT00IUuo4eFhAbeXWU2fvA7oXI53SxODVAA+zgZCk9aunAZgO+losjR3fAwA==", + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", + "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -6977,9 +6852,9 @@ } }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -7038,16 +6913,6 @@ "range-parser": "1.2.0" } }, - "node_modules/serve-handler/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/serve-handler/node_modules/mime-db": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", @@ -7069,18 +6934,6 @@ "node": ">= 0.6" } }, - "node_modules/serve-handler/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/serve/node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -7276,9 +7129,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", "dev": true }, "node_modules/sprintf-js": { @@ -7484,9 +7337,9 @@ } }, "node_modules/terser": { - "version": "5.31.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", - "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", + "version": "5.31.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.3.tgz", + "integrity": "sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -7525,28 +7378,6 @@ "node": ">=8" } }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -7661,6 +7492,18 @@ "node": ">=4" } }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", @@ -7735,9 +7578,9 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "peer": true, "bin": { @@ -7764,9 +7607,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", + "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", "dev": true }, "node_modules/update-browserslist-db": { @@ -7828,9 +7671,9 @@ } }, "node_modules/v8-to-istanbul": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", - "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", diff --git a/package.json b/package.json index af6f5aad..5ca4bd4f 100644 --- a/package.json +++ b/package.json @@ -61,13 +61,13 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@types/wicg-file-system-access": "^2023.10.5", - "@typescript-eslint/eslint-plugin": "^8.0.1", - "@typescript-eslint/parser": "^8.0.1", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", "autoprefixer": "^10.4.20", "concurrently": "^8.2.2", "cors": "^2.8.5", "cross-env": "^7.0.3", - "eslint": "^9.8.0", + "eslint": "^8.56.0", "jest": "^29.7.0", "playcanvas": "^1.73.4", "postcss": "^8.4.41", From 054dccde85d3be0ee2f0dafa19742c283bf03ad0 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Tue, 6 Aug 2024 13:59:02 +0100 Subject: [PATCH 25/33] add menu icons, update svg --- src/svg/crop.svg | 2 +- src/svg/delete.svg | 3 +++ src/svg/export.svg | 3 +++ src/svg/frame-selection.svg | 4 +-- src/svg/import.svg | 3 +++ src/svg/logo.svg | 13 ++++++++++ src/svg/new.svg | 3 +++ src/svg/open.svg | 3 +++ src/svg/redo.svg | 2 +- src/svg/save.svg | 3 +++ src/svg/select-all.svg | 3 +++ src/svg/select-brush.svg | 4 +-- src/svg/select-inverse.svg | 3 +++ src/svg/select-lasso.svg | 4 +-- src/svg/select-lock.svg | 5 ++++ src/svg/select-none.svg | 3 +++ src/svg/select-picker.svg | 2 +- src/svg/select-sphere.svg | 2 +- src/svg/select-unlock.svg | 5 ++++ src/svg/show-hide-splats.svg | 4 +-- src/svg/undo.svg | 2 +- src/ui/bottom-toolbar.scss | 29 ++++++---------------- src/ui/menu-panel.scss | 12 +++++++++ src/ui/menu-panel.ts | 12 ++++++--- src/ui/menu.ts | 47 ++++++++++++++++++++++++++---------- src/ui/panel.scss | 6 +++++ src/ui/right-toolbar.scss | 30 ++++++----------------- src/ui/scene-panel.ts | 18 ++++++++++---- 28 files changed, 153 insertions(+), 77 deletions(-) create mode 100644 src/svg/delete.svg create mode 100644 src/svg/export.svg create mode 100644 src/svg/import.svg create mode 100644 src/svg/logo.svg create mode 100644 src/svg/new.svg create mode 100644 src/svg/open.svg create mode 100644 src/svg/save.svg create mode 100644 src/svg/select-all.svg create mode 100644 src/svg/select-inverse.svg create mode 100644 src/svg/select-lock.svg create mode 100644 src/svg/select-none.svg create mode 100644 src/svg/select-unlock.svg diff --git a/src/svg/crop.svg b/src/svg/crop.svg index fa2680b9..3934ec0e 100644 --- a/src/svg/crop.svg +++ b/src/svg/crop.svg @@ -1,3 +1,3 @@ - + diff --git a/src/svg/delete.svg b/src/svg/delete.svg new file mode 100644 index 00000000..e714a56e --- /dev/null +++ b/src/svg/delete.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/export.svg b/src/svg/export.svg new file mode 100644 index 00000000..ab855903 --- /dev/null +++ b/src/svg/export.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/frame-selection.svg b/src/svg/frame-selection.svg index a703fa5e..5fc498b1 100644 --- a/src/svg/frame-selection.svg +++ b/src/svg/frame-selection.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/svg/import.svg b/src/svg/import.svg new file mode 100644 index 00000000..65e856c7 --- /dev/null +++ b/src/svg/import.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/logo.svg b/src/svg/logo.svg new file mode 100644 index 00000000..5c36a9d1 --- /dev/null +++ b/src/svg/logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/svg/new.svg b/src/svg/new.svg new file mode 100644 index 00000000..70dca81e --- /dev/null +++ b/src/svg/new.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/open.svg b/src/svg/open.svg new file mode 100644 index 00000000..c0f0348e --- /dev/null +++ b/src/svg/open.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/redo.svg b/src/svg/redo.svg index 0bf27c3f..f279aea9 100644 --- a/src/svg/redo.svg +++ b/src/svg/redo.svg @@ -1,3 +1,3 @@ - + diff --git a/src/svg/save.svg b/src/svg/save.svg new file mode 100644 index 00000000..0cb0b093 --- /dev/null +++ b/src/svg/save.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/select-all.svg b/src/svg/select-all.svg new file mode 100644 index 00000000..787e67f1 --- /dev/null +++ b/src/svg/select-all.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/select-brush.svg b/src/svg/select-brush.svg index 5ef0ad38..051a28aa 100644 --- a/src/svg/select-brush.svg +++ b/src/svg/select-brush.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/svg/select-inverse.svg b/src/svg/select-inverse.svg new file mode 100644 index 00000000..681f6aac --- /dev/null +++ b/src/svg/select-inverse.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/select-lasso.svg b/src/svg/select-lasso.svg index f3e962b5..1a5a9090 100644 --- a/src/svg/select-lasso.svg +++ b/src/svg/select-lasso.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/svg/select-lock.svg b/src/svg/select-lock.svg new file mode 100644 index 00000000..946b8338 --- /dev/null +++ b/src/svg/select-lock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/svg/select-none.svg b/src/svg/select-none.svg new file mode 100644 index 00000000..768f5226 --- /dev/null +++ b/src/svg/select-none.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/select-picker.svg b/src/svg/select-picker.svg index 3c04961a..ce3b67ab 100644 --- a/src/svg/select-picker.svg +++ b/src/svg/select-picker.svg @@ -1,3 +1,3 @@ - + diff --git a/src/svg/select-sphere.svg b/src/svg/select-sphere.svg index 73628dd2..0a511e90 100644 --- a/src/svg/select-sphere.svg +++ b/src/svg/select-sphere.svg @@ -1,3 +1,3 @@ - + diff --git a/src/svg/select-unlock.svg b/src/svg/select-unlock.svg new file mode 100644 index 00000000..d0f2ab75 --- /dev/null +++ b/src/svg/select-unlock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/svg/show-hide-splats.svg b/src/svg/show-hide-splats.svg index e5dbce33..aaa2f035 100644 --- a/src/svg/show-hide-splats.svg +++ b/src/svg/show-hide-splats.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/svg/undo.svg b/src/svg/undo.svg index 13491bc4..6ac62489 100644 --- a/src/svg/undo.svg +++ b/src/svg/undo.svg @@ -1,3 +1,3 @@ - + diff --git a/src/ui/bottom-toolbar.scss b/src/ui/bottom-toolbar.scss index d248f245..098a2a86 100644 --- a/src/ui/bottom-toolbar.scss +++ b/src/ui/bottom-toolbar.scss @@ -28,9 +28,7 @@ } svg { - path { - fill: $clr-default; - } + color: $clr-default; } } @@ -43,9 +41,7 @@ .bottom-toolbar-button { svg { - path { - fill: $clr-default; - } + color: $clr-default; } } @@ -53,9 +49,7 @@ background-color: $bcg-primary; svg { - path { - fill: $clr-default; - } + color: $clr-default } &.active { @@ -63,20 +57,16 @@ background-color: $clr-hilight !important; svg { - path { - fill: $clr-active; - stroke: $clr-active; - } + color: $clr-active; } } &.disabled { + background-color: $bcg-dark; + svg { - path { - fill: $clr-disabled; - } + color: $clr-disabled; } - background-color: $bcg-dark; } } @@ -86,9 +76,6 @@ } svg { - path { - fill: $clr-hilight; - stroke: $clr-hilight; - } + color: $clr-hilight; } } diff --git a/src/ui/menu-panel.scss b/src/ui/menu-panel.scss index 8d717c48..c0c61384 100644 --- a/src/ui/menu-panel.scss +++ b/src/ui/menu-panel.scss @@ -24,6 +24,10 @@ height: 32px; padding: 0px 8px; + svg { + color: $text-secondary; + } + &:hover:not(.pcui-disabled) { background-color: $bcg-darkest; cursor: pointer; @@ -31,12 +35,20 @@ & > .menu-row-text, .menu-row-postscript, .menu-row-icon { color: $text-primary; } + + svg { + color: $text-primary; + } } &.pcui-disabled { & > .menu-row-text, .menu-row-postscript, .menu-row-icon { color: $text-darkest; } + + svg { + color: $text-darkest; + } } // tweaks for attached boolean toggles diff --git a/src/ui/menu-panel.ts b/src/ui/menu-panel.ts index 90177ea2..e2c2b680 100644 --- a/src/ui/menu-panel.ts +++ b/src/ui/menu-panel.ts @@ -4,7 +4,7 @@ type Direction = 'left' | 'right' | 'top' | 'bottom'; type MenuItem = { text?: string; - icon?: string; + icon?: string | Element; extra?: string | Element; subMenu?: MenuPanel; @@ -79,12 +79,18 @@ class MenuPanel extends Container { for (const menuItem of menuItems) { const type = menuItem.subMenu ? 'menu' : menuItem.text ? 'button' : 'separator'; + const createIcon = (icon: string | Element) => { + return isString(menuItem.icon) ? + new Label({ class: 'menu-row-icon', text: menuItem.icon && String.fromCodePoint(parseInt(menuItem.icon as string, 16)) }) : + menuItem.icon; + }; + let row: Container | null = null; let activate: () => void | null = null; switch (type) { case 'button': { row = new Container({ class: 'menu-row' }); - const icon = new Label({ class: 'menu-row-icon', text: menuItem.icon && String.fromCodePoint(parseInt(menuItem.icon, 16)) }); + const icon = createIcon(menuItem.icon); const text = new Label({ class: 'menu-row-text', text: menuItem.text }); const postscript = isString(menuItem.extra) ? new Label({ class: 'menu-row-postscript', text: menuItem.extra as string }) : menuItem.extra; row.append(icon); @@ -96,7 +102,7 @@ class MenuPanel extends Container { case 'menu': { row = new Container({ class: 'menu-row' }); - const icon = new Label({ class: 'menu-row-icon', text: menuItem.icon &&String.fromCodePoint(parseInt(menuItem.icon, 16)) }); + const icon = createIcon(menuItem.icon); const text = new Label({ class: 'menu-row-text', text: menuItem.text }); const postscript = new Label({ class: 'menu-row-postscript', text: '\u232A' }); row.append(icon); diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 0757cdd0..1b6325ec 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -1,8 +1,26 @@ -import { Container, Label, BooleanInput } from 'pcui'; +import { Container, Element, Label, BooleanInput } from 'pcui'; import { Events } from '../events'; import { MenuPanel } from './menu-panel'; import logoSvg from '../svg/playcanvas-logo.svg'; +import sceneNew from '../svg/new.svg'; +import sceneOpen from '../svg/open.svg'; +import sceneSave from '../svg/save.svg'; +import sceneExport from '../svg/export.svg'; +import sceneImport from '../svg/import.svg'; +import selectAll from '../svg/select-all.svg'; +import selectNone from '../svg/select-none.svg'; +import selectInverse from '../svg/select-inverse.svg'; +import selectLock from '../svg/select-lock.svg'; +import selectUnlock from '../svg/select-unlock.svg'; +import selectDelete from '../svg/delete.svg'; + +const createSvg = (svgString: string) => { + const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length)); + return new Element({ + dom: new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement + }); +}; class Menu extends Container { constructor(events: Events, args = {}) { @@ -52,12 +70,12 @@ class Menu extends Container { const exportMenuPanel = new MenuPanel([{ text: 'Compressed Ply', - icon: 'E245', + icon: createSvg(sceneExport), onSelect: () => events.invoke('scene.export', 'compressed-ply'), isEnabled: () => !events.invoke('scene.empty'), }, { text: 'Splat file', - icon: 'E245', + icon: createSvg(sceneExport), onSelect: () => events.invoke('scene.export', 'splat'), isEnabled: () => !events.invoke('scene.empty'), }]); @@ -72,11 +90,11 @@ class Menu extends Container { const sceneMenuPanel = new MenuPanel([{ text: 'New', - icon: 'E208', + icon: createSvg(sceneNew), onSelect: () => events.invoke('scene.new') }, { text: 'Open', - icon: 'E226', + icon: createSvg(sceneOpen), onSelect: async () => { if (await events.invoke('scene.new')) { events.fire('scene.open'); @@ -84,7 +102,7 @@ class Menu extends Container { } }, { text: 'Import', - icon: 'E245', + icon: createSvg(sceneImport), onSelect: () => events.fire('scene.open') }, { // separator @@ -100,47 +118,50 @@ class Menu extends Container { // separator }, { text: 'Save', - icon: 'E216', + icon: createSvg(sceneSave), onSelect: () => events.fire('scene.save'), isEnabled: () => !events.invoke('scene.empty') }, { text: 'Save As...', - icon: 'E216', + icon: createSvg(sceneSave), onSelect: () => events.fire('scene.saveAs'), isEnabled: () => !events.invoke('scene.empty') }, { text: 'Export', - icon: 'E225', + icon: createSvg(sceneExport), subMenu: exportMenuPanel }]); const selectionMenuPanel = new MenuPanel([{ text: 'All', - icon: 'E0020', + icon: createSvg(selectAll), extra: 'A', onSelect: () => events.fire('select.all') }, { text: 'None', - icon: 'E0020', + icon: createSvg(selectNone), extra: 'Shift + A', onSelect: () => events.fire('select.none') }, { - text: 'Invert', - icon: 'E0020', + text: 'Inverse', + icon: createSvg(selectInverse), extra: 'I', onSelect: () => events.fire('select.invert') }, { // separator }, { text: 'Lock Selection', + icon: createSvg(selectLock), extra: 'H', onSelect: () => events.fire('select.hide') }, { text: 'Unlock All', + icon: createSvg(selectUnlock), extra: 'U', onSelect: () => events.fire('select.unhide') }, { text: 'Delete Selection', + icon: createSvg(selectDelete), extra: 'Delete', onSelect: () => events.fire('select.delete') }, { diff --git a/src/ui/panel.scss b/src/ui/panel.scss index 849e7f02..f9e50fd6 100644 --- a/src/ui/panel.scss +++ b/src/ui/panel.scss @@ -32,6 +32,12 @@ font-size: 13px; color: $clr-hilight; + padding: 4px; + + svg { + color: $clr-hilight; + } + &:hover { color: #ff9900; cursor: pointer; diff --git a/src/ui/right-toolbar.scss b/src/ui/right-toolbar.scss index a776c55e..bda4e43f 100644 --- a/src/ui/right-toolbar.scss +++ b/src/ui/right-toolbar.scss @@ -27,9 +27,7 @@ } svg { - path { - fill: $clr-default; - } + color: $clr-default; } } @@ -42,9 +40,7 @@ &>.right-toolbar-button { svg { - path { - fill: $clr-default; - } + color: $clr-default; } } @@ -52,9 +48,7 @@ background-color: $bcg-primary; svg { - path { - fill: $clr-default; - } + color: $clr-default; } &.active { @@ -62,20 +56,16 @@ background-color: $clr-hilight !important; svg { - path { - fill: $clr-active; - stroke: $clr-active; - } + color: $clr-active; } } &.disabled { + background-color: $bcg-dark; + svg { - path { - fill: $clr-disabled; - } + color: $clr-disabled; } - background-color: $bcg-dark; } } @@ -85,12 +75,8 @@ color: $clr-hilight; } - // highlight svg svg { - path { - fill: $clr-hilight; - stroke: $clr-hilight; - } + color: $clr-hilight; } } } \ No newline at end of file diff --git a/src/ui/scene-panel.ts b/src/ui/scene-panel.ts index 9f220815..0d39ab34 100644 --- a/src/ui/scene-panel.ts +++ b/src/ui/scene-panel.ts @@ -1,9 +1,17 @@ -import { Container, Label } from 'pcui'; +import { Container, Element, Label } from 'pcui'; import { Events } from '../events'; import { Tooltips } from './tooltips'; import { SplatList } from './splat-list'; import { Transform } from './transform'; +import sceneImportSvg from '../svg/import.svg'; +import sceneNewSvg from '../svg/new.svg'; + +const createSvg = (svgString: string) => { + const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length)); + return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement; +}; + class ScenePanel extends Container { constructor(events: Events, tooltips: Tooltips, args = {}) { args = { @@ -32,15 +40,15 @@ class ScenePanel extends Container { class: `panel-header-label` }); - const sceneImport = new Label({ - text: '\uE245', + const sceneImport = new Container({ class: `panel-header-button` }); + sceneImport.dom.appendChild(createSvg(sceneImportSvg)); - const sceneNew = new Label({ - text: '\uE208', + const sceneNew = new Container({ class: `panel-header-button` }); + sceneNew.dom.appendChild(createSvg(sceneNewSvg)); sceneHeader.append(sceneIcon); sceneHeader.append(sceneLabel); From fa4b7144489c9329310be0d612b3d375cb5ca51c Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Tue, 6 Aug 2024 14:26:29 +0100 Subject: [PATCH 26/33] remove unused tools, integrate final splat list icons --- src/svg/hidden.svg | 3 +++ src/svg/shown.svg | 4 ++++ src/ui/bottom-toolbar.scss | 4 ++-- src/ui/bottom-toolbar.ts | 32 ++++++++++++++++---------------- src/ui/splat-list.scss | 24 ++++++------------------ src/ui/splat-list.ts | 22 ++++++++++++++++++++++ 6 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 src/svg/hidden.svg create mode 100644 src/svg/shown.svg diff --git a/src/svg/hidden.svg b/src/svg/hidden.svg new file mode 100644 index 00000000..724a1ef2 --- /dev/null +++ b/src/svg/hidden.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/svg/shown.svg b/src/svg/shown.svg new file mode 100644 index 00000000..705db259 --- /dev/null +++ b/src/svg/shown.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/bottom-toolbar.scss b/src/ui/bottom-toolbar.scss index 098a2a86..4dbb3b63 100644 --- a/src/ui/bottom-toolbar.scss +++ b/src/ui/bottom-toolbar.scss @@ -33,9 +33,9 @@ } .bottom-toolbar-separator { - width: 1px; + width: 2px; height: 38px; - margin: 0px 8px; + margin: 0px 10px; background-color: $bcg-primary; } diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts index 703722ff..018d1712 100644 --- a/src/ui/bottom-toolbar.ts +++ b/src/ui/bottom-toolbar.ts @@ -7,8 +7,8 @@ import redoSvg from '../svg/redo.svg'; import pickerSvg from '../svg/select-picker.svg'; import brushSvg from '../svg/select-brush.svg'; import sphereSvg from '../svg/select-sphere.svg'; -import lassoSvg from '../svg/select-lasso.svg'; -import cropSvg from '../svg/crop.svg'; +// import lassoSvg from '../svg/select-lasso.svg'; +// import cropSvg from '../svg/crop.svg'; const createSvg = (svgString: string) => { const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length)); @@ -50,20 +50,20 @@ class BottomToolbar extends Container { class: 'bottom-toolbar-tool' }); - const lasso = new Button({ - id: 'bottom-toolbar-lasso', - class: ['bottom-toolbar-tool', 'disabled'] - }); + // const lasso = new Button({ + // id: 'bottom-toolbar-lasso', + // class: ['bottom-toolbar-tool', 'disabled'] + // }); const sphere = new Button({ id: 'bottom-toolbar-sphere', class: 'bottom-toolbar-tool' }); - const crop = new Button({ - id: 'bottom-toolbar-crop', - class: ['bottom-toolbar-tool', 'disabled'] - }); + // const crop = new Button({ + // id: 'bottom-toolbar-crop', + // class: ['bottom-toolbar-tool', 'disabled'] + // }); const translate = new Button({ id: 'bottom-toolbar-translate', @@ -94,18 +94,18 @@ class BottomToolbar extends Container { picker.dom.appendChild(createSvg(pickerSvg)); brush.dom.appendChild(createSvg(brushSvg)); sphere.dom.appendChild(createSvg(sphereSvg)); - lasso.dom.appendChild(createSvg(lassoSvg)); - crop.dom.appendChild(createSvg(cropSvg)); + // lasso.dom.appendChild(createSvg(lassoSvg)); + // crop.dom.appendChild(createSvg(cropSvg)); this.append(undo); this.append(redo); this.append(new Element({ class: 'bottom-toolbar-separator' })); this.append(picker); this.append(brush); - this.append(lasso); + // this.append(lasso); this.append(new Element({ class: 'bottom-toolbar-separator' })); this.append(sphere); - this.append(crop); + // this.append(crop); this.append(new Element({ class: 'bottom-toolbar-separator' })); this.append(translate); this.append(rotate); @@ -143,9 +143,9 @@ class BottomToolbar extends Container { tooltips.register(redo, 'Redo'); tooltips.register(picker, 'Picker Select'); tooltips.register(brush, 'Brush Select'); - tooltips.register(lasso, 'Lasso Select'); + // tooltips.register(lasso, 'Lasso Select'); tooltips.register(sphere, 'Sphere Select'); - tooltips.register(crop, 'Crop'); + // tooltips.register(crop, 'Crop'); tooltips.register(translate, 'Translate'); tooltips.register(rotate, 'Rotate'); tooltips.register(scale, 'Scale'); diff --git a/src/ui/splat-list.scss b/src/ui/splat-list.scss index f10e2360..de6a1c60 100644 --- a/src/ui/splat-list.scss +++ b/src/ui/splat-list.scss @@ -36,13 +36,11 @@ flex-grow: 0; flex-shrink: 0; - padding: 4px; - width: 16px; - height: 16px; - line-height: 16px; + width: 24px; + height: 24px; + line-height: 24px; - color: black; - background-color: transparent; + color: $text-secondary; cursor: pointer; @@ -51,14 +49,9 @@ } &:hover { + color: $text-primary; background-color: $bcg-dark; } - - &::after { - font-family: pc-icon; - font-size: 16px; - content: '\E117'; - } } .splat-item-delete { @@ -75,12 +68,7 @@ cursor: pointer; &:hover { + color: $text-primary; background-color: $bcg-dark; } - - &::after { - font-family: pc-icon; - font-size: 14px; - content: '\E124'; - } } \ No newline at end of file diff --git a/src/ui/splat-list.ts b/src/ui/splat-list.ts index cd0f85fe..cf93f6d5 100644 --- a/src/ui/splat-list.ts +++ b/src/ui/splat-list.ts @@ -3,6 +3,15 @@ import { Events } from '../events'; import { Splat } from '../splat'; import { Element, ElementType } from '../element'; +import shownSvg from '../svg/shown.svg'; +import hiddenSvg from '../svg/hidden.svg'; +import deleteSvg from '../svg/delete.svg'; + +const createSvg = (svgString: string) => { + const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length)); + return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement; +}; + class SplatItem extends Container { getSelected: () => boolean; setSelected: (value: boolean) => void; @@ -24,15 +33,24 @@ class SplatItem extends Container { }); const visible = new PcuiElement({ + dom: createSvg(shownSvg), + class: 'splat-item-visible', + }); + + const invisible = new PcuiElement({ + dom: createSvg(hiddenSvg), class: 'splat-item-visible', + hidden: true }); const remove = new PcuiElement({ + dom: createSvg(deleteSvg), class: 'splat-item-delete' }); this.append(text); this.append(visible); + this.append(invisible); this.append(remove); this.getSelected = () => { @@ -57,6 +75,8 @@ class SplatItem extends Container { this.setVisible = (value: boolean) => { if (value !== this.visible) { + visible.hidden = !value; + invisible.hidden = value; if (value) { this.class.add('visible'); this.emit('visible', this); @@ -79,10 +99,12 @@ class SplatItem extends Container { // handle clicks visible.dom.addEventListener('click', toggleVisible, true); + invisible.dom.addEventListener('click', toggleVisible, true); remove.dom.addEventListener('click', handleRemove, true); this.destroy = () => { visible.dom.removeEventListener('click', toggleVisible, true); + invisible.dom.removeEventListener('click', toggleVisible, true); remove.dom.removeEventListener('click', handleRemove, true); } } From f48d4f335d728fc160b24058c8b12150fc679474 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Tue, 6 Aug 2024 16:55:22 +0100 Subject: [PATCH 27/33] small panel footer update --- src/ui/panel.scss | 2 +- src/ui/scene-panel.ts | 4 ++++ src/ui/transform.scss | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ui/panel.scss b/src/ui/panel.scss index f9e50fd6..a2cf5267 100644 --- a/src/ui/panel.scss +++ b/src/ui/panel.scss @@ -25,7 +25,7 @@ font-weight: bold; flex-grow: 1; } - + & > .panel-header-button { font-family: pc-icon; font-weight: bold; diff --git a/src/ui/scene-panel.ts b/src/ui/scene-panel.ts index 0d39ab34..5e447fae 100644 --- a/src/ui/scene-panel.ts +++ b/src/ui/scene-panel.ts @@ -89,6 +89,10 @@ class ScenePanel extends Container { this.append(splatList); this.append(transformHeader); this.append(new Transform(events)); + this.append(new Element({ + class: `panel-header`, + height: 20 + })); } } diff --git a/src/ui/transform.scss b/src/ui/transform.scss index 0a2b0092..edf3f815 100644 --- a/src/ui/transform.scss +++ b/src/ui/transform.scss @@ -5,7 +5,7 @@ background-color: $bcg-primary; - padding: 6px; + padding: 0px 6px 12px 6px; } .transform-row { From 9da8306e0fce03c2ccfca8dd90921a5b15e17d91 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Tue, 6 Aug 2024 17:37:20 +0100 Subject: [PATCH 28/33] add about popup --- src/ui/editor.ts | 8 ++++++++ src/ui/menu.ts | 2 +- src/ui/popup.scss | 8 ++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ui/editor.ts b/src/ui/editor.ts index b6238d35..7d0aafbf 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -144,6 +144,14 @@ class EditorUI { shortcutsPopup.hidden = false; }); + events.function('show.about', () => { + return this.popup.show({ + type: 'info', + header: 'About', + message: `SUPERSPLAT v${version}` + }); + }); + events.function('showPopup', (options: ShowOptions) => { return this.popup.show(options); }); diff --git a/src/ui/menu.ts b/src/ui/menu.ts index 1b6325ec..a1d02fc7 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -172,7 +172,7 @@ class Menu extends Container { const helpMenuPanel = new MenuPanel([{ text: 'About SuperSplat', icon: 'E138', - onSelect: () => events.fire('show.about') + onSelect: () => events.invoke('show.about') }, { text: 'Keyboard Shortcuts', icon: 'E136', diff --git a/src/ui/popup.scss b/src/ui/popup.scss index 0efe382a..c65e76a9 100644 --- a/src/ui/popup.scss +++ b/src/ui/popup.scss @@ -40,11 +40,13 @@ text-align: center; padding: 20px 0px; - color: $text-secondary; + color: $text-primary; &::before { font-family: 'pc-icon'; + font-size: 16px; margin: 0px 10px; + color: $text-primary; } &.error::before { @@ -54,17 +56,14 @@ &.info::before { content: '\E400'; - color: $text-primary; } &.yesno::before { content: '\E138'; - color: $text-primary; } &.okcancel::before { content: '\E138'; - color: $text-primary; } } @@ -85,6 +84,7 @@ background-color: $bcg-darker; &:hover { + color: $text-primary; background-color: $clr-hilight; } } From b6e3c5da65fd1920d08a2191eb4832e6ed966710 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Wed, 7 Aug 2024 11:33:33 +0100 Subject: [PATCH 29/33] sphere selection bugfix and dbl click to place, popup styling --- src/editor.ts | 4 +++- src/tools/sphere-selection.ts | 10 +++++++++- src/ui/popup.scss | 8 +++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/editor.ts b/src/editor.ts index 6a8758bf..ab825581 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -191,7 +191,9 @@ const registerEditorEvents = (events: Events, editHistory: EditHistory, scene: S const y = splatData.getProp('y'); const z = splatData.getProp('z'); - const radius2 = sphere[3] * sphere[3]; + splat.worldTransform.getScale(vec); + + const radius2 = (sphere[3] / vec.x) ** 2; vec.set(sphere[0], sphere[1], sphere[2]); mat.invert(splat.worldTransform); diff --git a/src/tools/sphere-selection.ts b/src/tools/sphere-selection.ts index 9cb1130e..cf8c1adb 100644 --- a/src/tools/sphere-selection.ts +++ b/src/tools/sphere-selection.ts @@ -1,8 +1,9 @@ -import { TranslateGizmo } from 'playcanvas'; +import { TranslateGizmo, Vec3 } from 'playcanvas'; import { Button, Container, NumericInput } from 'pcui'; import { Events } from '../events'; import { Scene } from '../scene'; import { SphereShape } from '../sphere-shape'; +import { Splat } from '../splat'; class SphereSelection { activate: () => void; @@ -59,6 +60,13 @@ class SphereSelection { removeButton.dom.addEventListener('pointerdown', (e) => { e.stopPropagation(); apply('remove'); }); radius.on('change', () => { sphere.radius = radius.value; }); + events.on('camera.focalPointPicked', (details: { splat: Splat, position: Vec3 }) => { + if (this.active) { + sphere.pivot.setPosition(details.position); + gizmo.attach([sphere.pivot]); + } + }); + this.activate = () => { this.active = true; scene.add(sphere); diff --git a/src/ui/popup.scss b/src/ui/popup.scss index c65e76a9..3bf40e04 100644 --- a/src/ui/popup.scss +++ b/src/ui/popup.scss @@ -9,7 +9,8 @@ position: absolute; left: 50%; top: 50%; - width: 320px; + min-width: 320px; + max-width: 480px; transform: translate(-50%, -50%); display: flex; @@ -38,7 +39,7 @@ #popup-text { text-wrap: wrap; text-align: center; - padding: 20px 0px; + padding: 20px 10px; color: $text-primary; @@ -68,7 +69,8 @@ } #popup-text-input { - margin-bottom: 24px; + width: 360px; + margin: 20px; } #popup-buttons { From 4302b2a2ee35cedf27d68f544a62a38b9ffe6982 Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Wed, 7 Aug 2024 12:00:32 +0100 Subject: [PATCH 30/33] styling, open pc site on icon click --- src/ui/bottom-toolbar.ts | 14 +++++++------- src/ui/menu.scss | 1 + src/ui/menu.ts | 15 +++++++++++---- src/ui/right-toolbar.scss | 4 ++-- src/ui/right-toolbar.ts | 6 +++--- src/ui/splat-list.scss | 2 -- 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/ui/bottom-toolbar.ts b/src/ui/bottom-toolbar.ts index 018d1712..b87c7522 100644 --- a/src/ui/bottom-toolbar.ts +++ b/src/ui/bottom-toolbar.ts @@ -139,16 +139,16 @@ class BottomToolbar extends Container { }); // register tooltips - tooltips.register(undo, 'Undo'); - tooltips.register(redo, 'Redo'); - tooltips.register(picker, 'Picker Select'); - tooltips.register(brush, 'Brush Select'); + tooltips.register(undo, 'Undo ( Ctrl + Z )'); + tooltips.register(redo, 'Redo ( Ctrl + Shift + Z )'); + tooltips.register(picker, 'Picker Select ( P )'); + tooltips.register(brush, 'Brush Select ( B )'); // tooltips.register(lasso, 'Lasso Select'); tooltips.register(sphere, 'Sphere Select'); // tooltips.register(crop, 'Crop'); - tooltips.register(translate, 'Translate'); - tooltips.register(rotate, 'Rotate'); - tooltips.register(scale, 'Scale'); + tooltips.register(translate, 'Translate ( 1 )'); + tooltips.register(rotate, 'Rotate ( 2 )'); + tooltips.register(scale, 'Scale ( 3 )'); tooltips.register(coordSpace, 'Local Space Gizmo'); } diff --git a/src/ui/menu.scss b/src/ui/menu.scss index 20ea5f59..6525257c 100644 --- a/src/ui/menu.scss +++ b/src/ui/menu.scss @@ -22,6 +22,7 @@ #menu-icon { width: 54px; font-family: 'pc-icon'; + cursor: pointer; } #menu-container { diff --git a/src/ui/menu.ts b/src/ui/menu.ts index a1d02fc7..0e2ee322 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -39,9 +39,16 @@ class Menu extends Container { event.stopPropagation(); }); - const icon = document.createElement('img'); - icon.setAttribute('id', 'menu-icon'); - icon.src = logoSvg; + const iconDom = document.createElement('img'); + iconDom.src = logoSvg; + iconDom.setAttribute('id', 'menu-icon'); + iconDom.addEventListener('pointerdown', (event) => { + window.open('https://playcanvas.com', '_blank').focus() + }); + + const icon = new Element({ + dom: iconDom + }); const scene = new Label({ text: 'Scene', @@ -65,7 +72,7 @@ class Menu extends Container { buttonsContainer.append(selection); buttonsContainer.append(help); - menubar.dom.appendChild(icon); + menubar.append(icon); menubar.append(buttonsContainer); const exportMenuPanel = new MenuPanel([{ diff --git a/src/ui/right-toolbar.scss b/src/ui/right-toolbar.scss index bda4e43f..3246e550 100644 --- a/src/ui/right-toolbar.scss +++ b/src/ui/right-toolbar.scss @@ -1,9 +1,9 @@ #right-toolbar { position: absolute; - right: 70px; + right: 24px; top: 50%; width: 54px; - transform: translate(50%, -50%); + transform: translate(0, -50%); padding: 8px 0px; border-radius: 8px; diff --git a/src/ui/right-toolbar.ts b/src/ui/right-toolbar.ts index a4bb078c..6d7da95c 100644 --- a/src/ui/right-toolbar.ts +++ b/src/ui/right-toolbar.ts @@ -47,9 +47,9 @@ class RightToolbar extends Container { this.append(new Element({ class: 'right-toolbar-separator' })); this.append(options); - tooltips.register(showHideSplats, 'Show/Hide Splats', 'left'); - tooltips.register(frameSelection, 'Frame Selection', 'left'); - tooltips.register(options, 'Options', 'left'); + tooltips.register(showHideSplats, 'Show/Hide Splats ( Space )', 'left'); + tooltips.register(frameSelection, 'Frame Selection ( F )', 'left'); + tooltips.register(options, 'View Options', 'left'); // add event handlers diff --git a/src/ui/splat-list.scss b/src/ui/splat-list.scss index de6a1c60..5aff230b 100644 --- a/src/ui/splat-list.scss +++ b/src/ui/splat-list.scss @@ -50,7 +50,6 @@ &:hover { color: $text-primary; - background-color: $bcg-dark; } } @@ -69,6 +68,5 @@ &:hover { color: $text-primary; - background-color: $bcg-dark; } } \ No newline at end of file From b173d7d579477a98f10dc3e9f3d3e850018a62ec Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Wed, 7 Aug 2024 15:00:19 +0100 Subject: [PATCH 31/33] add model toggel --- src/main.ts | 8 +---- src/svg/centers.svg | 3 ++ src/svg/rings.svg | 4 +++ src/ui/data-panel.ts | 2 -- src/ui/editor.ts | 3 ++ src/ui/mode-toggle.scss | 43 ++++++++++++++++++++++++++ src/ui/mode-toggle.ts | 63 +++++++++++++++++++++++++++++++++++++++ src/ui/right-toolbar.scss | 16 ++++++++-- src/ui/right-toolbar.ts | 29 ++++++++++++------ src/ui/style.scss | 1 + src/ui/view-panel.ts | 2 +- 11 files changed, 153 insertions(+), 21 deletions(-) create mode 100644 src/svg/centers.svg create mode 100644 src/svg/rings.svg create mode 100644 src/ui/mode-toggle.scss create mode 100644 src/ui/mode-toggle.ts diff --git a/src/main.ts b/src/main.ts index 718fa1f1..392525f1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -82,13 +82,7 @@ const initShortcuts = (events: Events) => { shortcuts.register(['Z', 'z'], { event: 'edit.redo', ctrl: true, shift: true }); shortcuts.register(['M', 'm'], { event: 'camera.toggleMode' }); shortcuts.register(['D', 'd'], { event: 'dataPanel.toggle' }); - - // space toggles between 0 and size - shortcuts.register([' '], { - func: () => { - events.fire('camera.toggleDebug'); - } - }); + shortcuts.register([' '], { event: 'camera.toggleDebug' }); return shortcuts; }; diff --git a/src/svg/centers.svg b/src/svg/centers.svg new file mode 100644 index 00000000..24832679 --- /dev/null +++ b/src/svg/centers.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/svg/rings.svg b/src/svg/rings.svg new file mode 100644 index 00000000..3a10b18e --- /dev/null +++ b/src/svg/rings.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/ui/data-panel.ts b/src/ui/data-panel.ts index bd8b32b4..26757080 100644 --- a/src/ui/data-panel.ts +++ b/src/ui/data-panel.ts @@ -100,8 +100,6 @@ class DataPanel extends Panel { id: 'data-controls' }); - controls.append(sepLabel('Histogram')); - const dataSelector = new SelectInput({ class: 'control-element-expand', defaultValue: 'surface-area', diff --git a/src/ui/editor.ts b/src/ui/editor.ts index 7d0aafbf..0ef4d11a 100644 --- a/src/ui/editor.ts +++ b/src/ui/editor.ts @@ -9,6 +9,7 @@ import { ScenePanel } from './scene-panel'; import { ViewPanel } from './view-panel'; import { BottomToolbar } from './bottom-toolbar'; import { RightToolbar } from './right-toolbar'; +import { ModeToggle } from './mode-toggle'; import { Tooltips } from './tooltips'; import { ShortcutsPopup } from './shortcuts-popup'; @@ -88,6 +89,7 @@ class EditorUI { const viewPanel = new ViewPanel(events, tooltips); const bottomToolbar = new BottomToolbar(events, tooltips); const rightToolbar = new RightToolbar(events, tooltips); + const modeToggle = new ModeToggle(events, tooltips); const menu = new Menu(events); canvasContainer.dom.appendChild(canvas); @@ -97,6 +99,7 @@ class EditorUI { canvasContainer.append(viewPanel); canvasContainer.append(bottomToolbar); canvasContainer.append(rightToolbar); + canvasContainer.append(modeToggle); canvasContainer.append(menu); // view axes container diff --git a/src/ui/mode-toggle.scss b/src/ui/mode-toggle.scss new file mode 100644 index 00000000..2921c4a2 --- /dev/null +++ b/src/ui/mode-toggle.scss @@ -0,0 +1,43 @@ + +#mode-toggle { + position: absolute; + left: calc(50% - 60px); + top: 24px; + width: 120px; + + padding: 0px 8px; + border-radius: 4px; + + background-color: $bcg-dark; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + cursor: pointer; + + &.centers-mode { + #rings-icon, #rings-text { + display: none; + } + } + + &.rings-mode { + #centers-icon, #centers-text { + display: none; + } + } + + #centers-icon { + color: $clr-hilight; + } + + #rings-icon { + color: $clr-hilight; + } + + &:hover { + color: $text-primary; + } +} \ No newline at end of file diff --git a/src/ui/mode-toggle.ts b/src/ui/mode-toggle.ts new file mode 100644 index 00000000..38308476 --- /dev/null +++ b/src/ui/mode-toggle.ts @@ -0,0 +1,63 @@ +import { Container, Element, Label } from 'pcui'; +import { Events } from '../events'; +import { Tooltips } from './tooltips'; + +import centersSvg from '../svg/centers.svg'; +import ringsSvg from '../svg/rings.svg'; + +const createSvg = (svgString: string) => { + const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length)); + return new DOMParser().parseFromString(decodedStr, 'image/svg+xml').documentElement; +}; + +class ModeToggle extends Container { + constructor(events: Events, tooltips: Tooltips, args = {}) { + args = { + id: 'mode-toggle', + class: 'centers-mode', + ...args + }; + + super(args); + + const centersIcon = new Element({ + id: 'centers-icon', + dom: createSvg(centersSvg) + }); + + const ringsIcon = new Element({ + id: 'rings-icon', + dom: createSvg(ringsSvg) + }); + + const centersText = new Label({ + id: 'centers-text', + text: 'Centers' + }); + + const ringsText = new Label({ + id: 'rings-text', + text: 'Rings' + }); + + this.append(centersIcon); + this.append(ringsIcon); + this.append(centersText); + this.append(ringsText); + + this.dom.addEventListener('pointerdown', (event) => { + event.stopPropagation(); + events.fire('camera.toggleMode'); + + }); + + events.on('camera.mode', (mode: string) => { + this.class[mode === 'centers' ? 'add' : 'remove']('centers-mode'); + this.class[mode === 'rings' ? 'add' : 'remove']('rings-mode'); + }); + + tooltips.register(this, 'Toggle Mode ( M )'); + } +} + +export { ModeToggle }; diff --git a/src/ui/right-toolbar.scss b/src/ui/right-toolbar.scss index 3246e550..39a24919 100644 --- a/src/ui/right-toolbar.scss +++ b/src/ui/right-toolbar.scss @@ -13,6 +13,18 @@ flex-direction: column; align-items: center; + #right-toolbar-mode-toggle { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + svg { + width: 20px; + height: 20px; + } + } + &>.right-toolbar-button, .right-toolbar-tool, .right-toolbar-toggle { width: 38px; height: 38px; @@ -33,8 +45,8 @@ &>.right-toolbar-separator { width: 38px; - height: 1px; - margin: 8px 0px; + height: 2px; + margin: 10px 0px; background-color: $bcg-primary; } diff --git a/src/ui/right-toolbar.ts b/src/ui/right-toolbar.ts index 6d7da95c..70b306d1 100644 --- a/src/ui/right-toolbar.ts +++ b/src/ui/right-toolbar.ts @@ -1,9 +1,11 @@ -import { Button, Container, Element } from 'pcui'; +import { Button, Container, Element, Label } from 'pcui'; import { Events } from '../events'; import { Tooltips } from './tooltips'; import showHideSplatsSvg from '../svg/show-hide-splats.svg'; import frameSelectionSvg from '../svg/frame-selection.svg'; +import centersSvg from '../svg/centers.svg'; +import ringsSvg from '../svg/rings.svg'; const createSvg = (svgString: string) => { const decodedStr = decodeURIComponent(svgString.substring('data:image/svg+xml,'.length)); @@ -23,6 +25,11 @@ class RightToolbar extends Container { event.stopPropagation(); }); + const ringsModeToggle = new Button({ + id: 'right-toolbar-mode-toggle', + class: 'right-toolbar-toggle' + }); + const showHideSplats = new Button({ id: 'right-toolbar-show-hide', class: ['right-toolbar-toggle', 'active'] @@ -39,35 +46,39 @@ class RightToolbar extends Container { icon: 'E283' }); + ringsModeToggle.dom.appendChild(createSvg(ringsSvg)); showHideSplats.dom.appendChild(createSvg(showHideSplatsSvg)); frameSelection.dom.appendChild(createSvg(frameSelectionSvg)); + this.append(ringsModeToggle); this.append(showHideSplats); + this.append(new Element({ class: 'right-toolbar-separator' })); this.append(frameSelection); this.append(new Element({ class: 'right-toolbar-separator' })); this.append(options); + tooltips.register(ringsModeToggle, 'Toggle Mode ( M )', 'left'); tooltips.register(showHideSplats, 'Show/Hide Splats ( Space )', 'left'); tooltips.register(frameSelection, 'Frame Selection ( F )', 'left'); tooltips.register(options, 'View Options', 'left'); // add event handlers - options.on('click', () => events.fire('viewPanel.toggleVisible')); + ringsModeToggle.on('click', () => events.fire('camera.toggleMode')); + showHideSplats.on('click', () => events.fire('camera.toggleDebug')); frameSelection.on('click', () => events.fire('camera.focus')); + options.on('click', () => events.fire('viewPanel.toggleVisible')); - events.on('viewPanel.visible', (visible: boolean) => { - options.class[visible ? 'add' : 'remove']('active'); + events.on('camera.mode', (mode: string) => { + ringsModeToggle.class[mode === 'rings' ? 'add' : 'remove']('active'); }); - // show-hide splats - events.on('camera.debug', (debug: boolean) => { - showHideSplats.dom.classList[debug ? 'add' : 'remove']('active'); + showHideSplats.class[debug ? 'add' : 'remove']('active'); }); - showHideSplats.dom.addEventListener('click', () => { - events.fire('camera.toggleDebug'); + events.on('viewPanel.visible', (visible: boolean) => { + options.class[visible ? 'add' : 'remove']('active'); }); } } diff --git a/src/ui/style.scss b/src/ui/style.scss index 46b1dab5..b119fd15 100644 --- a/src/ui/style.scss +++ b/src/ui/style.scss @@ -21,6 +21,7 @@ $bcg-darkest: #181818; @import 'select-toolbar.scss'; @import 'data-panel.scss'; @import 'popup.scss'; +@import 'mode-toggle.scss'; * { font-size: 12px; diff --git a/src/ui/view-panel.ts b/src/ui/view-panel.ts index 76160231..c48065df 100644 --- a/src/ui/view-panel.ts +++ b/src/ui/view-panel.ts @@ -82,7 +82,7 @@ class ViewPanel extends Container { }); const splatSizeLabel = new Label({ - text: 'Splat Size', + text: 'Centers Size', class: 'view-panel-row-label' }); From 44072a9c565533fcddda9fbab3cac809decdcd5b Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Wed, 7 Aug 2024 15:33:44 +0100 Subject: [PATCH 32/33] v1.0.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1ec9f962..c64e2690 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "supersplat", - "version": "1.0.0-alpha.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "supersplat", - "version": "1.0.0-alpha.0", + "version": "1.0.0", "license": "MIT", "devDependencies": { "@playcanvas/eslint-config": "^1.7.4", diff --git a/package.json b/package.json index 5ca4bd4f..cdf7e433 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "supersplat", - "version": "1.0.0-alpha.0", + "version": "1.0.0", "author": "PlayCanvas", "homepage": "https://playcanvas.com/supersplat/editor", "description": "3D Gaussian Splat Editor", From 80e6cc4e7199da21df93ad31c66607a3dd21cf4d Mon Sep 17 00:00:00 2001 From: Donovan Hutchence Date: Thu, 8 Aug 2024 10:27:46 +0100 Subject: [PATCH 33/33] final tweaks --- src/controllers.ts | 4 ++- src/ui/mode-toggle.scss | 4 +-- src/ui/mode-toggle.ts | 8 ++--- src/ui/right-toolbar.ts | 16 ++++++++-- src/ui/view-panel.ts | 65 ++--------------------------------------- 5 files changed, 25 insertions(+), 72 deletions(-) diff --git a/src/controllers.ts b/src/controllers.ts index 132aaf1b..54153a02 100644 --- a/src/controllers.ts +++ b/src/controllers.ts @@ -142,7 +142,9 @@ class PointerController { }; const dblclick = (event: globalThis.MouseEvent) => { - camera.pickFocalPoint(event.offsetX, event.offsetY); + if (event.target === target) { + camera.pickFocalPoint(event.offsetX, event.offsetY); + } }; // key state diff --git a/src/ui/mode-toggle.scss b/src/ui/mode-toggle.scss index 2921c4a2..242eb22d 100644 --- a/src/ui/mode-toggle.scss +++ b/src/ui/mode-toggle.scss @@ -2,11 +2,11 @@ #mode-toggle { position: absolute; left: calc(50% - 60px); - top: 24px; + top: 0px; width: 120px; padding: 0px 8px; - border-radius: 4px; + border-radius: 0px 0px 8px 8px; background-color: $bcg-dark; diff --git a/src/ui/mode-toggle.ts b/src/ui/mode-toggle.ts index 38308476..6c982d2e 100644 --- a/src/ui/mode-toggle.ts +++ b/src/ui/mode-toggle.ts @@ -32,12 +32,12 @@ class ModeToggle extends Container { const centersText = new Label({ id: 'centers-text', - text: 'Centers' + text: 'Centers mode' }); const ringsText = new Label({ id: 'rings-text', - text: 'Rings' + text: 'Rings mode' }); this.append(centersIcon); @@ -48,7 +48,7 @@ class ModeToggle extends Container { this.dom.addEventListener('pointerdown', (event) => { event.stopPropagation(); events.fire('camera.toggleMode'); - + events.fire('camera.setDebug', true); }); events.on('camera.mode', (mode: string) => { @@ -56,7 +56,7 @@ class ModeToggle extends Container { this.class[mode === 'rings' ? 'add' : 'remove']('rings-mode'); }); - tooltips.register(this, 'Toggle Mode ( M )'); + tooltips.register(this, 'Splat Mode ( M )'); } } diff --git a/src/ui/right-toolbar.ts b/src/ui/right-toolbar.ts index 70b306d1..718ce45a 100644 --- a/src/ui/right-toolbar.ts +++ b/src/ui/right-toolbar.ts @@ -46,7 +46,12 @@ class RightToolbar extends Container { icon: 'E283' }); - ringsModeToggle.dom.appendChild(createSvg(ringsSvg)); + const centersDom = createSvg(centersSvg); + const ringsDom = createSvg(ringsSvg); + ringsDom.style.display = 'none'; + + ringsModeToggle.dom.appendChild(centersDom); + ringsModeToggle.dom.appendChild(ringsDom); showHideSplats.dom.appendChild(createSvg(showHideSplatsSvg)); frameSelection.dom.appendChild(createSvg(frameSelectionSvg)); @@ -57,20 +62,25 @@ class RightToolbar extends Container { this.append(new Element({ class: 'right-toolbar-separator' })); this.append(options); - tooltips.register(ringsModeToggle, 'Toggle Mode ( M )', 'left'); + tooltips.register(ringsModeToggle, 'Splat Mode ( M )', 'left'); tooltips.register(showHideSplats, 'Show/Hide Splats ( Space )', 'left'); tooltips.register(frameSelection, 'Frame Selection ( F )', 'left'); tooltips.register(options, 'View Options', 'left'); // add event handlers - ringsModeToggle.on('click', () => events.fire('camera.toggleMode')); + ringsModeToggle.on('click', () => { + events.fire('camera.toggleMode'); + events.fire('camera.setDebug', true); + }); showHideSplats.on('click', () => events.fire('camera.toggleDebug')); frameSelection.on('click', () => events.fire('camera.focus')); options.on('click', () => events.fire('viewPanel.toggleVisible')); events.on('camera.mode', (mode: string) => { ringsModeToggle.class[mode === 'rings' ? 'add' : 'remove']('active'); + centersDom.style.display = mode === 'rings' ? 'none' : 'block'; + ringsDom.style.display = mode === 'rings' ? 'block' : 'none'; }); events.on('camera.debug', (debug: boolean) => { diff --git a/src/ui/view-panel.ts b/src/ui/view-panel.ts index c48065df..621e9886 100644 --- a/src/ui/view-panel.ts +++ b/src/ui/view-panel.ts @@ -36,46 +36,7 @@ class ViewPanel extends Container { header.append(icon); header.append(label); - // rings mode - - const ringsModeRow = new Container({ - class: 'view-panel-row' - }); - - const ringsModeLabel = new Label({ - text: 'Rings Mode', - class: 'view-panel-row-label' - }); - - const ringsModeToggle = new BooleanInput({ - type: 'toggle', - class: 'view-panel-row-toggle' - }); - - ringsModeRow.append(ringsModeLabel); - ringsModeRow.append(ringsModeToggle); - - // show splats - - const showSplatsRow = new Container({ - class: 'view-panel-row' - }); - - const showSplatsLabel = new Label({ - text: 'Show Splats', - class: 'view-panel-row-label' - }); - - const showSplatsToggle = new BooleanInput({ - type: 'toggle', - class: 'view-panel-row-toggle', - value: true - }); - - showSplatsRow.append(showSplatsLabel); - showSplatsRow.append(showSplatsToggle); - - // splat size + // centers size const splatSizeRow = new Container({ class: 'view-panel-row' @@ -118,8 +79,6 @@ class ViewPanel extends Container { showGridRow.append(showGridToggle); this.append(header); - this.append(ringsModeRow); - this.append(showSplatsRow); this.append(splatSizeRow); this.append(showGridRow); @@ -144,26 +103,6 @@ class ViewPanel extends Container { setVisible(this.hidden); }); - // rings mode - - events.on('camera.mode', (mode: string) => { - ringsModeToggle.value = mode === 'rings'; - }); - - ringsModeToggle.on('change', () => { - events.fire('camera.setMode', ringsModeToggle.value ? 'rings' : 'centers'); - }); - - // show splats - - events.on('camera.debug', (debug: boolean) => { - showSplatsToggle.value = debug; - }); - - showSplatsToggle.on('change', () => { - events.fire('camera.setDebug', showSplatsToggle.value); - }); - // splat size events.on('camera.splatSize', (value: number) => { @@ -172,6 +111,8 @@ class ViewPanel extends Container { splatSizeSlider.on('change', (value: number) => { events.fire('camera.setSplatSize', value); + events.fire('camera.setDebug', true); + events.fire('camera.setMode', 'centers'); }); // show grid