diff --git a/modules/gems/iterables.js b/modules/gems/iterables.js index 16ba65df..4dd04bba 100644 --- a/modules/gems/iterables.js +++ b/modules/gems/iterables.js @@ -90,4 +90,11 @@ export function indexById(array) { return out; } +export function insertAfter(arr, item, toAdd) { + const index = arr.indexOf(item); + if (index !== -1) { + arr.splice(index+1, 0, toAdd); + } +} + export const EMPTY_ARRAY = Object.freeze([]); diff --git a/modules/lstream/combine.js b/modules/lstream/combine.js index cdd6adbb..f84700a6 100644 --- a/modules/lstream/combine.js +++ b/modules/lstream/combine.js @@ -5,6 +5,11 @@ export class CombineStream extends StreamBase { constructor(streams) { super(); + streams.forEach(stream => { + if (!stream) { + throw 'stream is undefined'; + } + }); this.streams = streams; this.values = this.streams.map(() => NOT_INITIALIZED); this.ready = false; diff --git a/modules/ui/components/Toolbar.jsx b/modules/ui/components/Toolbar.jsx index 8e414c8a..0b0588d8 100644 --- a/modules/ui/components/Toolbar.jsx +++ b/modules/ui/components/Toolbar.jsx @@ -20,4 +20,14 @@ export function ToolbarButton({children, disabled, ...props}) { export function ToolbarSplitter() { return
+} + +export function ToolbarBraker() { + return
+} + +export function ToolbarGroup({children}) { + return
+ {children} +
; } \ No newline at end of file diff --git a/modules/ui/components/Toolbar.less b/modules/ui/components/Toolbar.less index 96d217db..345c3070 100644 --- a/modules/ui/components/Toolbar.less +++ b/modules/ui/components/Toolbar.less @@ -8,7 +8,6 @@ &.flat { border-radius: 0; } - overflow: auto; align-content: center; } @@ -21,7 +20,8 @@ text-align: center; white-space: nowrap; font-size: 10px; - padding: 3px 7px; + padding: 2px 2px; + margin: 1px 5px; color: #555; pointer-events: auto; &:hover { @@ -57,5 +57,8 @@ margin: 0 2px; } +.group { + display: flex; +} diff --git a/package-lock.json b/package-lock.json index 413c1bfa..a69b0257 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9301,9 +9301,9 @@ } }, "react-icons": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-3.9.0.tgz", - "integrity": "sha512-gKbYKR+4QsD3PmIHLAM9TDDpnaTsr3XZeK1NTAb6WQQ+gxEdJ0xuCgLq0pxXdS7Utg2AIpcVhM1ut/jlDhcyNg==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-3.10.0.tgz", + "integrity": "sha512-WsQ5n1JToG9VixWilSo1bHv842Cj5aZqTGiS3Ud47myF6aK7S/IUY2+dHcBdmkQcCFRuHsJ9OMUI0kTDfjyZXQ==", "requires": { "camelcase": "^5.0.0" }, diff --git a/package.json b/package.json index 956df3a3..8b502228 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "prop-types": "15.6.0", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-icons": "3.9.0", + "react-icons": "^3.10.0", "react-toastify": "^5.5.0", "sprintf": "0.1.5", "three": "0.89.0" diff --git a/web/app/cad/actions/ActionButtonBehavior.jsx b/web/app/cad/actions/ActionButtonBehavior.jsx new file mode 100644 index 00000000..e8a9796d --- /dev/null +++ b/web/app/cad/actions/ActionButtonBehavior.jsx @@ -0,0 +1,41 @@ +import React, {useContext} from 'react'; +import {AppContext} from "../dom/components/AppContext"; + +export function ActionButtonBehavior({children, actionId}) { + + const ctx = useContext(AppContext); + + const request = {actionId, x: 0, y: 0}; + + let canceled = true; + let shown = false; + + function updateCoords({pageX, pageY}) { + request.x = pageX + 10; + request.y = pageY + 10; + } + + const actionService = ctx.services.action; + + return children({ + onClick: e => actionService.run(actionId, e), + onMouseEnter: e => { + updateCoords(e); + canceled = false; + shown = false; + setTimeout(() => { + if (!canceled) { + shown = true; + actionService.showHintFor(request) + } + }, 500); + }, + onMouseMove: updateCoords, + onMouseLeave: () => { + canceled = true; + if (shown) { + actionService.showHintFor(null) + } + } + }); +} diff --git a/web/app/cad/actions/actionButtonBehavior.js b/web/app/cad/actions/actionButtonBehavior.js deleted file mode 100644 index a60b8dc0..00000000 --- a/web/app/cad/actions/actionButtonBehavior.js +++ /dev/null @@ -1,36 +0,0 @@ - -export function mapActionBehavior(actionIdGetter) { - return ({services}, props) => { - const actionId = typeof actionIdGetter === 'string' ? actionIdGetter : actionIdGetter(props); - - let request = {actionId, x:0, y:0}; - let canceled = true; - let shown = false; - - function updateCoords({pageX, pageY}) { - request.x = pageX + 10; - request.y = pageY + 10; - } - - return { - onClick: e => services.action.run(actionId, e), - onMouseEnter: e => { - updateCoords(e); - canceled = false; - shown = false; - setTimeout(() => { - if (!canceled) { - shown = true; - services.action.showHintFor(request) - } - }, 500); - }, - onMouseMove: updateCoords, - onMouseLeave: () => { - canceled = true; - if (shown) { - services.action.showHintFor(null) - } - } - }}; -} diff --git a/web/app/cad/actions/actionDecorators.jsx b/web/app/cad/actions/actionDecorators.jsx index 63501184..f33581d4 100644 --- a/web/app/cad/actions/actionDecorators.jsx +++ b/web/app/cad/actions/actionDecorators.jsx @@ -1,8 +1,9 @@ import React from 'react'; -import {mapActionBehavior} from './actionButtonBehavior'; -import mapContext from '../../../../modules/ui/mapContext'; +import {ActionButtonBehavior} from './ActionButtonBehavior'; export function actionDecorator(actionId) { - return mapContext(mapActionBehavior(actionId)); + return Comp => props => + {bProps => } + ; } \ No newline at end of file diff --git a/web/app/cad/dom/components/AppContext.js b/web/app/cad/dom/components/AppContext.js new file mode 100644 index 00000000..433a29b3 --- /dev/null +++ b/web/app/cad/dom/components/AppContext.js @@ -0,0 +1,3 @@ +import React from "react"; + +export const AppContext = React.createContext({}); \ No newline at end of file diff --git a/web/app/cad/dom/components/HeadsUpToolbar.jsx b/web/app/cad/dom/components/HeadsUpToolbar.jsx index 94b22924..859b10e9 100644 --- a/web/app/cad/dom/components/HeadsUpToolbar.jsx +++ b/web/app/cad/dom/components/HeadsUpToolbar.jsx @@ -6,12 +6,13 @@ import ls from './HeadsUpToolbar.less'; import {combine} from '../../../../../modules/lstream'; export const HeadsUpToolbar = connect(streams => combine( - streams.ui.toolbars.headsUp, - streams.ui.toolbars.headsUpQuickActions).map(([actions, quickActions]) => ({actions, quickActions})))( - function HeadsUpToolbar({actions, quickActions}) { + streams.ui.toolbars.headsUp, + streams.ui.toolbars.headsUpShowTitles, + streams.ui.toolbars.headsUpQuickActions).map(([actions, showTitles, quickActions]) => ({actions, showTitles, quickActions})))( + function HeadsUpToolbar({actions, showTitles, quickActions}) { return
- +
diff --git a/web/app/cad/dom/components/HeadsUpToolbar.less b/web/app/cad/dom/components/HeadsUpToolbar.less index 2e78475b..70a36343 100644 --- a/web/app/cad/dom/components/HeadsUpToolbar.less +++ b/web/app/cad/dom/components/HeadsUpToolbar.less @@ -14,5 +14,9 @@ .mainActions { flex: 1 1; display: flex; - overflow-x: auto; + flex-wrap: wrap; + svg { + width: 24px; + height: 24px; + } } \ No newline at end of file diff --git a/web/app/cad/dom/components/PlugableControlBar.jsx b/web/app/cad/dom/components/PlugableControlBar.jsx index ae18f49b..5f28f491 100644 --- a/web/app/cad/dom/components/PlugableControlBar.jsx +++ b/web/app/cad/dom/components/PlugableControlBar.jsx @@ -4,11 +4,10 @@ import connect from 'ui/connect'; import Fa from 'ui/components/Fa'; import {toIdAndOverrides} from '../../actions/actionRef'; import {isMenuAction} from '../menu/menuPlugin'; -import {combine, merger} from 'lstream'; -import mapContext from 'ui/mapContext'; -import decoratorChain from '../../../../../modules/ui/decoratorChain'; +import {combine} from 'lstream'; import {menuAboveElementHint} from '../menu/menuUtils'; -import {actionDecorator} from '../../actions/actionDecorators'; +import {useStream} from "../../../../../modules/ui/effects"; +import {ActionButtonBehavior} from "../../actions/ActionButtonBehavior"; export default function PlugableControlBar() { return } right={}/>; @@ -42,14 +41,17 @@ class ActionButton extends React.Component { const LeftGroup = connect(streams => streams.ui.controlBars.left.map(actions => ({actions})))(ButtonGroup); const RightGroup = connect(streams => streams.ui.controlBars.right.map(actions => ({actions})))(ButtonGroup); -const ConnectedActionButton = decoratorChain( +function ConnectedActionButton(props) { - connect( - (streams, props) => combine( - streams.action.appearance[props.actionId], - streams.action.state[props.actionId]).map(merger)), - - actionDecorator(props => props.actionId) -) -(ActionButton); + const actionId = props.actionId; + const stream = useStream(ctx => combine(ctx.streams.action.appearance[actionId], ctx.streams.action.state[actionId])); + if (!stream) { + return null; + } + const [actionAppearance, actionState] = stream; + + return + {behaviourProps => } + ; +} diff --git a/web/app/cad/dom/components/PlugableToolbar.jsx b/web/app/cad/dom/components/PlugableToolbar.jsx index 40b51558..5ac9d011 100644 --- a/web/app/cad/dom/components/PlugableToolbar.jsx +++ b/web/app/cad/dom/components/PlugableToolbar.jsx @@ -1,15 +1,12 @@ import React from 'react'; -import connect from 'ui/connect'; import Fa from 'ui/components/Fa'; -import Toolbar, {ToolbarButton} from 'ui/components/Toolbar'; +import Toolbar, {ToolbarBraker, ToolbarButton, ToolbarGroup, ToolbarSplitter} from 'ui/components/Toolbar'; import ImgIcon from 'ui/components/ImgIcon'; import {toIdAndOverrides} from '../../actions/actionRef'; -import {mapActionBehavior} from '../../actions/actionButtonBehavior'; +import {ActionButtonBehavior} from '../../actions/ActionButtonBehavior'; import capitalize from 'gems/capitalize'; -import decoratorChain from 'ui/decoratorChain'; -import {combine, merger} from 'lstream'; -import mapContext from 'ui/mapContext'; -import {ToolbarSplitter} from 'ui/components/Toolbar'; +import {combine} from 'lstream'; +import {useStream} from "../../../../../modules/ui/effects"; function ConfigurableToolbar({actions, size, ...props}) { return @@ -17,31 +14,43 @@ function ConfigurableToolbar({actions, size, ...props}) { } -export function ToolbarActionButtons({actions, size}) { +export function ToolbarActionButtons({actions, showTitles, size}) { return actions.map((actionRef, i) => { if (actionRef === '-') { - return ; + return ; + } else if (actionRef === '|') { + return ; + } else if (Array.isArray(actionRef)) { + return
+ + +
; } let [id, overrides] = toIdAndOverrides(actionRef); - return + return }); } -function ActionButton({label, icon96, icon32, cssIcons, symbol, size, noLabel, enabled, visible, actionId, ...props}) { +function ActionButton({label, icon, icon96, icon32, cssIcons, symbol, size, noLabel, enabled, visible, actionId, ...props}) { if (!visible) { return null; } let smallOrMedium = size === 'medium' || size === 'small'; - let icon; - if (smallOrMedium) { - if (cssIcons) { - icon = ; - } else if (icon32) { - icon = ; + if (icon) { + const Icon = icon; + icon = ; + } + if (!icon) { + if (smallOrMedium) { + if (cssIcons) { + icon = ; + } else if (icon32) { + icon = ; + } + } else { + icon = ; } - } else { - icon = ; } if (!icon) { icon = {symbol||(label&&label.charAt(0))}; @@ -56,15 +65,27 @@ function ActionButton({label, icon96, icon32, cssIcons, symbol, size, noLabel, e } -export const ConnectedActionButton = decoratorChain( - connect((streams, {actionId}) => combine(streams.action.appearance[actionId], streams.action.state[actionId]).map(merger)), - mapContext(mapActionBehavior(props => props.actionId)) -) -(ActionButton); +export function ConnectedActionButton(props) { + + const actionId = props.actionId; + const stream = useStream(ctx => combine(ctx.streams.action.appearance[actionId], ctx.streams.action.state[actionId])); + if (!stream) { + return null; + } + const [actionAppearance, actionState] = stream; + + return + {behaviourProps => } + ; + +} export function createPlugableToolbar(streamSelector) { - return decoratorChain( - connect(streams => streamSelector(streams).map(actions => ({actions}))) - ) - (props => ); + return function (props) { + const actions = useStream(ctx => streamSelector(ctx.streams)); + if (!actions) { + return null; + } + return ; + } } diff --git a/web/app/cad/dom/components/SketcherMode.jsx b/web/app/cad/dom/components/SketcherMode.jsx deleted file mode 100644 index 8988cf26..00000000 --- a/web/app/cad/dom/components/SketcherMode.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import connect from 'ui/connect'; - -@connect(streams => streams.sketcher.sketchingMode.map(sketchingMode => ({visible: sketchingMode}))) -export default class SketcherMode extends React.Component { - - render() { - if (!this.props.visible) { - return null; - } - return this.props.children; - } -} \ No newline at end of file diff --git a/web/app/cad/dom/components/View3d.jsx b/web/app/cad/dom/components/View3d.jsx index 1a25475d..3153264a 100644 --- a/web/app/cad/dom/components/View3d.jsx +++ b/web/app/cad/dom/components/View3d.jsx @@ -1,20 +1,19 @@ import React from 'react'; import PlugableControlBar from './PlugableControlBar'; import ls from './View3d.less'; -import Abs from 'ui/components/Abs'; import UISystem from './UISystem'; import WizardManager from '../../craft/wizard/components/WizardManager'; import FloatView from './FloatView'; import HistoryTimeline from '../../craft/ui/HistoryTimeline'; import SelectedModificationInfo from '../../craft/ui/SelectedModificationInfo'; -import BottomStack from './BottomStack'; -import SketcherToolbars from './SketcherToolbars'; import CameraControl from './CameraControl'; import HeadsUpHelper from './HeadsUpHelper'; import {HeadsUpToolbar} from './HeadsUpToolbar'; import {SketchObjectExplorer} from '../../../sketcher/components/SketchObjectExplorer'; -import SketcherMode from './SketcherMode'; +import SketcherMode from '../../sketch/components/SketcherMode'; import {ConstraintExplorer} from '../../../sketcher/components/ConstraintExplorer'; +import {Scope} from "../../../sketcher/components/Scope"; +import {InplaceSketcher} from "../../sketch/components/InplaceSketcher"; export default class View3d extends React.Component { @@ -37,20 +36,16 @@ export default class View3d extends React.Component {
-
- - -
+ +
+ + +
+
- -
-
- -
-
diff --git a/web/app/cad/dom/components/WebApplication.jsx b/web/app/cad/dom/components/WebApplication.jsx index a57a796b..d82a780b 100644 --- a/web/app/cad/dom/components/WebApplication.jsx +++ b/web/app/cad/dom/components/WebApplication.jsx @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'; import 'ui/styles/init/index.less'; import AppTabs from "./AppTabs"; +import {StreamsContext} from "../../../../../modules/ui/streamsContext"; +import {AppContext} from "./AppContext"; export default class WebApplication extends React.Component { @@ -14,7 +16,12 @@ export default class WebApplication extends React.Component { render() { - return + const {appContext} = this.props; + return + + + + } getChildContext() { diff --git a/web/app/cad/dom/menu/MenuHolder.jsx b/web/app/cad/dom/menu/MenuHolder.jsx index 8eaab2fa..cfdc4100 100644 --- a/web/app/cad/dom/menu/MenuHolder.jsx +++ b/web/app/cad/dom/menu/MenuHolder.jsx @@ -2,11 +2,10 @@ import React from 'react'; import Menu, {MenuItem, MenuSeparator} from 'ui/components/Menu'; import Filler from 'ui/components/Filler'; import Fa from 'ui/components/Fa'; -import {mapActionBehavior} from '../../actions/actionButtonBehavior'; +import {ActionButtonBehavior} from '../../actions/ActionButtonBehavior'; import connect from 'ui/connect'; import {combine, merger} from 'lstream'; -import mapContext from 'ui/mapContext'; -import decoratorChain from 'ui/decoratorChain'; +import {useStream} from "../../../../../modules/ui/effects"; function MenuHolder({menus}) { return menus.map(({id, actions}) => ); @@ -55,17 +54,21 @@ const ConnectedActionMenu = connect((streams, props) => .map(([s, keymap]) => ({...s, keymap}))) (ActionMenu); +export function ConnectedMenuItem(props) { -let ConnectedMenuItem = decoratorChain( + const actionId = props.actionId; + const stream = useStream(ctx => combine(ctx.streams.action.appearance[actionId], ctx.streams.action.state[actionId])); + if (!stream) { + return null; + } + const [actionAppearance, actionState] = stream; + + return + {behaviourProps => } + ; + +} - connect((streams, {actionId}) => - combine( - streams.action.state[actionId], - streams.action.appearance[actionId]).map(merger)), - - mapContext(mapActionBehavior(props => props.actionId)) -) -(ActionMenuItem); export default connect(streams => streams.ui.menu.all.map(menus => ({menus})))(MenuHolder); diff --git a/web/app/cad/dom/uiPlugin.js b/web/app/cad/dom/uiPlugin.js index 199e97c4..4555456a 100644 --- a/web/app/cad/dom/uiPlugin.js +++ b/web/app/cad/dom/uiPlugin.js @@ -9,6 +9,7 @@ export function defineStreams({streams}) { }, toolbars: { headsUp: state([]), + headsUpShowTitles: state(true), headsUpQuickActions: state([]), sketcherGeneral: state([]), sketcherConstraints: state([]), diff --git a/web/app/cad/part/uiConfigPlugin.js b/web/app/cad/part/uiConfigPlugin.js index 7614ebde..7a0b447a 100644 --- a/web/app/cad/part/uiConfigPlugin.js +++ b/web/app/cad/part/uiConfigPlugin.js @@ -8,6 +8,9 @@ import React from 'react'; import OperationHistory from '../craft/ui/OperationHistory'; import Expressions from '../expressions/Expressions'; +export const STANDARD_MODE_HEADS_UP_TOOLBAR = ['DATUM_CREATE', 'PLANE', 'EditFace', 'EXTRUDE', 'CUT', 'REVOLVE', 'LOFT', + '-', 'FILLET', '-', 'INTERSECTION', 'SUBTRACT', 'UNION']; + export function activate({services, streams}) { streams.ui.controlBars.left.value = ['menu.file', 'menu.craft', 'menu.boolean', 'menu.primitives', 'menu.views', 'Donate', 'GitHub']; streams.ui.controlBars.right.value = [ @@ -16,8 +19,7 @@ export function activate({services, streams}) { ['ShowSketches', {label: 'sketches'}], ['DeselectAll', {label: null}], ['ToggleCameraMode', {label: null}] ]; - streams.ui.toolbars.headsUp.value = ['DATUM_CREATE', 'PLANE', 'EditFace', 'EXTRUDE', 'CUT', 'REVOLVE', 'LOFT', - '-', 'FILLET', '-', 'INTERSECTION', 'SUBTRACT', 'UNION']; + streams.ui.toolbars.headsUp.value = STANDARD_MODE_HEADS_UP_TOOLBAR; streams.ui.toolbars.headsUpQuickActions.value = ['Save', 'StlExport']; services.action.registerActions(CoreActions); diff --git a/web/app/cad/sketch/components/InplaceSketcher.jsx b/web/app/cad/sketch/components/InplaceSketcher.jsx new file mode 100644 index 00000000..246839a2 --- /dev/null +++ b/web/app/cad/sketch/components/InplaceSketcher.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import {useStream} from "../../../../../modules/ui/effects"; +import {StreamsContext} from "../../../../../modules/ui/streamsContext"; +import {SketcherAppContext} from "../../../sketcher/components/SketcherAppContext"; +import {Scope} from "../../../sketcher/components/Scope"; + +export function InplaceSketcher({children}) { + + const sketcherAppContext = useStream(ctx => ctx.streams.sketcher.sketcherAppContext); + + if (sketcherAppContext === null) { + return null; + } + + return + + {children} + + +} \ No newline at end of file diff --git a/web/app/cad/sketch/components/SketcherMode.jsx b/web/app/cad/sketch/components/SketcherMode.jsx new file mode 100644 index 00000000..bccde382 --- /dev/null +++ b/web/app/cad/sketch/components/SketcherMode.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import {useStream} from "../../../../../modules/ui/effects"; + +export default function SketcherMode({children}) { + + const visible = useStream(ctx => ctx.streams.sketcher.sketchingMode); + + if (!visible) { + return null; + } + + return children; +} \ No newline at end of file diff --git a/web/app/cad/dom/components/SketcherToolbars.jsx b/web/app/cad/sketch/components/SketcherToolbars.jsx similarity index 85% rename from web/app/cad/dom/components/SketcherToolbars.jsx rename to web/app/cad/sketch/components/SketcherToolbars.jsx index f3da1a40..1832d345 100644 --- a/web/app/cad/dom/components/SketcherToolbars.jsx +++ b/web/app/cad/sketch/components/SketcherToolbars.jsx @@ -1,6 +1,7 @@ import React from 'react'; import ls from './SketcherToolbars.less'; -import {createPlugableToolbar} from './PlugableToolbar'; +import {createPlugableToolbar} from '../../dom/components/PlugableToolbar'; +import '../../../sketcher/actions'; export default function SketcherToolbars({visible}) { return
diff --git a/web/app/cad/dom/components/SketcherToolbars.less b/web/app/cad/sketch/components/SketcherToolbars.less similarity index 100% rename from web/app/cad/dom/components/SketcherToolbars.less rename to web/app/cad/sketch/components/SketcherToolbars.less diff --git a/web/app/cad/sketch/inPlaceSketcher.js b/web/app/cad/sketch/inPlaceSketcher.js index efd1d23d..30a15ab2 100644 --- a/web/app/cad/sketch/inPlaceSketcher.js +++ b/web/app/cad/sketch/inPlaceSketcher.js @@ -1,18 +1,22 @@ -import {Viewer} from '../../sketcher/viewer2d'; -import {IO} from '../../sketcher/io'; import {DelegatingPanTool} from '../../sketcher/tools/pan'; import {Matrix4} from 'three/src/math/Matrix4'; import {ORIGIN} from '../../math/l3space'; import {CAMERA_MODE} from '../scene/viewer'; import DPR from 'dpr'; -import sketcherStreams from '../../sketcher/sketcherStreams'; +import {SKETCHER_MODE_HEADS_UP_ACTIONS} from "./sketcherUIContrib"; +import {createEssentialAppContext} from "../../sketcher/sketcherContext"; +import {STANDARD_MODE_HEADS_UP_TOOLBAR} from "../part/uiConfigPlugin"; export class InPlaceSketcher { constructor(ctx) { this.face = null; // should be only one in the state this.ctx = ctx; - this.viewer = null; + this.sketcherAppContext = null; + } + + get viewer() { + return this.sketcherAppContext ? this.sketcherAppContext.viewer : null; } get inEditMode() { @@ -33,18 +37,21 @@ export class InPlaceSketcher { canvas.style.bottom = 0; container.appendChild(canvas); - this.viewer = new Viewer(canvas, IO); + this.sketcherAppContext = createEssentialAppContext(canvas); this.viewer.parametricManager.externalConstantResolver = this.ctx.services.expressions.evaluateExpression; - this.ctx.streams.sketcherApp = this.viewer.streams; this.syncWithCamera(); this.viewer.toolManager.setDefaultTool(new DelegatingPanTool(this.viewer, viewer3d.sceneSetup.renderer.domElement)); viewer3d.sceneSetup.trackballControls.addEventListener( 'change', this.onCameraChange); + this.ctx.streams.ui.toolbars.headsUp.next(SKETCHER_MODE_HEADS_UP_ACTIONS); + this.ctx.streams.ui.toolbars.headsUpShowTitles.next(false); + let sketchData = this.ctx.services.storage.get(this.sketchStorageKey); this.viewer.historyManager.init(sketchData); this.viewer.io.loadSketch(sketchData); - this.ctx.streams.sketcher.sketchingFace.value = face; + this.ctx.streams.sketcher.sketchingFace.next(face); + this.ctx.streams.sketcher.sketcherAppContext.next(this.sketcherAppContext); } get sketchStorageKey() { @@ -60,9 +67,11 @@ export class InPlaceSketcher { this.face = null; this.viewer.canvas.parentNode.removeChild(this.viewer.canvas); this.viewer.dispose(); - this.viewer = null; - this.ctx.streams.sketcher.sketchingFace.value = null; - this.ctx.streams.sketcherApp = null; + this.sketcherAppContext = null; + this.ctx.streams.sketcher.sketchingFace.next(null); + this.ctx.streams.sketcher.sketcherAppContext.next(null); + this.ctx.streams.ui.toolbars.headsUp.next(STANDARD_MODE_HEADS_UP_TOOLBAR); + this.ctx.streams.ui.toolbars.headsUpShowTitles.next(true); viewer3d.requestRender(); } diff --git a/web/app/cad/sketch/sketcherControlActions.js b/web/app/cad/sketch/sketcherControlActions.js index 15fa9d28..a8d316b2 100644 --- a/web/app/cad/sketch/sketcherControlActions.js +++ b/web/app/cad/sketch/sketcherControlActions.js @@ -1,10 +1,14 @@ +import {FcCancel, FcCheckmark} from "react-icons/fc"; +import {RiExternalLinkLine} from "react-icons/ri"; + export default [ { id: 'sketchSaveAndExit', appearance: { info: 'save sketch changes and exit', label: 'commit', - cssIcons: ['check'], + icon: FcCheckmark, + }, invoke: ({services}) => { services.sketcher.inPlaceEditor.save(); @@ -16,7 +20,7 @@ export default [ appearance: { info: 'drop sketch changes and exit', label: 'exit sketch', - cssIcons: ['times'], + icon: FcCancel, }, invoke: ({services}) => { services.sketcher.inPlaceEditor.exit(); @@ -27,7 +31,7 @@ export default [ appearance: { info: 'save changes and open sketch 2D in a tab', label: '2D', - cssIcons: ['external-link'], + icon: RiExternalLinkLine, }, invoke: ({services}) => { let face = services.sketcher.inPlaceEditor.face; diff --git a/web/app/cad/sketch/sketcherPlugin.js b/web/app/cad/sketch/sketcherPlugin.js index e78b24af..bf3da83c 100644 --- a/web/app/cad/sketch/sketcherPlugin.js +++ b/web/app/cad/sketch/sketcherPlugin.js @@ -12,7 +12,8 @@ import {DelegatingPanTool} from "../../sketcher/tools/pan"; export function defineStreams(ctx) { ctx.streams.sketcher = { update: stream(), - sketchingFace: state(null) + sketchingFace: state(null), + sketcherAppContext: state(null) }; ctx.streams.sketcher.sketchingMode = ctx.streams.sketcher.sketchingFace.map(face => !!face); } diff --git a/web/app/cad/sketch/sketcherUIContrib.js b/web/app/cad/sketch/sketcherUIContrib.js index 10547a3a..05df7663 100644 --- a/web/app/cad/sketch/sketcherUIContrib.js +++ b/web/app/cad/sketch/sketcherUIContrib.js @@ -1,54 +1,61 @@ -import SketcherToolActions from './sketcherToolActions'; -import SketcherConstrainsActions from './sketcherConstraintsActions'; -import SketcherControlActions from './sketcherControlActions'; -import {state} from '../../../../modules/lstream'; +import {startOperation} from "../../sketcher/actions"; +import objectToolActions from '../../sketcher/actions/objectToolActions'; +import measureActions from '../../sketcher/actions/measureActions'; +import {insertAfter} from '../../../../modules/gems/iterables'; +import operationActions from "../../sketcher/actions/operationActions"; +import constraintGlobalActions from "../../sketcher/actions/constraintGlobalActions"; +import generalToolActions from "../../sketcher/actions/generalToolActions"; +import sketcherControlActions from "./sketcherControlActions"; export default function ({services, streams}) { + services.action.registerActions(sketcherControlActions); + services.action.registerActions([ + ...constraintGlobalActions, + ...measureActions, + ...generalToolActions, + ...objectToolActions, + ...operationActions, + + ].map(convertSketcherAction)); + +} + +const SKETCHER_PREFIX = 'sketcher.'; + +function toSketcherActionId(id) { + return SKETCHER_PREFIX + id; +} + +function convertSketcherAction(action) { + + return { + id: toSketcherActionId(action.id), + appearance: { + icon: action.icon, + label: action.shortName, + info: action.description, + }, + invoke: ({services}, e) => action.invoke(services.sketcher.inPlaceEditor.sketcherAppContext) + } +} +export const SKETCHER_MODE_HEADS_UP_ACTIONS = [ + ['sketchSaveAndExit', 'sketchExit'], + '-', + generalToolActions.map(a => toSketcherActionId(a.id)), + '-', + [ + ...objectToolActions.map(a => toSketcherActionId(a.id)), + toSketcherActionId('Offset'), + ], + '-', + measureActions.map(a => toSketcherActionId(a.id)), + '-', + constraintGlobalActions.map(a => toSketcherActionId(a.id)), + '-', + ['sketchOpenInTab'] +]; + +insertAfter(SKETCHER_MODE_HEADS_UP_ACTIONS, SKETCHER_PREFIX + 'Export', '-'); +insertAfter(SKETCHER_MODE_HEADS_UP_ACTIONS, SKETCHER_PREFIX + 'PanTool', '-'); +insertAfter(SKETCHER_MODE_HEADS_UP_ACTIONS, SKETCHER_PREFIX + 'BezierTool', '-'); - services.action.registerActions(SketcherToolActions); - services.action.registerActions(SketcherConstrainsActions); - services.action.registerActions(SketcherControlActions); - - streams.ui.toolbars.sketcherGeneral.value = [ - 'sketchReferencePoint', - 'sketchPanTool', - 'sketchAddPoint', - 'sketchAddSegment', - 'sketchAddMultiSegment', - 'sketchAddArc', - 'sketchAddCircle', - 'sketchAddEllipse', - 'sketchAddEllipticalArc', - 'sketchAddCubicBezierSpline', - 'sketchAddRectangle', - 'sketchOffsetTool', - 'sketchAddFillet', - 'sketchAddDim', - 'sketchAddHDim', - 'sketchAddVDim', - 'sketchCircleDim', - ]; - streams.ui.toolbars.sketcherConstraints.value = [ - 'sketchConstraint_coincident', - 'sketchConstraint_verticalConstraint', - 'sketchConstraint_horizontalConstraint', - 'sketchConstraint_parallelConstraint', - 'sketchConstraint_perpendicularConstraint', - 'sketchConstraint_P2LDistanceConstraint', - 'sketchConstraint_P2PDistanceConstraint', - 'sketchConstraint_RadiusConstraint', - 'sketchConstraint_EntityEqualityConstraint', - 'sketchConstraint_tangentConstraint', - 'sketchConstraint_lockConstraint', - 'sketchConstraint_pointOnLine', - 'sketchConstraint_pointOnArc', - 'sketchConstraint_pointInMiddle', - 'sketchConstraint_llAngle', - 'sketchConstraint_symmetry', - 'sketchConstraint_mirror', - 'sketchConstraint_lockConvex' - ]; - streams.ui.toolbars.sketcherControl.value = [ - 'sketchSaveAndExit', 'sketchOpenInTab', 'sketchExit' - ]; -} \ No newline at end of file diff --git a/web/app/sketcher/actions/generalToolActions.js b/web/app/sketcher/actions/generalToolActions.js new file mode 100644 index 00000000..d810b22c --- /dev/null +++ b/web/app/sketcher/actions/generalToolActions.js @@ -0,0 +1,31 @@ +import {ReferencePointTool} from "../tools/origin"; +import {IoIosHand} from "react-icons/io"; +import {GiCrosshair} from "react-icons/gi"; + +export default [ + { + id: 'PanTool', + shortName: 'Pan', + kind: 'Tool', + description: 'Pan mode', + icon: IoIosHand, + + invoke: (ctx) => { + ctx.viewer.toolManager.releaseControl(); + } + + }, + + { + id: 'ReferencePointTool', + shortName: 'Set Origin', + kind: 'Tool', + description: 'Sets reference point for commands', + icon: GiCrosshair, + command: 'origin', + invoke: (ctx) => { + ctx.viewer.toolManager.takeControl(new ReferencePointTool(ctx.viewer)); + } + + }, +] \ No newline at end of file diff --git a/web/app/sketcher/actions/index.js b/web/app/sketcher/actions/index.js index 9d8034a0..f03044d0 100644 --- a/web/app/sketcher/actions/index.js +++ b/web/app/sketcher/actions/index.js @@ -4,9 +4,10 @@ import {toast} from "react-toastify"; import operationActions from "./operationActions"; import constraintGlobalActions from "./constraintGlobalActions"; import measureActions from "./measureActions"; -import toolActions from "./toolActions"; +import objectToolActions from "./objectToolActions"; import commonActions from "./commonActions"; import exportActions from "./exportActions"; +import generalToolActions from "./generalToolActions"; const ALL_CONTEXTUAL_ACTIONS = [ ...constraintActions, @@ -16,7 +17,8 @@ const ALL_CONTEXTUAL_ACTIONS = [ const ACTIONS = [ ...constraintGlobalActions, ...measureActions, - ...toolActions, + ...generalToolActions, + ...objectToolActions, ...commonActions, ...exportActions //keep going here diff --git a/web/app/sketcher/actions/toolActions.js b/web/app/sketcher/actions/objectToolActions.js similarity index 82% rename from web/app/sketcher/actions/toolActions.js rename to web/app/sketcher/actions/objectToolActions.js index 419f0f29..5d6c7e0b 100644 --- a/web/app/sketcher/actions/toolActions.js +++ b/web/app/sketcher/actions/objectToolActions.js @@ -16,39 +16,9 @@ import {EllipseTool} from "../tools/ellipse"; import {AddPointTool} from "../tools/point"; import {AddArcTool} from "../tools/arc"; import {EditCircleTool} from "../tools/circle"; -import {IoIosHand} from "react-icons/io"; -import {ReferencePointTool} from "../tools/origin"; -import {GiCrosshair} from "react-icons/gi"; export default [ - { - id: 'PanTool', - shortName: 'Pan', - kind: 'Tool', - description: 'Pan mode', - icon: IoIosHand, - - invoke: (ctx) => { - ctx.viewer.toolManager.releaseControl(); - - } - - }, - - { - id: 'ReferencePointTool', - shortName: 'Set Origin', - kind: 'Tool', - description: 'Sets reference point for commands', - icon: GiCrosshair, - command: 'origin', - invoke: (ctx) => { - ctx.viewer.toolManager.takeControl(new ReferencePointTool(ctx.viewer)); - } - - }, - { id: 'PointTool', shortName: 'Point', diff --git a/web/app/sketcher/components/ConstraintEditor.jsx b/web/app/sketcher/components/ConstraintEditor.jsx index 33c51d84..b4862284 100644 --- a/web/app/sketcher/components/ConstraintEditor.jsx +++ b/web/app/sketcher/components/ConstraintEditor.jsx @@ -8,8 +8,8 @@ import CheckboxControl from "ui/components/controls/CheckboxControl"; import Window from "ui/components/Window"; import Field from "ui/components/controls/Field"; import Label from "../../../../modules/ui/components/controls/Label"; -import {SketcherAppContext} from "./SketcherApp"; import {EMPTY_OBJECT} from "../../../../modules/gems/objects"; +import {SketcherAppContext} from "./SketcherAppContext"; export function ConstraintEditor() { diff --git a/web/app/sketcher/components/ConstraintExplorer.jsx b/web/app/sketcher/components/ConstraintExplorer.jsx index 8a78a834..6fde72a2 100644 --- a/web/app/sketcher/components/ConstraintExplorer.jsx +++ b/web/app/sketcher/components/ConstraintExplorer.jsx @@ -2,11 +2,10 @@ import React, {useContext, useEffect} from 'react'; import ls from './ConstraintExplorer.less'; import Fa from 'ui/components/Fa'; import {useStream} from "ui/effects"; -import {SketcherAppContext} from "./SketcherApp"; import cx from 'classnames'; import {editConstraint} from "./ConstraintEditor"; import {NoIcon} from "../icons/NoIcon"; - +import {SketcherAppContext} from "./SketcherAppContext"; export function ConstraintExplorer(props) { return diff --git a/web/app/sketcher/components/ContextualControls.jsx b/web/app/sketcher/components/ContextualControls.jsx index 4e7d045f..35d48ea2 100644 --- a/web/app/sketcher/components/ContextualControls.jsx +++ b/web/app/sketcher/components/ContextualControls.jsx @@ -2,11 +2,11 @@ import React, {useContext} from 'react'; import ls from './ContextualControls.less'; import {matchAvailableActions} from "../actions"; import {useStream} from "../../../../modules/ui/effects"; -import {SketcherAppContext} from "./SketcherApp"; import {MatchIndex, matchSelection} from "../selectionMatcher"; import {ConstraintButton, GeneratorButton} from "./ConstraintExplorer"; import {Columnizer} from "../../../../modules/ui/components/Columnizer"; import {NoIcon} from "../icons/NoIcon"; +import {SketcherAppContext} from "./SketcherAppContext"; export function ContextualControls() { diff --git a/web/app/sketcher/components/SketchManager.jsx b/web/app/sketcher/components/SketchManager.jsx index c3b57ece..a7c538de 100644 --- a/web/app/sketcher/components/SketchManager.jsx +++ b/web/app/sketcher/components/SketchManager.jsx @@ -1,12 +1,11 @@ import React, {useContext, useMemo, useState} from 'react'; -import {SketcherAppContext} from "./SketcherApp"; import {useStreamWithUpdater} from "ui/effects"; import Window, {DIRECTIONS} from "ui/components/Window"; -import App2D from "../sketcherContext"; import Stack from "ui/components/Stack"; import Button from "ui/components/controls/Button"; import {RiDeleteBinLine} from "react-icons/ri"; import {SKETCHER_STORAGE_PREFIX} from "../project"; +import {SketcherAppContext} from "./SketcherAppContext"; export function SketchManager() { diff --git a/web/app/sketcher/components/SketchObjectExplorer.jsx b/web/app/sketcher/components/SketchObjectExplorer.jsx index 6d05941d..f39e2576 100644 --- a/web/app/sketcher/components/SketchObjectExplorer.jsx +++ b/web/app/sketcher/components/SketchObjectExplorer.jsx @@ -1,84 +1,85 @@ -import React from 'react'; +import React, {useContext, useState} from 'react'; import cx from 'classnames'; import ls from './SketchObjectExplorer.less' - -import connect from 'ui/connect'; import {combine} from 'lstream'; -import mapContext from '../../../../modules/ui/mapContext'; +import {useStream} from "../../../../modules/ui/effects"; +import {SketcherAppContext} from "./SketcherAppContext"; -@connect(streams => combine(streams.sketcherApp.objects, streams.sketcherApp.selection) - .map(([objects, selection]) => ({ - objects, - selection - }))) -@mapContext(ctx => ({ - select: (obj, exclusive) => { - let viewer = ctx.services.sketcher.inPlaceEditor.viewer; + +export function SketchObjectExplorer() { + + const [modification, setModification] = useState(0); + const stream = useStream(ctx => combine(ctx.viewer.streams.objects, ctx.viewer.streams.selection)); + const ctx = useContext(SketcherAppContext); + if (!stream) { + return null + } + const [objects, selection] = stream; + + const select = (obj, exclusive) => { + let viewer = ctx.viewer; viewer.select([obj], exclusive); viewer.refresh(); - }, - deselect: obj => { - let viewer = ctx.services.sketcher.inPlaceEditor.viewer; + }; + const deselect = obj => { + let viewer = ctx.viewer; viewer.deselect(obj); viewer.refresh(); - }, - setRole: (obj, role) => { - let viewer = ctx.services.sketcher.inPlaceEditor.viewer; + }; + const setRole = (obj, role) => { + let viewer = ctx.viewer; if (obj.aux) { return; } obj.role = role; viewer.refresh(); - } -})) -export class SketchObjectExplorer extends React.Component { + }; - render() { - const {objects} = this.props; - return -
Objects
-
- {objects.map(o =>
- - {this.getObjectRole(o)} - this.tweakSelection(o, e.shiftKey)} className={cx(ls.objectTag, o.marked&&ls.selected)}>{o.simpleClassName} {o.id} - ... -
)} -
-
- } - - tweakSelection(obj, shiftKey) { + const tweakSelection = (obj, shiftKey) => { if (obj.marked) { - this.props.deselect(obj); + deselect(obj); } else { - this.props.select(obj, !shiftKey); + select(obj, !shiftKey); } - } - - tweakRole(obj) { - if (obj.role === 'construction') { - this.props.setRole(obj, null); - } else if (obj.role === null) { - this.props.setRole(obj, 'construction'); - } - this.forceUpdate(); - } + }; - getObjectRole(o) { + const tweakRole = (obj) => { + if (obj.role === 'construction') { + setRole(obj, null); + } else if (obj.role === null) { + setRole(obj, 'construction'); + } + setModification(count => count + 1); + }; + + const getObjectRole = (o) => { if (o.aux) { return B } else if (o.role === 'construction') { - return this.tweakRole(o)} title="construction object not used for 3D operations" className={cx(ls.objectRole, ls.construction)}>C + return tweakRole(o)} title="construction object not used for 3D operations" + className={cx(ls.objectRole, ls.construction)}>C } else { - return this.tweakRole(o)} title="sketch object participates in 3D operations" className={cx(ls.objectRole, ls.sketch)}>S + return tweakRole(o)} title="sketch object participates in 3D operations" + className={cx(ls.objectRole, ls.sketch)}>S } - } + }; + return +
Objects
+
+ {objects.map(o =>
+ + {getObjectRole(o)} + tweakSelection(o, e.shiftKey)} + className={cx(ls.objectTag, o.marked && ls.selected)}>{o.simpleClassName} {o.id} + ... +
)} +
+
} function ObjectIcon({object}) { - + return null; } diff --git a/web/app/sketcher/components/SketcherActionButton.jsx b/web/app/sketcher/components/SketcherActionButton.jsx index 0fb70fe9..0a36de2f 100644 --- a/web/app/sketcher/components/SketcherActionButton.jsx +++ b/web/app/sketcher/components/SketcherActionButton.jsx @@ -1,6 +1,6 @@ import {getSketcherAction} from "../actions"; import React, {useContext} from "react"; -import {SketcherAppContext} from "./SketcherApp"; +import {SketcherAppContext} from "./SketcherAppContext"; export function SketcherActionButton({actionId, text=false}) { diff --git a/web/app/sketcher/components/SketcherApp.jsx b/web/app/sketcher/components/SketcherApp.jsx index af3b393d..f5962f7b 100644 --- a/web/app/sketcher/components/SketcherApp.jsx +++ b/web/app/sketcher/components/SketcherApp.jsx @@ -17,7 +17,9 @@ import {SketcherPropertiesView} from "./SketcherPropertiesView"; import {SketcherDimensionView} from "./SketcherDimensionsView"; import {SketcherTerminal} from "./TerminalView"; -export const SketcherAppContext = React.createContext({}); +import {SketcherAppContext} from './SketcherAppContext'; + +export {SketcherAppContext}; export function SketcherApp({applicationContext}) { return diff --git a/web/app/sketcher/components/SketcherAppContext.js b/web/app/sketcher/components/SketcherAppContext.js new file mode 100644 index 00000000..61c8c651 --- /dev/null +++ b/web/app/sketcher/components/SketcherAppContext.js @@ -0,0 +1,3 @@ +import React from "react"; + +export const SketcherAppContext = React.createContext({}); \ No newline at end of file diff --git a/web/app/sketcher/components/SketcherOperationWizard.jsx b/web/app/sketcher/components/SketcherOperationWizard.jsx index 7f1629dc..25475802 100644 --- a/web/app/sketcher/components/SketcherOperationWizard.jsx +++ b/web/app/sketcher/components/SketcherOperationWizard.jsx @@ -5,8 +5,8 @@ import Window from "ui/components/Window"; import Stack from "ui/components/Stack"; import ButtonGroup from "ui/components/controls/ButtonGroup"; import Button from "ui/components/controls/Button"; -import {SketcherAppContext} from "./SketcherApp"; import {useStreamWithUpdater} from "../../../../modules/ui/effects"; +import {SketcherAppContext} from "./SketcherAppContext"; export default function SketcherOperationWizard({}) { diff --git a/web/app/sketcher/components/StageControl.jsx b/web/app/sketcher/components/StageControl.jsx index 4fb1fddd..7b8c7c5d 100644 --- a/web/app/sketcher/components/StageControl.jsx +++ b/web/app/sketcher/components/StageControl.jsx @@ -1,7 +1,7 @@ import React, {useContext} from 'react'; -import {useStream, useStreamWithUpdater} from "../../../../modules/ui/effects"; +import {useStreamWithUpdater} from "../../../../modules/ui/effects"; import ls from "./ContextualControls.less"; -import {SketcherAppContext} from "./SketcherApp"; +import {SketcherAppContext} from "./SketcherAppContext"; export function StageControl() { diff --git a/web/app/sketcher/components/TerminalView.jsx b/web/app/sketcher/components/TerminalView.jsx index bc9663ff..4a18b0cf 100644 --- a/web/app/sketcher/components/TerminalView.jsx +++ b/web/app/sketcher/components/TerminalView.jsx @@ -1,11 +1,11 @@ import React, {useCallback, useContext, useEffect, useRef, useState} from 'react'; import {useStreamWithUpdater} from "ui/effects"; import Window from "ui/components/Window"; -import {SketcherAppContext} from "./SketcherApp"; import {getAllSketcherActions} from "../actions"; import {memoize} from "lodash/function"; import ls from './TerminalView.less'; import {DIRECTIONS} from "ui/components/Window"; +import {SketcherAppContext} from "./SketcherAppContext"; export function TerminalView({visible, output, addToOutput, onClose, variantsSupplier, commandProcessor}) { diff --git a/web/app/sketcher/sketcherContext.js b/web/app/sketcher/sketcherContext.js index 23fba3e4..f3d340d5 100644 --- a/web/app/sketcher/sketcherContext.js +++ b/web/app/sketcher/sketcherContext.js @@ -7,13 +7,16 @@ import {Project} from "./project"; export function createAppContext() { + const ctx = createEssentialAppContext(document.getElementById('viewer')); + ctx.project = new Project(ctx.viewer); + return ctx; +} - const viewer = new Viewer(document.getElementById('viewer'), IO); +export function createEssentialAppContext(canvas) { return { - viewer, - project: new Project(viewer), + viewer: new Viewer(canvas, IO), get actions() { return getSketcherActionIndex(); @@ -35,4 +38,3 @@ export function createAppContext() { } - diff --git a/web/app/sketcher/uiConfig.js b/web/app/sketcher/uiConfig.js index 9b329d31..f76d0552 100644 --- a/web/app/sketcher/uiConfig.js +++ b/web/app/sketcher/uiConfig.js @@ -1,14 +1,16 @@ import constraintGlobalActions from "./actions/constraintGlobalActions"; import measureActions from "./actions/measureActions"; -import toolActions from "./actions/toolActions"; +import objectToolActions from "./actions/objectToolActions"; import commonActions from "./actions/commonActions"; -import {removeInPlace} from "../../../modules/gems/iterables"; +import {insertAfter, removeInPlace} from "../../../modules/gems/iterables"; +import generalToolActions from "./actions/generalToolActions"; export const sketcherRightToolbarConfig = constraintGlobalActions.map(a => a.id); export const sketcherTopToolbarConfig = [ ...commonActions.map(a => a.id), - ...toolActions.map(a => a.id), + ...generalToolActions.map(a => a.id), + ...objectToolActions.map(a => a.id), 'Offset', '-', ...measureActions.map(a => a.id) @@ -18,11 +20,4 @@ insertAfter(sketcherTopToolbarConfig, 'Export', '-'); insertAfter(sketcherTopToolbarConfig, 'PanTool', '-'); insertAfter(sketcherTopToolbarConfig, 'BezierTool', '-'); -function insertAfter(arr, item, toAdd) { - const index = arr.indexOf(item); - if (index !== -1) { - arr.splice(index+1, 0, toAdd); - } -} - removeInPlace(sketcherTopToolbarConfig, 'ToggleTerminal'); \ No newline at end of file