change the main toolbar in the sketcher mode

This commit is contained in:
Val Erastov (xibyte) 2020-05-05 02:13:41 -07:00
parent 2941687dbc
commit 1b766da2d4
44 changed files with 427 additions and 311 deletions

View file

@ -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([]);

View file

@ -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;

View file

@ -20,4 +20,14 @@ export function ToolbarButton({children, disabled, ...props}) {
export function ToolbarSplitter() {
return <div className={ls.splitter} />
}
export function ToolbarBraker() {
return <div className={ls.braker} />
}
export function ToolbarGroup({children}) {
return <div className={ls.group} >
{children}
</div>;
}

View file

@ -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;
}

6
package-lock.json generated
View file

@ -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"
},

View file

@ -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"

View file

@ -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)
}
}
});
}

View file

@ -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)
}
}
}};
}

View file

@ -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 => <ActionButtonBehavior actionId={actionId} >
{bProps => <Comp {...bProps} {...props} />}
</ActionButtonBehavior>;
}

View file

@ -0,0 +1,3 @@
import React from "react";
export const AppContext = React.createContext({});

View file

@ -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 <Toolbar flat>
<div className={ls.mainActions}>
<ToolbarActionButtons actions={actions} />
<ToolbarActionButtons actions={actions} showTitles={showTitles}/>
</div>
<div className={ls.quickButtons}>

View file

@ -14,5 +14,9 @@
.mainActions {
flex: 1 1;
display: flex;
overflow-x: auto;
flex-wrap: wrap;
svg {
width: 24px;
height: 24px;
}
}

View file

@ -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 <ControlBar left={<LeftGroup />} right={<RightGroup />}/>;
@ -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 <ActionButtonBehavior actionId={actionId}>
{behaviourProps => <ActionButton {...behaviourProps} {...actionAppearance} {...actionState} {...props} />}
</ActionButtonBehavior>;
}

View file

@ -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 <Toolbar size={size} {...props}>
@ -17,31 +14,43 @@ function ConfigurableToolbar({actions, size, ...props}) {
</Toolbar>
}
export function ToolbarActionButtons({actions, size}) {
export function ToolbarActionButtons({actions, showTitles, size}) {
return actions.map((actionRef, i) => {
if (actionRef === '-') {
return <ToolbarSplitter key={'ToolbarSplitter' + i}/>;
return <ToolbarSplitter key={'ToolbarSplitter' + i} />;
} else if (actionRef === '|') {
return <ToolbarBraker key={'ToolbarBraker' + i} />;
} else if (Array.isArray(actionRef)) {
return <div key={'ToolbarGroup' + i}>
<ToolbarGroup><ToolbarActionButtons actions={actionRef.slice(0, actionRef.length / 2)} showTitles={showTitles} size={size} /></ToolbarGroup>
<ToolbarGroup><ToolbarActionButtons actions={actionRef.slice(actionRef.length / 2, actionRef.length)} showTitles={showTitles} size={size} /></ToolbarGroup>
</div>;
}
let [id, overrides] = toIdAndOverrides(actionRef);
return <ConnectedActionButton actionId={id} key={id} size={size} {...overrides} />
return <ConnectedActionButton actionId={id} key={id} size={size} {...overrides} noLabel={!showTitles}/>
});
}
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 = <Fa fa={cssIcons} fw />;
} else if (icon32) {
icon = <ImgIcon url={icon32} size={size === 'small' ? 16 : 24} />;
if (icon) {
const Icon = icon;
icon = <Icon />;
}
if (!icon) {
if (smallOrMedium) {
if (cssIcons) {
icon = <Fa fa={cssIcons} fw />;
} else if (icon32) {
icon = <ImgIcon url={icon32} size={size === 'small' ? 16 : 24} />;
}
} else {
icon = <ImgIcon url={icon96} size={48} />;
}
} else {
icon = <ImgIcon url={icon96} size={48} />;
}
if (!icon) {
icon = <span>{symbol||(label&&label.charAt(0))}</span>;
@ -56,15 +65,27 @@ function ActionButton({label, icon96, icon32, cssIcons, symbol, size, noLabel, e
</ToolbarButton>
}
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<ActionButtonBehavior actionId={actionId}>
{behaviourProps => <ActionButton {...behaviourProps} {...actionAppearance} {...actionState} {...props} />}
</ActionButtonBehavior>;
}
export function createPlugableToolbar(streamSelector) {
return decoratorChain(
connect(streams => streamSelector(streams).map(actions => ({actions})))
)
(props => <ConfigurableToolbar {...props} />);
return function (props) {
const actions = useStream(ctx => streamSelector(ctx.streams));
if (!actions) {
return null;
}
return <ConfigurableToolbar actions={actions} {...props} />;
}
}

View file

@ -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;
}
}

