From f9c202ba13b2bb0b9fa56c47448ac5309782be82 Mon Sep 17 00:00:00 2001 From: Val Erastov Date: Fri, 16 Feb 2018 00:50:38 -0800 Subject: [PATCH] basic actions for craft history manipulation --- web/app/cad/craft/craftHistoryUtils.js | 40 +++++++++ web/app/cad/craft/craftPlugin.js | 85 ++++++++---------- web/app/cad/craft/wizard/wizardPlugin.js | 2 +- .../cad/dom/components/OperationHistory.jsx | 25 ++++-- .../cad/dom/components/OperationHistory.less | 28 +++++- .../dom/components/wizard/HistoryWizard.jsx | 27 ++++++ web/app/cad/dom/components/wizard/Wizard.jsx | 86 ++++++++++++------- .../dom/components/wizard/WizardManager.jsx | 42 +++++---- web/app/cad/keyboard/keymaps/default.js | 1 + 9 files changed, 225 insertions(+), 111 deletions(-) create mode 100644 web/app/cad/craft/craftHistoryUtils.js create mode 100644 web/app/cad/dom/components/wizard/HistoryWizard.jsx diff --git a/web/app/cad/craft/craftHistoryUtils.js b/web/app/cad/craft/craftHistoryUtils.js new file mode 100644 index 00000000..651c239b --- /dev/null +++ b/web/app/cad/craft/craftHistoryUtils.js @@ -0,0 +1,40 @@ + +export function addModification({history, pointer}, request) { + let changingHistory = pointer !== history.length - 1; + if (changingHistory) { + history.slice(0, pointer); + history.push(request); + return { + history, + pointer: ++pointer + } + } else { + return { + history: [...history, request], + pointer: ++pointer + } + } +} + +export function stepOverridingParams({history, pointer}, params) { + history[pointer + 1] = { + type: history[pointer + 1].type, + params + }; + return { + history, + pointer: ++pointer + }; +} + +export function finishHistoryEditing({history}) { + return ({history, pointer: history.length - 1}); +} + +export function removeAndDropDependants({history}, indexToRemove) { + history = history.slice(0, indexToRemove); + return { + history, + pointer: history.length - 1 + } +} \ No newline at end of file diff --git a/web/app/cad/craft/craftPlugin.js b/web/app/cad/craft/craftPlugin.js index 099d48c6..0f222f88 100644 --- a/web/app/cad/craft/craftPlugin.js +++ b/web/app/cad/craft/craftPlugin.js @@ -1,4 +1,5 @@ import {createToken} from "bus"; +import {addModification} from './craftHistoryUtils'; export function activate({bus, services}) { @@ -7,45 +8,35 @@ export function activate({bus, services}) { pointer: -1 }); - function getHistory() { - return bus.state[TOKENS.MODIFICATIONS].history; + function isAdditiveChange({history, pointer}, {history:oldHistory, pointer:oldPointer}) { + if (pointer < oldPointer) { + return false; + } + + for (let i = 0; i <= oldPointer; i++) { + let modCurr = history[i]; + let modPrev = oldHistory[i]; + if (modCurr !== modPrev) { + return false; + } + } + return true; } - - bus.subscribe(TOKENS.HISTORY_POINTER, (pointer) => { - let history = getHistory(); - if (pointer < history.length) { - resetInternal(history.slice(0, pointer)); - bus.setState(TOKENS.MODIFICATIONS, {pointer}); + + bus.subscribe(TOKENS.MODIFICATIONS, (curr, prev) => { + let beginIndex; + if (isAdditiveChange(curr, prev)) { + beginIndex = prev.pointer + 1; + } else { + services.cadRegistry.reset(); + beginIndex = 0; + } + let {history, pointer} = curr; + for (let i = beginIndex; i <= pointer; i++) { + modifyInternal(history[i]); } }); - function remove(modificationIndex) { - bus.updateState(TOKENS.MODIFICATIONS, - ({history, pointer}) => { - return { - history: history.slice(0, modificationIndex), - pointer: Math.min(pointer, modificationIndex - 1) - } - }); - } - - function resetInternal(modifications) { - services.cadRegistry.reset(); - for (let request of modifications) { - modifyInternal(request); - } - } - - function reset(modifications) { - resetInternal(modifications); - bus.updateState(TOKENS.MODIFICATIONS, - () => { - return { - history: modifications, - pointer: modifications.length - 1 - } - }); - } function modifyInternal(request) { let op = services.operation.registry[request.type]; @@ -57,23 +48,21 @@ export function activate({bus, services}) { } function modify(request) { - modifyInternal(request); - - bus.updateState(TOKENS.MODIFICATIONS, - ({history, pointer}) => { - return { - history: [...history, request], - pointer: pointer++ - } - }); + bus.updateState(TOKENS.MODIFICATIONS, modifications => addModification(modifications, request)); } - + + function reset(modifications) { + bus.dispatch(TOKENS.MODIFICATIONS, { + history: modifications, + pointer: modifications.length - 1 + }); + } + services.craft = { - modify, remove, reset, TOKENS + modify, reset, TOKENS } } export const TOKENS = { - MODIFICATIONS: createToken('craft', 'modifications'), - HISTORY_POINTER: createToken('craft', 'historyPointer'), + MODIFICATIONS: createToken('craft', 'modifications') }; diff --git a/web/app/cad/craft/wizard/wizardPlugin.js b/web/app/cad/craft/wizard/wizardPlugin.js index ebd5633f..deff21b1 100644 --- a/web/app/cad/craft/wizard/wizardPlugin.js +++ b/web/app/cad/craft/wizard/wizardPlugin.js @@ -15,7 +15,7 @@ export function activate({bus, services}) { }); bus.subscribe(TOKENS.CLOSE, wizard => { - bus.updateState(TOKENS.WIZARDS, opened => opened.filter(w => w === wizard)); + bus.updateState(TOKENS.WIZARDS, opened => opened.filter(w => w !== wizard)); }); } diff --git a/web/app/cad/dom/components/OperationHistory.jsx b/web/app/cad/dom/components/OperationHistory.jsx index b534ffa2..b8ff2c98 100644 --- a/web/app/cad/dom/components/OperationHistory.jsx +++ b/web/app/cad/dom/components/OperationHistory.jsx @@ -4,27 +4,35 @@ import Stack from 'ui/components/Stack'; import connect from 'ui/connect'; import Fa from 'ui/components/Fa'; import ImgIcon from 'ui/components/ImgIcon'; -import ls from './OperationHistory.less' +import ls from './OperationHistory.less'; +import cx from 'classnames'; import {TOKENS as CRAFT_TOKENS} from '../../craft/craftPlugin'; +import ButtonGroup from '../../../../../modules/ui/components/controls/ButtonGroup'; +import Button from '../../../../../modules/ui/components/controls/Button'; +import {removeAndDropDependants} from '../../craft/craftHistoryUtils'; -function OperationHistory({history, pointer}, {services: {operation: operationService}}) { +function OperationHistory({history, pointer, setHistoryPointer, remove}, {services: {operation: operationService}}) { + let lastMod = history.length - 1; return {history.map(({type, params}, index) => { let {appearance, paramsInfo} = getDescriptor(type, operationService.registry); - return
+ return
setHistoryPointer(index - 1)} + className={cx(ls.item, pointer + 1 === index && ls.selected)}> {appearance && } {type} {paramsInfo && paramsInfo(params)} - + remove(index)}/>
; })} - + {pointer !== lastMod && + + } ; } @@ -41,4 +49,9 @@ OperationHistory.contextTypes = { services: PropTypes.object }; -export default connect(OperationHistory, CRAFT_TOKENS.MODIFICATIONS); +export default connect(OperationHistory, CRAFT_TOKENS.MODIFICATIONS, { + mapActions: ({setState, updateState}) => ({ + setHistoryPointer: pointer => setState(CRAFT_TOKENS.MODIFICATIONS, {pointer}), + remove: atIndex => updateState(CRAFT_TOKENS.MODIFICATIONS, modifications => removeAndDropDependants(modifications, atIndex)) + }) +}); diff --git a/web/app/cad/dom/components/OperationHistory.less b/web/app/cad/dom/components/OperationHistory.less index fa3b409e..e5513396 100644 --- a/web/app/cad/dom/components/OperationHistory.less +++ b/web/app/cad/dom/components/OperationHistory.less @@ -1,8 +1,32 @@ .item { word-wrap: break-word; word-break: break-all; + cursor: pointer; + &.selected, &:hover { + background-color: #780000; + } + display: flex; + justify-content: space-between; + align-items: baseline; + & .buttons { + display: none; + } + &:hover .buttons { + display: initial; + } } .buttons { - -} \ No newline at end of file + display: none; + font-size: 1.3rem; + & > * { + padding: 0 0.3rem; + &.danger:hover { + color: red; + } + } +} + +.buttons > i:hover { + color: yellowgreen; +} diff --git a/web/app/cad/dom/components/wizard/HistoryWizard.jsx b/web/app/cad/dom/components/wizard/HistoryWizard.jsx new file mode 100644 index 00000000..5d27f7fe --- /dev/null +++ b/web/app/cad/dom/components/wizard/HistoryWizard.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import connect from '../../../../../../modules/ui/connect'; +import {TOKENS as CRAFT_TOKENS} from '../../../craft/craftPlugin'; +import Wizard from './Wizard'; +import {finishHistoryEditing, stepOverridingParams} from '../../../craft/craftHistoryUtils'; + +function HistoryWizard({history, pointer, step, cancel, offset}) { + if (pointer === history.length - 1) { + return null; + } + + let {type, params: initialState} = history[pointer + 1]; + return + +} + +export default connect(HistoryWizard, CRAFT_TOKENS.MODIFICATIONS, { + mapActions: ({updateState}) => ({ + step: (params) => updateState(CRAFT_TOKENS.MODIFICATIONS, modifications => stepOverridingParams(modifications, params)), + cancel: () => updateState(CRAFT_TOKENS.MODIFICATIONS, modifications => finishHistoryEditing(modifications)), + }), + mapSelfProps: ({offset}) => ({offset}) +}); + +const NOOP = () => {}; diff --git a/web/app/cad/dom/components/wizard/Wizard.jsx b/web/app/cad/dom/components/wizard/Wizard.jsx index 309f94f8..cc6cb390 100644 --- a/web/app/cad/dom/components/wizard/Wizard.jsx +++ b/web/app/cad/dom/components/wizard/Wizard.jsx @@ -15,45 +15,57 @@ import {CURRENT_SELECTION} from "../../../craft/wizard/wizardPlugin"; import ls from './Wizard.less'; import RadioButtons, {RadioButton} from "ui/components/controls/RadioButtons"; import CadError from '../../../../utils/errors'; +import {createPreviewer} from '../../../preview/scenePreviewer'; export default class Wizard extends React.Component { - constructor({initialState, metadata, previewer}, {services: {selection}}) { + constructor({initialState, type}, {services}) { super(); + let {metadata, previewGeomProvider} = services.operation.get(type); + this.metadata = metadata; + this.previewGeomProvider = previewGeomProvider; + this.state = {hasError: false}; this.params = {}; - metadata.forEach(([name, type, v]) => { + this.metadata.forEach(([name, type, v]) => { if (type === 'face' && v === CURRENT_SELECTION) { - let selectedFace = selection.face()[0]; + let selectedFace = services.selection.face()[0]; v = selectedFace ? selectedFace.id : ''; } this.params[name] = v }); Object.assign(this.params, initialState); - - this.preview = previewer(this.params); + this.previewer = createPreviewer(previewGeomProvider, services); + } + + componentDidMount() { + this.preview = this.previewer(this.params); + } + + componentWillUnmount() { + this.preview.dispose(); } render() { - let {left, title, metadata} = this.props; + let {type, left} = this.props; return - {metadata.map(([name, type, , params], index) => { + {this.metadata.map(([name, type, , params], index) => { return {this.controlForType(name, type, params)} } )} - + {this.state.hasError &&
@@ -70,7 +82,7 @@ export default class Wizard extends React.Component { onKeyDown = e => { switch (e.keyCode) { case 27 : - this.onClose(); + this.cancel() break; case 13 : this.onOK(); @@ -86,34 +98,44 @@ export default class Wizard extends React.Component { toFocus.focus() }; - onClose = () => { - this.preview.dispose(); - this.props.onCancel(); + cancel = () => { + if (this.props.onCancel) { + this.props.onCancel(); + } + this.props.close(); }; onOK = () => { try { - this.props.onOK(this.params); - this.onClose(); - } catch (error) { - let stateUpdate = { - hasError: true - }; - let printError = true; - if (error.TYPE === CadError) { - let {code, userMessage, kind} = error; - printError = !code; - if (code && kind === CadError.KIND.INTERNAL_ERROR) { - console.warn('Operation Error Code: ' + code); - } - Object.assign(stateUpdate, {code, userMessage}); - } - if (printError) { - console.error(error); + if (this.props.onOK) { + this.props.onOK(this.params); + } else { + this.context.services.craft.modify({type: this.props.type, params: this.params}); } - this.setState(stateUpdate); + this.props.close(); + } catch (error) { + this.handleError(error); } }; + + handleError(error) { + let stateUpdate = { + hasError: true + }; + let printError = true; + if (error.TYPE === CadError) { + let {code, userMessage, kind} = error; + printError = !code; + if (code && kind === CadError.KIND.INTERNAL_ERROR) { + console.warn('Operation Error Code: ' + code); + } + Object.assign(stateUpdate, {code, userMessage}); + } + if (printError) { + console.error(error); + } + this.setState(stateUpdate); + } controlForType(name, type, params, tabindex) { const onChange = val => { diff --git a/web/app/cad/dom/components/wizard/WizardManager.jsx b/web/app/cad/dom/components/wizard/WizardManager.jsx index fd5abfc0..efde3391 100644 --- a/web/app/cad/dom/components/wizard/WizardManager.jsx +++ b/web/app/cad/dom/components/wizard/WizardManager.jsx @@ -1,30 +1,28 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, {Fragment} from 'react'; import {TOKENS as WIZARD_TOKENS} from '../../../craft/wizard/wizardPlugin'; import connect from 'ui/connect'; -import Wizard from "./Wizard"; -import {createPreviewer} from "../../../preview/scenePreviewer"; +import Wizard from './Wizard'; +import HistoryWizard from './HistoryWizard'; -function WizardManager({wizards, close}, {services}) { - return wizards.map( ({type, initialState}, wizardIndex) => { - let {metadata, previewGeomProvider, run} = services.operation.get(type); - - function onOK(params) { - services.craft.modify({type, params}); - close(); - } - - let previewer = createPreviewer(previewGeomProvider, services); - return - }); +function WizardManager({wizards, close}) { + return + {wizards.map((wizardRef, wizardIndex) => { + let {type, initialState} = wizardRef; + + const closeInstance = () => close(wizardRef); + return + })} + + } -WizardManager.contextTypes = { - services: PropTypes.object -}; + +function offset(wizardIndex) { + return 70 + (wizardIndex * (250 + 20)); +} export default connect(WizardManager, WIZARD_TOKENS.WIZARDS, { mapProps: ([wizards]) => ({wizards}), diff --git a/web/app/cad/keyboard/keymaps/default.js b/web/app/cad/keyboard/keymaps/default.js index b165f256..3d43cf6b 100644 --- a/web/app/cad/keyboard/keymaps/default.js +++ b/web/app/cad/keyboard/keymaps/default.js @@ -2,6 +2,7 @@ export default { 'CUT': 'c', 'EXTRUDE': 'e', 'PLANE': 'p', + 'BOX': 'b', 'ZoomIn': '+', 'ZoomOut': '-', 'menu.craft': 'shift+c',