From 8d38b7fc8b06f659d8769837b1b590693a819a4c Mon Sep 17 00:00:00 2001 From: hobbes6-2 Date: Wed, 26 Aug 2026 13:13:18 +0530 Subject: [PATCH 01/10] init --- src/Editor/Editor.jsx | 55 +++++++++ .../MobileContainer/MobileContainer.jsx | 1 + .../PopOuts/ModifiersPanel/ModifiersPanel.jsx | 110 ++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx diff --git a/src/Editor/Editor.jsx b/src/Editor/Editor.jsx index 7d9594408..76f124764 100644 --- a/src/Editor/Editor.jsx +++ b/src/Editor/Editor.jsx @@ -52,6 +52,7 @@ import AssetLibrary from './Panels/AssetLibrary/AssetLibrary'; import Outliner from './Panels/Outliner/Outliner'; import OutlinerExpandButton from './Panels/OutlinerExpandButton/OutlinerExpandButton'; import WickCodeEditor from './PopOuts/WickCodeEditor/WickCodeEditor'; +import ModifiersPanel from './PopOuts/ModifiersPanel/ModifiersPanel'; import EditorWrapper from './EditorWrapper'; @@ -171,6 +172,7 @@ class Editor extends EditorCore { activeModalName: window.localStorage.skipWelcomeMessage ? null : "WelcomeMessage", activeModalQueue: [], codeEditorOpen: false, + modifiersPanelOpen: false, scriptToEdit: "default", showCanvasActions: false, showBooleanCanvasActions: false, @@ -284,6 +286,7 @@ class Editor extends EditorCore { onStopAssetLibraryResize: throttle(this.onStopAssetLibraryResize, this.resizeThrottleAmount), onStopTimelineResize: throttle(this.onStopTimelineResize, this.resizeThrottleAmount), onStopCodeEditorResize: throttle(this.onStopCodeEditorResize, this.resizeThrottleAmount), + onStopModifiersPanelResize: throttle(this.onStopModifiersPanelResize, this.resizeThrottleAmount), onResize: throttle(this.onResize, this.resizeThrottleAmount), onWindowResize: throttle(this.onWindowResize, this.windowResizeThrottleAmount), }; @@ -342,6 +345,7 @@ class Editor extends EditorCore { ...this.state, project: this.project.serialize(), codeEditorWindowProperties: this.getDefaultCodeEditorProperties(), + modifiersPanelWindowProperties: this.getDefaultModifiersPanelProperties(), }); // Leave Page warning. @@ -541,6 +545,7 @@ class Editor extends EditorCore { // reset the code window if we resize the window. this.setState({ codeEditorWindowProperties: this.getDefaultCodeEditorProperties(), + modifiersPanelWindowProperties: this.getDefaultModifiersPanelProperties(), }); // re-render project to avoid incorrect pan @@ -565,6 +570,20 @@ class Editor extends EditorCore { ); } + getDefaultModifiersPanelProperties = () => { + return ( + { + width: window.innerWidth - 510, // magic number: outliner/inspector width + height: window.innerHeight - 260, // magic number: toolbar/timeline height + x: 0, + y: 40, // magic number: toolbar height (not including the menubar, since for some reason y=0 is at the bottom of the menubar?) + minWidth: 400, + minHeight: 250, + fontSize: 16, + } + ); + } + updateLastColors = (color, edit) => { let newArray = this.state.lastColorsUsed.concat([]); // make a deep copy. @@ -630,6 +649,21 @@ class Editor extends EditorCore { }); } + /** + * Updates the modifiers panel properties in the state. + * @param {object} newProperties object with new modifiers panel properties. Can include width, height, x, y. + */ + updateModifiersPanelWindowProperties = (newProperties) => { + let finalProperties = this.state.modifiersPanelWindowProperties; + Object.keys(newProperties).forEach(key => { + finalProperties[key] = newProperties[key]; + }); + + this.setState({ + modifiersPanelWindowProperties: finalProperties, + }); + } + /** * Called when any script is updated. @@ -751,6 +785,20 @@ class Editor extends EditorCore { }); } + /** + * Opens and closes the modifiers panel depending on the state of the panel. + * @param {boolean} state - Optional. True will open the modifiers panel, false will close. + */ + toggleModifiersPanel = (state) => { + if (state === undefined || (typeof state !== "boolean")) { + state = !this.state.modifiersPanelOpen; + } + + this.setState({ + modifiersPanelOpen: state, + }); + } + /** * Opens and closes the canvas actions popover. * @param {boolean} state - Optional. True will open the canvas actions menu, false will close. @@ -1459,6 +1507,13 @@ class Editor extends EditorCore { setConsoleLogs={this.setConsoleLogs} renderSize={renderSize} />} + {this.state.modifiersPanelOpen && + } diff --git a/src/Editor/Panels/MobileContainer/MobileContainer.jsx b/src/Editor/Panels/MobileContainer/MobileContainer.jsx index 19db41eb9..2518d86bf 100644 --- a/src/Editor/Panels/MobileContainer/MobileContainer.jsx +++ b/src/Editor/Panels/MobileContainer/MobileContainer.jsx @@ -139,6 +139,7 @@ class MobileContainer extends Component { tabs={[{label: "timeline", icon: timelineIcon, iconActive: timelineIconActive, iconAlt: "timeline icon"}, {label: "inspector", icon: inspectorIcon, iconActive: inspectorIconActive, iconAlt: "inspector icon"}, {label: "code", icon: codeIcon, iconActive: codeIconActive, iconAlt: "code editor icon"}, + {label: "modifiers", icon: modifiersIcon, iconActive: modifiersIconActive, iconAlt: "modifiers panel icon"}, {label: "asset", icon: assetIcon, iconActive: assetIconActive, iconAlt: "asset library icon"} ]}> {this.renderTimeline(this.props)} diff --git a/src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx b/src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx new file mode 100644 index 000000000..b53a65425 --- /dev/null +++ b/src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx @@ -0,0 +1,110 @@ +/* + * Copyright 2026 Candlestickers + * + * This file is part of Candlestick. + * + * Candlestick is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Candlestick is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Candlestick. If not, see . + */ + +import { ReflexContainer, ReflexElement } from 'react-reflex' +import { Rnd } from 'react-rnd'; +import ActionButton from 'Editor/Util/ActionButton/ActionButton'; + +import 'Editor/styles/PopOuts/_wickcodeeditor.css'; + +export default function ModifiersPanel(props) { + + /** + * To be called when the modifiers popout is repositioned. + */ + function onDragHandler(_e, d) { + props.updateModifiersPanelWindowProperties({ + x: d.x, + y: d.y, + }); + } + + /** + * To be called when the modifiers popout is resized. + */ + function onResizeHandler(_e, _dir, ref, _delta, _position) { + props.updateModifiersPanelWindowProperties({ + width: ref.style.width, + height: ref.style.height, + }); + updateEditorLines(); + } + + if (props.renderSize === 'small') { + return ( + +
+
+
+ Modifier Keys +
+ +
+ +
+ {renderModifiersPanel()} +
+
+
) + } + else return ( + + +
+
{""}
+
Modifiers Panel
+ +
+ +
+
+ + + {renderModifiersPanel()} + + +
+
+
+ ) +} \ No newline at end of file From b0462a9e40247459f0c0007af1cd5273c56d86e9 Mon Sep 17 00:00:00 2001 From: hobbes6-2 Date: Wed, 26 Aug 2026 16:32:35 +0530 Subject: [PATCH 02/10] runnability --- package-lock.json | 359 ++++++++++++++---- package.json | 2 +- .../MobileContainer/MobileContainer.jsx | 2 +- 3 files changed, 286 insertions(+), 77 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0dfbdae63..de55503b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "react-modal": "^3.16.3", "react-numeric-input": "^2.2.3", "react-reflex": "^3.0.6", - "react-resize-detector": "^12.3.0", + "react-resize-detector": "^9.1.1", "react-rnd": "^10.5.2", "react-scripts": "^5.0.1", "react-select": "^5.9.0", @@ -2498,6 +2498,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@emotion/babel-plugin/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/@emotion/cache": { "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", @@ -3085,6 +3094,18 @@ "node": ">=8.6" } }, + "node_modules/@jest/core/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@jest/core/node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3333,6 +3354,18 @@ "node": ">=8.6" } }, + "node_modules/@jest/transform/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@jest/transform/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3766,7 +3799,6 @@ "version": "2.5.6", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3806,7 +3838,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3827,7 +3858,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3848,7 +3878,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3869,7 +3898,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3890,7 +3918,6 @@ "cpu": [ "arm" ], - "dev": true, "libc": [ "glibc" ], @@ -3914,7 +3941,6 @@ "cpu": [ "arm" ], - "dev": true, "libc": [ "musl" ], @@ -3938,7 +3964,6 @@ "cpu": [ "arm64" ], - "dev": true, "libc": [ "glibc" ], @@ -3962,7 +3987,6 @@ "cpu": [ "arm64" ], - "dev": true, "libc": [ "musl" ], @@ -3986,7 +4010,6 @@ "cpu": [ "x64" ], - "dev": true, "libc": [ "glibc" ], @@ -4010,7 +4033,6 @@ "cpu": [ "x64" ], - "dev": true, "libc": [ "musl" ], @@ -4034,7 +4056,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4055,7 +4076,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4076,7 +4096,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4090,20 +4109,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.17.tgz", @@ -4258,6 +4263,18 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "license": "MIT" }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -4467,6 +4484,15 @@ "node": ">=10" } }, + "node_modules/@svgr/core/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/@svgr/hast-util-to-babel-ast": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", @@ -4536,6 +4562,15 @@ "node": ">=10" } }, + "node_modules/@svgr/plugin-svgo/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/@svgr/webpack": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", @@ -5070,6 +5105,16 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.12", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", @@ -5079,6 +5124,13 @@ "@types/react": "*" } }, + "node_modules/@types/react/node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT", + "peer": true + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -6822,6 +6874,15 @@ "node": ">=10" } }, + "node_modules/babel-preset-react-app/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -7996,6 +8057,15 @@ "node": ">=8" } }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -8336,6 +8406,15 @@ "postcss": "^8.2.15" } }, + "node_modules/cssnano/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/csso": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", @@ -8715,7 +8794,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, "license": "Apache-2.0", "optional": true, "engines": { @@ -9348,16 +9426,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-toolkit": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz", - "integrity": "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==", - "license": "MIT", - "workspaces": [ - "docs", - "benchmarks" - ] - }, "node_modules/es5-ext": { "version": "0.10.64", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", @@ -9954,6 +10022,18 @@ "node": ">=8.6" } }, + "node_modules/eslint-webpack-plugin/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/eslint-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -10593,6 +10673,18 @@ "node": ">=8.6" } }, + "node_modules/fast-glob/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/fast-glob/node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11246,6 +11338,18 @@ "node": ">=0.12.0" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -12550,6 +12654,18 @@ "node": ">=8.6" } }, + "node_modules/http-proxy-middleware/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/http-proxy-middleware/node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -12649,7 +12765,7 @@ "version": "5.1.9", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/import-fresh": { @@ -13928,6 +14044,18 @@ "node": ">=8.6" } }, + "node_modules/jest-config/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-config/node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14126,6 +14254,18 @@ "node": ">=8.6" } }, + "node_modules/jest-haste-map/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-haste-map/node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14260,6 +14400,18 @@ "node": ">=8.6" } }, + "node_modules/jest-message-util/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-message-util/node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14486,6 +14638,18 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-validate": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", @@ -14760,6 +14924,18 @@ "node": ">=8.6" } }, + "node_modules/jest-watch-typeahead/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-watch-typeahead/node_modules/pretty-format": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", @@ -14913,6 +15089,13 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "license": "MIT", + "peer": true + }, "node_modules/js-binary-schema-parser": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz", @@ -16208,7 +16391,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, "license": "MIT", "optional": true }, @@ -17018,12 +17200,12 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -17822,6 +18004,15 @@ "node": ">=10" } }, + "node_modules/postcss-loader/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/postcss-logical": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", @@ -19278,15 +19469,16 @@ } }, "node_modules/react-resize-detector": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-12.3.0.tgz", - "integrity": "sha512-mIDOVrTHKGnKe6qEUWi8dFdfHM5CPyTOpqoHctdMQf89Ljm/0qqDIzkP3vTRZZJi9/raaMiRxDEOqO4you5x+A==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-9.1.1.tgz", + "integrity": "sha512-siLzop7i4xIvZIACE/PHTvRegA8QRCEt0TfmvJ/qCIFQJ4U+3NuYcF8tNDmDWxfIn+X1eNCyY2rauH4KRxge8w==", "license": "MIT", "dependencies": { - "es-toolkit": "^1.39.6" + "lodash": "^4.17.21" }, "peerDependencies": { - "react": "^18.0.0 || ^19.0.0" + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/react-rnd": { @@ -20514,7 +20706,7 @@ "version": "1.101.0", "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.0.tgz", "integrity": "sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "chokidar": "^5.0.0", @@ -20573,7 +20765,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "readdirp": "^5.0.0" @@ -20589,7 +20781,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -22462,6 +22654,18 @@ "node": ">=8.6" } }, + "node_modules/tailwindcss/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tailwindcss/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -22750,18 +22954,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -23163,6 +23355,20 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/ua-parser-js": { "version": "1.0.41", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", @@ -24056,6 +24262,18 @@ "node": ">=0.12.0" } }, + "node_modules/webpack-dev-server/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/webpack-dev-server/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -24766,15 +24984,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, - "node_modules/yaml": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, "node_modules/yargs": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", diff --git a/package.json b/package.json index 0020c9524..d842969e4 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "react-modal": "^3.16.3", "react-numeric-input": "^2.2.3", "react-reflex": "^3.0.6", - "react-resize-detector": "^12.3.0", + "react-resize-detector": "^9.1.1", "react-rnd": "^10.5.2", "react-scripts": "^5.0.1", "react-select": "^5.9.0", diff --git a/src/Editor/Panels/MobileContainer/MobileContainer.jsx b/src/Editor/Panels/MobileContainer/MobileContainer.jsx index 2518d86bf..f459a51a3 100644 --- a/src/Editor/Panels/MobileContainer/MobileContainer.jsx +++ b/src/Editor/Panels/MobileContainer/MobileContainer.jsx @@ -139,7 +139,7 @@ class MobileContainer extends Component { tabs={[{label: "timeline", icon: timelineIcon, iconActive: timelineIconActive, iconAlt: "timeline icon"}, {label: "inspector", icon: inspectorIcon, iconActive: inspectorIconActive, iconAlt: "inspector icon"}, {label: "code", icon: codeIcon, iconActive: codeIconActive, iconAlt: "code editor icon"}, - {label: "modifiers", icon: modifiersIcon, iconActive: modifiersIconActive, iconAlt: "modifiers panel icon"}, + {label: "modifiers", icon: /*modifiersIcon*/codeIcon, iconActive: /*modifiersIconActive*/codeIconActive, iconAlt: "modifiers panel icon"}, {label: "asset", icon: assetIcon, iconActive: assetIconActive, iconAlt: "asset library icon"} ]}> {this.renderTimeline(this.props)} From 7eaa1f8a74c021f86b9a62bfa4139ffc207c44d1 Mon Sep 17 00:00:00 2001 From: hobbes6-2 Date: Thu, 3 Sep 2026 21:44:05 +0530 Subject: [PATCH 03/10] cleanup, todo list --- .../PopOuts/ModifiersPanel/ModifiersPanel.jsx | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx b/src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx index b53a65425..981cadd25 100644 --- a/src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx +++ b/src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx @@ -17,12 +17,20 @@ * along with Candlestick. If not, see . */ -import { ReflexContainer, ReflexElement } from 'react-reflex' import { Rnd } from 'react-rnd'; import ActionButton from 'Editor/Util/ActionButton/ActionButton'; import 'Editor/styles/PopOuts/_wickcodeeditor.css'; +// TODO: make it testable +/* +What we must do to intercept mouseclicks etc: +1 - Intercept. +2 - Stop propagation. +3 - do: e = new MouseEvent('mouseup', {...e, ctrlKey: this.ctrl, shiftKey: this.shift, altKey: this.alt, metaKey: this.meta}); +4 - Let it continue to the editor. +*/ + export default function ModifiersPanel(props) { /** @@ -43,7 +51,6 @@ export default function ModifiersPanel(props) { width: ref.style.width, height: ref.style.height, }); - updateEditorLines(); } if (props.renderSize === 'small') { @@ -68,10 +75,6 @@ export default function ModifiersPanel(props) { icon="cancel-white" action={props.toggleModifiersPanel} /> - -
- {renderModifiersPanel()} -
) } @@ -95,16 +98,6 @@ export default function ModifiersPanel(props) { icon="cancel-white" action={props.toggleModifiersPanel} /> - -
-
- - - {renderModifiersPanel()} - - -
-
) } \ No newline at end of file From 4fabaff0c85b249e6cdcfae1d261522a6680402f Mon Sep 17 00:00:00 2001 From: hobbes6-2 Date: Fri, 4 Sep 2026 18:32:09 +0530 Subject: [PATCH 04/10] functionality --- engine/src/tools/PathCursor.js | 2 +- src/Editor/Editor.jsx | 30 ++++ src/Editor/EditorWrapper.jsx | 44 +++++- src/Editor/Panels/MenuBar/MenuBar.jsx | 10 ++ .../PopOuts/ModifiersPanel/ModifiersPanel.jsx | 36 ++++- .../styles/PopOuts/_modifierspanel.scss | 128 ++++++++++++++++++ 6 files changed, 240 insertions(+), 10 deletions(-) create mode 100644 src/Editor/styles/PopOuts/_modifierspanel.scss diff --git a/engine/src/tools/PathCursor.js b/engine/src/tools/PathCursor.js index 4a037e15a..7d2d9cd2f 100644 --- a/engine/src/tools/PathCursor.js +++ b/engine/src/tools/PathCursor.js @@ -122,7 +122,7 @@ Wick.Tools.PathCursor = class extends Wick.Tool { onDoubleClick (e) { this.hitResult = this._updateHitResult(e); - if (this.detailedEditing == null) { + if (this.detailedEditing == null && this.hitResult.item) { // If detailed editing is off, turn it on for this path. this.detailedEditing = this.hitResult.item; this.detailedEditing.setFullySelected(true); diff --git a/src/Editor/Editor.jsx b/src/Editor/Editor.jsx index 76f124764..3bbb1cc27 100644 --- a/src/Editor/Editor.jsx +++ b/src/Editor/Editor.jsx @@ -164,6 +164,7 @@ class Editor extends EditorCore { this.project = null; this.paper = null; this.editorVersion = version + ''; + this.modifierKeys = { ctrlKey: false, altKey: false, shiftKey: false }; // GUI state this.state = { @@ -799,6 +800,32 @@ class Editor extends EditorCore { }); } + // Maps our modifier key names to the DOM/KeyboardEvent.key values Paper.js's + // Key module listens for (see engine's paper.js Key.modifiers tracking). + static MODIFIER_KEY_DOM_NAMES = { ctrl: 'Control', alt: 'Alt', shift: 'Shift' }; + + /** + * Toggles a modifier key on or off. Toggled-on keys are applied to every mouse event + * dispatched through the editor (see EditorWrapper's onMouseDown), and are also reflected + * as real keydown/keyup events so Paper.js tools (which track modifiers via their own + * Key module, not the mouse event) pick up the change too. + * @param {string} key - The key to toggle. Should be one of 'ctrl', 'alt', or 'shift'. + */ + toggleModifierKey = (key) => { + let propName = key.toLowerCase() + 'Key'; + let isDown = this.modifierKeys[propName] = !this.modifierKeys[propName]; + + let domKey = Editor.MODIFIER_KEY_DOM_NAMES[key.toLowerCase()]; + document.dispatchEvent(new KeyboardEvent(isDown ? 'keydown' : 'keyup', { + key: domKey, + bubbles: true, + cancelable: true, + [propName]: isDown, + })); + + this.forceUpdate(); + } + /** * Opens and closes the canvas actions popover. * @param {boolean} state - Optional. True will open the canvas actions menu, false will close. @@ -1199,6 +1226,7 @@ class Editor extends EditorCore { toast={this.toast} openExportMedia={() => { this.openModal('ExportMedia') }} openExportOptions={() => { this.openModal('ExportOptions') }} + toggleModifiersPanel={this.toggleModifiersPanel} /> @@ -1512,7 +1540,9 @@ class Editor extends EditorCore { modifiersPanelWindowProperties={this.state.modifiersPanelWindowProperties} updateModifiersPanelWindowProperties={this.updateModifiersPanelWindowProperties} toggleModifiersPanel={this.toggleModifiersPanel} + toggleModifierKey={this.toggleModifierKey} renderSize={renderSize} + keys={this.modifierKeys} />} diff --git a/src/Editor/EditorWrapper.jsx b/src/Editor/EditorWrapper.jsx index 84aebb8d7..3628046a3 100644 --- a/src/Editor/EditorWrapper.jsx +++ b/src/Editor/EditorWrapper.jsx @@ -40,11 +40,51 @@ export default function EditorWrapper(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []) + useEffect(() => { + let editorElem = document.getElementById('editor'); + + let onMouseDownCapture = (e) => { + if (e._modifierKeysApplied) { + return; + } + if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") { + return; + } + + e.stopPropagation(); + e.preventDefault(); + + // NOTE: MouseEvent's constructor silently ignores unrecognized keys in its init + // dict -- it does NOT set them as properties on the resulting event. The marker + // must be assigned onto the created event object afterward, or it never sticks + // and this handler will re-process (and re-dispatch) its own event forever. + let redispatchedEvent = new MouseEvent(e.type, { + bubbles: true, + cancelable: true, + view: window, + detail: e.detail, + screenX: e.screenX, + screenY: e.screenY, + clientX: e.clientX, + clientY: e.clientY, + button: e.button, + buttons: e.buttons, + relatedTarget: e.relatedTarget, + ...props.editor.modifierKeys, + }); + redispatchedEvent._modifierKeysApplied = true; + e.target.dispatchEvent(redispatchedEvent); + }; + + editorElem.addEventListener('mousedown', onMouseDownCapture, true); + return () => editorElem.removeEventListener('mousedown', onMouseDownCapture, true); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return ( { props.editor.autoSaveProject(() => { "Project Autosaved" }) }} + processError={(_error, _errorInfo) => { props.editor.autoSaveProject(() => { "Project Autosaved" }) }} > ) -} +} \ No newline at end of file diff --git a/src/Editor/Panels/MenuBar/MenuBar.jsx b/src/Editor/Panels/MenuBar/MenuBar.jsx index d65cc0285..baf4761df 100644 --- a/src/Editor/Panels/MenuBar/MenuBar.jsx +++ b/src/Editor/Panels/MenuBar/MenuBar.jsx @@ -61,6 +61,11 @@ class MenuBar extends Component { useClickEvent={true} /> + + {this.props.exporting ? this.props.openExportMedia() : this.props.openExportOptions()}} @@ -99,6 +104,11 @@ class MenuBar extends Component {
+ + ( + + )); + } + /** * To be called when the modifiers popout is repositioned. */ @@ -65,16 +82,19 @@ export default function ModifiersPanel(props) { default={props.modifiersPanelWindowProperties} >
-
+
Modifier Keys
+
+ {renderModifierKeyButtons()} +
) } @@ -90,14 +110,16 @@ export default function ModifiersPanel(props) { default={props.modifiersPanelWindowProperties}>
-
{""}
-
Modifiers Panel
+
Modifiers
+
+ {renderModifierKeyButtons()} +
) } \ No newline at end of file diff --git a/src/Editor/styles/PopOuts/_modifierspanel.scss b/src/Editor/styles/PopOuts/_modifierspanel.scss new file mode 100644 index 000000000..cee527bc1 --- /dev/null +++ b/src/Editor/styles/PopOuts/_modifierspanel.scss @@ -0,0 +1,128 @@ +/* + * Copyright 2026 Candlestickers + * + * This file is part of Candlestick. + * + * Candlestick is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Candlestick is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Candlestick. If not, see . + */ + +@use 'sass:color'; +@use 'Editor/_wickbrand.scss' as *; + +#cs-modifiers-panel-resizeable, +#cs-modifiers-panel-resizeable-small { + --handle-height: 40px; + display: flex; + box-shadow: var(--wick-box-shadow); + border-radius: var(--border-radius-primary); +} + +.cs-modifiers-panel-small { + --handle-height: 40px; + display: flex; + flex-direction: column; + height: 100%; +} + +.cs-modifiers-panel-drag-handle { + position: relative; + width: 100%; + height: var(--handle-height); + background-color: var(--wick-primary); + color: var(--wick-text-primary); + cursor: move; + + display: flex; + flex-direction: row; + align-items: center; + border-top-right-radius: var(--border-radius-primary); + border-top-left-radius: var(--border-radius-primary); +} + +.cs-modifiers-panel-title { + padding-left: 8px; + color: white; + margin-right: auto; + white-space: nowrap; +} + +.cs-modifiers-panel-title.small { + font-size: 20px; + font-weight: normal; + padding-left: 4px; +} + +.cs-modifiers-panel-icon { + display: flex; + align-items: center; + justify-content: center; + padding-left: 8px; + color: white; +} + +.cs-modifiers-close-button { + width: 36px; + height: 36px; + min-width: 36px; + min-height: 36px; + margin-right: 4px; + margin-left: auto; +} + +.cs-modifiers-panel-content { + background-color: var(--wick-secondary); + color: var(--wick-text-secondary); + width: 100%; + height: calc(100% - var(--handle-height)); + flex: 1; + + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 12px; + padding: 12px; +} + +.cs-modifier-key-button { + display: inline-block; + min-width: 2.4em; + padding: 6px 10px; + + background-color: #eee; + border-radius: 3px; + border: 1px solid #b4b4b4; + box-shadow: 0 1px 1px rgba(0, 0, 0, .2), 0 2px 0 0 rgba(255, 255, 255, .7) inset; + color: #333; + font-size: 1.1em; + font-weight: 700; + line-height: 1; + white-space: nowrap; + cursor: pointer; +} + +.cs-modifier-key-button:hover { + background-color: #e0e0e0; +} + +.cs-modifier-key-button:active { + box-shadow: 0 1px 1px rgba(0, 0, 0, .2) inset; +} + +.cs-modifier-key-button.active { + background-color: $wick-cs-yellow; + border-color: color.adjust($wick-cs-yellow, $lightness: -15%); + box-shadow: 0 1px 1px rgba(0, 0, 0, .2) inset; +} \ No newline at end of file From 842181d806a3b5c968c438afc43fac156fa58585 Mon Sep 17 00:00:00 2001 From: hobbes6-2 Date: Fri, 4 Sep 2026 18:33:39 +0530 Subject: [PATCH 05/10] death to comments --- src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx b/src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx index f4e0b423e..c47db6e0e 100644 --- a/src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx +++ b/src/Editor/PopOuts/ModifiersPanel/ModifiersPanel.jsx @@ -24,14 +24,6 @@ import classNames from 'classnames'; import 'Editor/styles/PopOuts/_modifierspanel.scss'; -/* -What we must do to intercept mouseclicks etc: -1 - Intercept. -2 - Stop propagation. -3 - do: e = new MouseEvent('mouseup', {...e, ctrlKey: this.ctrl, shiftKey: this.shift, altKey: this.alt}); -4 - Let it continue to the editor. -*/ - const MODIFIER_KEYS = ['ctrl', 'alt', 'shift']; export default function ModifiersPanel(props) { From ed5352870c27369508afaa16d8f6daba31b22321 Mon Sep 17 00:00:00 2001 From: hobbes6-2 Date: Fri, 4 Sep 2026 18:44:22 +0530 Subject: [PATCH 06/10] tweak --- src/Editor/Editor.jsx | 10 +++++----- src/Editor/styles/PopOuts/_modifierspanel.scss | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Editor/Editor.jsx b/src/Editor/Editor.jsx index 3bbb1cc27..aacda5fd3 100644 --- a/src/Editor/Editor.jsx +++ b/src/Editor/Editor.jsx @@ -574,12 +574,12 @@ class Editor extends EditorCore { getDefaultModifiersPanelProperties = () => { return ( { - width: window.innerWidth - 510, // magic number: outliner/inspector width - height: window.innerHeight - 260, // magic number: toolbar/timeline height + width: 260, + height: 160, x: 0, - y: 40, // magic number: toolbar height (not including the menubar, since for some reason y=0 is at the bottom of the menubar?) - minWidth: 400, - minHeight: 250, + y: 40, + minWidth: 200, + minHeight: 125, fontSize: 16, } ); diff --git a/src/Editor/styles/PopOuts/_modifierspanel.scss b/src/Editor/styles/PopOuts/_modifierspanel.scss index cee527bc1..224fa0436 100644 --- a/src/Editor/styles/PopOuts/_modifierspanel.scss +++ b/src/Editor/styles/PopOuts/_modifierspanel.scss @@ -123,6 +123,6 @@ .cs-modifier-key-button.active { background-color: $wick-cs-yellow; - border-color: color.adjust($wick-cs-yellow, $lightness: -15%); + border-color: color.adjust($wick-cs-yellow, $lightness: -10%); box-shadow: 0 1px 1px rgba(0, 0, 0, .2) inset; } \ No newline at end of file From 0aed9172fb076b7285cac6983a8dc05a2fd7a0bb Mon Sep 17 00:00:00 2001 From: hobbes6-2 Date: Fri, 4 Sep 2026 18:48:11 +0530 Subject: [PATCH 07/10] die comments --- src/Editor/EditorWrapper.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Editor/EditorWrapper.jsx b/src/Editor/EditorWrapper.jsx index 3628046a3..df8424d3a 100644 --- a/src/Editor/EditorWrapper.jsx +++ b/src/Editor/EditorWrapper.jsx @@ -54,10 +54,6 @@ export default function EditorWrapper(props) { e.stopPropagation(); e.preventDefault(); - // NOTE: MouseEvent's constructor silently ignores unrecognized keys in its init - // dict -- it does NOT set them as properties on the resulting event. The marker - // must be assigned onto the created event object afterward, or it never sticks - // and this handler will re-process (and re-dispatch) its own event forever. let redispatchedEvent = new MouseEvent(e.type, { bubbles: true, cancelable: true, From 04c92a0f41db3abd8c3008896a7ba36b7c40e2d0 Mon Sep 17 00:00:00 2001 From: hobbes6-2 Date: Fri, 4 Sep 2026 18:58:29 +0530 Subject: [PATCH 08/10] clenup --- src/Editor/Editor.jsx | 3 +-- src/Editor/Panels/MobileContainer/MobileContainer.jsx | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Editor/Editor.jsx b/src/Editor/Editor.jsx index aacda5fd3..663803ff5 100644 --- a/src/Editor/Editor.jsx +++ b/src/Editor/Editor.jsx @@ -800,8 +800,7 @@ class Editor extends EditorCore { }); } - // Maps our modifier key names to the DOM/KeyboardEvent.key values Paper.js's - // Key module listens for (see engine's paper.js Key.modifiers tracking). + // Maps our modifier key names to the values paper.js' listens for. static MODIFIER_KEY_DOM_NAMES = { ctrl: 'Control', alt: 'Alt', shift: 'Shift' }; /** diff --git a/src/Editor/Panels/MobileContainer/MobileContainer.jsx b/src/Editor/Panels/MobileContainer/MobileContainer.jsx index f459a51a3..19db41eb9 100644 --- a/src/Editor/Panels/MobileContainer/MobileContainer.jsx +++ b/src/Editor/Panels/MobileContainer/MobileContainer.jsx @@ -139,7 +139,6 @@ class MobileContainer extends Component { tabs={[{label: "timeline", icon: timelineIcon, iconActive: timelineIconActive, iconAlt: "timeline icon"}, {label: "inspector", icon: inspectorIcon, iconActive: inspectorIconActive, iconAlt: "inspector icon"}, {label: "code", icon: codeIcon, iconActive: codeIconActive, iconAlt: "code editor icon"}, - {label: "modifiers", icon: /*modifiersIcon*/codeIcon, iconActive: /*modifiersIconActive*/codeIconActive, iconAlt: "modifiers panel icon"}, {label: "asset", icon: assetIcon, iconActive: assetIconActive, iconAlt: "asset library icon"} ]}> {this.renderTimeline(this.props)} From de7a3ae868696f81171b410a3a104d82cf544b06 Mon Sep 17 00:00:00 2001 From: hobbes6-2 Date: Sat, 5 Sep 2026 14:33:36 +0530 Subject: [PATCH 09/10] fix a bit --- src/Editor/EditorWrapper.jsx | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/Editor/EditorWrapper.jsx b/src/Editor/EditorWrapper.jsx index df8424d3a..32d1ae559 100644 --- a/src/Editor/EditorWrapper.jsx +++ b/src/Editor/EditorWrapper.jsx @@ -43,33 +43,17 @@ export default function EditorWrapper(props) { useEffect(() => { let editorElem = document.getElementById('editor'); + // Merge the virtual modifier-key state onto the real, trusted mousedown event + // instead of replacing it. ctrlKey/altKey/shiftKey are accessor properties + // inherited from MouseEvent.prototype, so Object.defineProperty is needed to define them on the instance. let onMouseDownCapture = (e) => { - if (e._modifierKeysApplied) { - return; - } if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") { return; } - e.stopPropagation(); - e.preventDefault(); - - let redispatchedEvent = new MouseEvent(e.type, { - bubbles: true, - cancelable: true, - view: window, - detail: e.detail, - screenX: e.screenX, - screenY: e.screenY, - clientX: e.clientX, - clientY: e.clientY, - button: e.button, - buttons: e.buttons, - relatedTarget: e.relatedTarget, - ...props.editor.modifierKeys, - }); - redispatchedEvent._modifierKeysApplied = true; - e.target.dispatchEvent(redispatchedEvent); + Object.defineProperty(e, 'ctrlKey', { value: e.ctrlKey || props.editor.modifierKeys.ctrlKey, configurable: true }); + Object.defineProperty(e, 'altKey', { value: e.altKey || props.editor.modifierKeys.altKey, configurable: true }); + Object.defineProperty(e, 'shiftKey', { value: e.shiftKey || props.editor.modifierKeys.shiftKey, configurable: true }); }; editorElem.addEventListener('mousedown', onMouseDownCapture, true); From c2ec03be056d12ebfef0ab7b6089d432d18aa188 Mon Sep 17 00:00:00 2001 From: hobbes6-2 Date: Sat, 5 Sep 2026 14:34:00 +0530 Subject: [PATCH 10/10] clenupmore --- src/Editor/EditorWrapper.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Editor/EditorWrapper.jsx b/src/Editor/EditorWrapper.jsx index 32d1ae559..a69fa41e9 100644 --- a/src/Editor/EditorWrapper.jsx +++ b/src/Editor/EditorWrapper.jsx @@ -47,9 +47,7 @@ export default function EditorWrapper(props) { // instead of replacing it. ctrlKey/altKey/shiftKey are accessor properties // inherited from MouseEvent.prototype, so Object.defineProperty is needed to define them on the instance. let onMouseDownCapture = (e) => { - if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") { - return; - } + if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") return; Object.defineProperty(e, 'ctrlKey', { value: e.ctrlKey || props.editor.modifierKeys.ctrlKey, configurable: true }); Object.defineProperty(e, 'altKey', { value: e.altKey || props.editor.modifierKeys.altKey, configurable: true });