View file

@ -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 {
<div className={ls.middleSection}>
<SketcherMode>
<div className={ls.overlayingPanel} >
<SketchObjectExplorer />
<ConstraintExplorer />
</div>
<InplaceSketcher>
<div className={ls.overlayingPanel} >
<Scope><SketchObjectExplorer /></Scope>
<Scope><ConstraintExplorer /></Scope>
</div>
</InplaceSketcher>
</SketcherMode>
<div className={ls.wizardArea} >
<WizardManager/>
</div>
<SketcherMode>
<div className={ls.spring} />
<div className={ls.middleRight}>
<SketcherToolbars />
</div>
</SketcherMode>
</div>
<div className={ls.bottomStack}>

View file

@ -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 <AppTabs />
const {appContext} = this.props;
return <StreamsContext.Provider value={appContext}>
<AppContext.Provider value={appContext}>
<AppTabs />
</AppContext.Provider>
</StreamsContext.Provider>
}
getChildContext() {

View file

@ -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}) => <ConnectedActionMenu key={id} menuId={id} actions={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 <ActionButtonBehavior actionId={actionId}>
{behaviourProps => <ActionMenuItem {...behaviourProps} {...actionAppearance} {...actionState} {...props} />}
</ActionButtonBehavior>;
}
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);

View file

@ -9,6 +9,7 @@ export function defineStreams({streams}) {
},
toolbars: {
headsUp: state([]),
headsUpShowTitles: state(true),
headsUpQuickActions: state([]),
sketcherGeneral: state([]),
sketcherConstraints: state([]),

View file

@ -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);

View file

@ -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 <SketcherAppContext.Provider value={sketcherAppContext}>
<StreamsContext.Provider value={sketcherAppContext}>
<Scope>{children}</Scope>
</StreamsContext.Provider>
</SketcherAppContext.Provider>
}

View file

@ -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;
}

View file

@ -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 <div className={ls.sketcherToolbars}>

View file

@ -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();
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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'
];
}

View file

@ -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));
}
},
]

View file

@ -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

View file

@ -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',

View file

@ -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() {

View file

@ -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 <React.Fragment>

View file

@ -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() {

View file

@ -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() {

View file

@ -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 <React.Fragment>
<div className={ls.titleBar}>Objects</div>
<div className={ls.scrollableArea}>
{objects.map(o => <div key={o.id} className={cx(ls.objectItem, getClassName(o))}>
<span className={ls.objectIcon}><img width="15px" src='img/vec/pointOnArc.svg' /></span>
{this.getObjectRole(o)}
<span onClick={e => this.tweakSelection(o, e.shiftKey)} className={cx(ls.objectTag, o.marked&&ls.selected)}>{o.simpleClassName} <span>{o.id}</span> </span>
<span className={ls.menuButton}>...</span>
</div>)}
</div>
</React.Fragment>
}
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 <span title="object is a readonly 3D feature/boundary" className={cx(ls.objectRole, ls.aux)}>B</span>
} else if (o.role === 'construction') {
return <span onClick={e => this.tweakRole(o)} title="construction object not used for 3D operations" className={cx(ls.objectRole, ls.construction)}>C</span>
return <span onClick={e => tweakRole(o)} title="construction object not used for 3D operations"
className={cx(ls.objectRole, ls.construction)}>C</span>
} else {
return <span onClick={e => this.tweakRole(o)} title="sketch object participates in 3D operations" className={cx(ls.objectRole, ls.sketch)}>S</span>
return <span onClick={e => tweakRole(o)} title="sketch object participates in 3D operations"
className={cx(ls.objectRole, ls.sketch)}>S</span>
}
}
};
return <React.Fragment>
<div className={ls.titleBar}>Objects</div>
<div className={ls.scrollableArea}>
{objects.map(o => <div key={o.id} className={cx(ls.objectItem, getClassName(o))}>
<span className={ls.objectIcon}><img width="15px" src='img/vec/pointOnArc.svg'/></span>
{getObjectRole(o)}
<span onClick={e => tweakSelection(o, e.shiftKey)}
className={cx(ls.objectTag, o.marked && ls.selected)}>{o.simpleClassName} <span>{o.id}</span> </span>
<span className={ls.menuButton}>...</span>
</div>)}
</div>
</React.Fragment>
}
function ObjectIcon({object}) {
return null;
}

View file

@ -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}) {

View file

@ -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 <SketcherAppContext.Provider value={applicationContext}>

View file

@ -0,0 +1,3 @@
import React from "react";
export const SketcherAppContext = React.createContext({});

View file

@ -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({}) {

View file

@ -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() {

View file

@ -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}) {

View file

@ -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() {
}

View file

@ -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');