diff --git a/modules/gems/objects.js b/modules/gems/objects.js new file mode 100644 index 00000000..e1e208ae --- /dev/null +++ b/modules/gems/objects.js @@ -0,0 +1 @@ +export const EMPTY_OBJECT = Object.freeze({}); diff --git a/modules/lstream/base.js b/modules/lstream/base.js index 76a1b778..3d25c250 100644 --- a/modules/lstream/base.js +++ b/modules/lstream/base.js @@ -13,8 +13,12 @@ export class StreamBase { pairwise(first) { return new PairwiseStream(this, first); } + + scan(initAccumulator) { + return new ScanStream(this, initAccumulator); + } - keep() { + remember() { let stateStream = new StateStream(undefined); this.attach(v => stateStream.next(v)); return stateStream; @@ -25,4 +29,4 @@ const {MapStream} = require('./map'); const {FilterStream} = require('./filter'); const {StateStream} = require('./state'); const {PairwiseStream} = require('./pairwise'); - +const {ScanStream} = require('./scan'); diff --git a/modules/lstream/scan.js b/modules/lstream/scan.js new file mode 100644 index 00000000..10a3810c --- /dev/null +++ b/modules/lstream/scan.js @@ -0,0 +1,14 @@ +import {StreamBase} from './base'; + +export class ScanStream extends StreamBase { + + constructor(stream, initAccumulator) { + super(); + this.stream = stream; + this.acc = initAccumulator; + } + + attach(observer) { + return this.stream.attach(v => this.acc = observer(this.acc, v)); + } +} diff --git a/modules/ui/components/AuxWidget.jsx b/modules/ui/components/AuxWidget.jsx index 9c203fb7..7efcaad6 100644 --- a/modules/ui/components/AuxWidget.jsx +++ b/modules/ui/components/AuxWidget.jsx @@ -2,10 +2,13 @@ import React from 'react'; import cx from 'classnames'; import ls from './AuxWidget.less'; -import AdjustableAbs from "./AdjustableAbs"; +import AdjustableAbs from './AdjustableAbs'; -export default function AuxWidget({flatTop, flatBottom, children, className, ...props}) { - return +export default function AuxWidget({flatTop, flatBottom, flatRight, flatLeft, children, className, ...props}) { + return {children} - + ; } \ No newline at end of file diff --git a/modules/ui/components/AuxWidget.less b/modules/ui/components/AuxWidget.less index 795da87d..b50dc3bf 100644 --- a/modules/ui/components/AuxWidget.less +++ b/modules/ui/components/AuxWidget.less @@ -1,3 +1,4 @@ +@import "./flatEdges.less"; @border-radius: 3px; .root { @@ -8,9 +9,18 @@ } .flatBottom { - border-radius: @border-radius @border-radius 0 0; + ._flatBottom(@border-radius); } .flatTop { - border-radius: 0 0 @border-radius @border-radius; + ._flatTop(@border-radius); } + +.flatRight { + ._flatRight(@border-radius); +} + +.flatLeft { + ._flatLeft(@border-radius); +} + diff --git a/modules/ui/components/RenderObject.jsx b/modules/ui/components/RenderObject.jsx new file mode 100644 index 00000000..e70b64d6 --- /dev/null +++ b/modules/ui/components/RenderObject.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import ls from './RenderObject.less'; + +export default function RenderObject({object}) { + return
+} + +function RenderObjectImpl({object, inner}) { + if (object === undefined || object === null) { + return {object}; + } + if (typeof object === 'object') { + return
+ {Object.keys(object).map(field =>
+ {field}: +
)} +
; + } else if (Array.isArray(object)) { + return
+ {Object.map(object).map((item, i) =>
+
+
)} + {Object.keys(object).map(field =>
+ {field}: +
)} +
; + } else { + return {object}; + } +} \ No newline at end of file diff --git a/modules/ui/components/RenderObject.less b/modules/ui/components/RenderObject.less new file mode 100644 index 00000000..7f502749 --- /dev/null +++ b/modules/ui/components/RenderObject.less @@ -0,0 +1,3 @@ +.root { + line-height: 1.5; +} \ No newline at end of file diff --git a/modules/ui/components/Widget.jsx b/modules/ui/components/Widget.jsx new file mode 100644 index 00000000..d53f1f7c --- /dev/null +++ b/modules/ui/components/Widget.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import cx from 'classnames'; + +import ls from './Widget.less'; +import AdjustableAbs from './AdjustableAbs'; +import Fa from './Fa'; +import SymbolButton from './controls/SymbolButton'; + +export default function Widget({flatTop, flatBottom, flatRight, flatLeft, children, className, title, onClose, ...props}) { + return +
+
{title}
+ + + +
+
+ {children} +
+
; +} diff --git a/modules/ui/components/Widget.less b/modules/ui/components/Widget.less new file mode 100644 index 00000000..c53b09be --- /dev/null +++ b/modules/ui/components/Widget.less @@ -0,0 +1,50 @@ +@import "./flatEdges.less"; +@border-radius: 5px; + +.root { + color: #fff; + background-color: rgba(40, 40, 40, 0.95); + border: solid 1px #000; + border-radius: @border-radius; +} + +.flatBottom { + ._flatBottom(@border-radius); +} + +.flatTop { + ._flatTop(@border-radius); +} + +.flatRight { + ._flatRight(@border-radius); +} + +.flatLeft { + ._flatLeft(@border-radius); +} + +//IMPL + +.root { + padding: 5px 5px 10px 15px; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.title { + font-size: 16px; +} + +.headerButtons { + font-size: 20px; + margin-left: 10px; +} + +.content { + padding-top: 5px; +} diff --git a/modules/ui/components/controls/Button.jsx b/modules/ui/components/controls/Button.jsx index 8a465580..1e2570e8 100644 --- a/modules/ui/components/controls/Button.jsx +++ b/modules/ui/components/controls/Button.jsx @@ -1,10 +1,11 @@ import React from 'react'; import ls from './Button.less' +import cx from 'classnames'; export default function Button({type, onClick, children}) { - return + return } diff --git a/modules/ui/components/controls/Button.less b/modules/ui/components/controls/Button.less index 6d918c15..ae77c0a9 100644 --- a/modules/ui/components/controls/Button.less +++ b/modules/ui/components/controls/Button.less @@ -1,6 +1,10 @@ @import '../../styles/theme.less'; @import '../../styles/mixins.less'; +.button { + line-height: 1.5; +} + .neutral, .accent, .danger { background-color: darken(@color-neutral, 10%); } diff --git a/modules/ui/components/controls/SymbolButton.jsx b/modules/ui/components/controls/SymbolButton.jsx new file mode 100644 index 00000000..3c9af559 --- /dev/null +++ b/modules/ui/components/controls/SymbolButton.jsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import ls from './SymbolButton.less'; + +export default function SymbolButton({type, onClick, children}) { + return
{children}
+} + +SymbolButton.defaultProps = { + type: 'neutral', +}; + + + + diff --git a/modules/ui/components/controls/SymbolButton.less b/modules/ui/components/controls/SymbolButton.less new file mode 100644 index 00000000..a147fc6f --- /dev/null +++ b/modules/ui/components/controls/SymbolButton.less @@ -0,0 +1,30 @@ +@import '../../styles/theme.less'; + +.symbolButton { + &:active { + transition: 100ms; + } + cursor: pointer; +} + +.neutral { + .symbolButton; + color: #fff; + &:hover { + color: #EFEFEF; + } + &:active { + color: #9cdaf7; + } +} + +.danger { + .symbolButton; + color: #fff; + &:hover { + color: @color-danger; + } + &:active { + color: lighten(@color-danger, 20%); + } +} \ No newline at end of file diff --git a/modules/ui/components/flatEdges.less b/modules/ui/components/flatEdges.less new file mode 100644 index 00000000..f1498dc6 --- /dev/null +++ b/modules/ui/components/flatEdges.less @@ -0,0 +1,20 @@ + +._flatBottom(@border-radius) { + border-radius: @border-radius @border-radius 0 0; +} + +._flatTop(@border-radius) { + border-radius: 0 0 @border-radius @border-radius; +} + +._flatRight(@border-radius) { + border-radius: @border-radius 0 0 @border-radius; +} + +._flatLeft(@border-radius) { + border-radius: 0 @border-radius @border-radius 0; +} + + + + \ No newline at end of file diff --git a/modules/ui/connect.js b/modules/ui/connect.js index f1f01c8c..7f42e98c 100644 --- a/modules/ui/connect.js +++ b/modules/ui/connect.js @@ -5,12 +5,18 @@ export default function connect(streamProvider) { return function (Component) { return class Connected extends React.Component { + state = {hasError: false}; + streamProps = {}; componentWillMount() { let stream = streamProvider(context.streams, this.props); this.detacher = stream.attach(data => { this.streamProps = data; + if (this.state.hasError) { + this.setState({hasError: false}); + return; + } this.forceUpdate(); }); } @@ -20,10 +26,18 @@ export default function connect(streamProvider) { } render() { + if (this.state.hasError) { + return null; + } + return ; } + + componentDidCatch() { + this.setState({hasError: true}); + } }; } } diff --git a/modules/ui/errorBoundary.js b/modules/ui/errorBoundary.js index b3413411..9f2d8da4 100644 --- a/modules/ui/errorBoundary.js +++ b/modules/ui/errorBoundary.js @@ -1,8 +1,9 @@ import React from 'react'; +import context from 'context'; -export default function errorBoundary(message, fix) { +export default function errorBoundary(message, fix, resetOn) { return function(Comp) { - return class extends React.Component { + class ErrorBoundary extends React.Component { state = { hasError: false, @@ -17,8 +18,26 @@ export default function errorBoundary(message, fix) { this.setState({hasError: false, fixAttempt: true}); } } + if (resetOn) { + let stream = resetOn(context.streams); + if (stream) { + this.attcahing = true; + this.detacher = stream.attach(this.reset); + this.attcahing = false; + } + } } + reset = () => { + if (this.attcahing) { + return; + } + this.setState({hasError: false, fixAttempt: false}); + if (this.detacher) { + this.detacher(); + } + }; + render() { if (this.state.hasError) { return message || null; @@ -26,5 +45,6 @@ export default function errorBoundary(message, fix) { return ; } } + return ErrorBoundary; } } \ No newline at end of file diff --git a/modules/ui/positionUtils.js b/modules/ui/positionUtils.js new file mode 100644 index 00000000..e7c095a1 --- /dev/null +++ b/modules/ui/positionUtils.js @@ -0,0 +1,7 @@ +export function aboveElement(el) { + let r = el.getBoundingClientRect(); + return { + x: r.left, + y: r.top + } +} diff --git a/modules/ui/styles/init/main.less b/modules/ui/styles/init/main.less index 809fe5b4..c9b187d9 100644 --- a/modules/ui/styles/init/main.less +++ b/modules/ui/styles/init/main.less @@ -1,14 +1,13 @@ @import "../theme.less"; @import "../mixins.less"; -html { - font: 10px 'Lucida Grande', sans-serif; +html, pre { + font: 11px 'Lucida Grande', sans-serif; } body { background-color: @bg-color; color: @font-color; - font-size: 11px; } iframe { @@ -29,3 +28,6 @@ button { color: inherit; } +pre { + line-height: 1.5; +} diff --git a/package-lock.json b/package-lock.json index 3562ef0a..c84c0f9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5312,16 +5312,6 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, - "js-yaml": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", - "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^2.6.0" - } - }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -8426,6 +8416,18 @@ "mkdirp": "~0.5.1", "sax": "~1.2.1", "whet.extend": "~0.9.9" + }, + "dependencies": { + "js-yaml": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", + "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^2.6.0" + } + } } }, "table": { diff --git a/web/app/cad/craft/cadRegistryPlugin.js b/web/app/cad/craft/cadRegistryPlugin.js index a09468d3..226115d0 100644 --- a/web/app/cad/craft/cadRegistryPlugin.js +++ b/web/app/cad/craft/cadRegistryPlugin.js @@ -4,7 +4,7 @@ import {EDGE, FACE, SKETCH_OBJECT} from '../scene/entites'; export function activate({streams, services}) { streams.cadRegistry = { - shellIndex: streams.craft.models.map(models => models.reduce((i, v)=> i.set(v.id, v), new Map())).keep() + shellIndex: streams.craft.models.map(models => models.reduce((i, v)=> i.set(v.id, v), new Map())).remember() }; streams.cadRegistry.update = streams.cadRegistry.shellIndex; diff --git a/web/app/cad/craft/craftPlugin.js b/web/app/cad/craft/craftPlugin.js index e61c67b3..3ca66685 100644 --- a/web/app/cad/craft/craftPlugin.js +++ b/web/app/cad/craft/craftPlugin.js @@ -44,7 +44,7 @@ export function activate({streams, services}) { for (let i = beginIndex; i <= pointer; i++) { let request = history[i]; - let op = services.operation.registry[request.type]; + let op = services.operation.get(request.type); if (!op) { console.log(`unknown operation ${request.type}`); } diff --git a/web/app/cad/craft/craftUiPlugin.js b/web/app/cad/craft/craftUiPlugin.js new file mode 100644 index 00000000..6d95df38 --- /dev/null +++ b/web/app/cad/craft/craftUiPlugin.js @@ -0,0 +1,9 @@ +import {state} from 'lstream'; +import {EMPTY_OBJECT} from 'gems/objects'; + +export function activate({streams}) { + streams.ui.craft = { + modificationSelection: state(EMPTY_OBJECT) + } +} + diff --git a/web/app/cad/craft/operationPlugin.js b/web/app/cad/craft/operationPlugin.js index 3990c638..1f57125a 100644 --- a/web/app/cad/craft/operationPlugin.js +++ b/web/app/cad/craft/operationPlugin.js @@ -1,28 +1,33 @@ +import {state} from 'lstream'; export function activate(context) { let {services} = context; - let registry = {}; - + context.streams.operation = { + registry: state({}) + }; + + let registry$ = context.streams.operation.registry; + function addOperation(descriptor, actions) { let {id, label, info, icon, actionParams} = descriptor; - - let opAction = { - id: id, - appearance: { - label, + let appearance = { + label, info, icon32: icon + '32.png', icon96: icon + '96.png', - }, + }; + let opAction = { + id: id, + appearance, invoke: () => services.wizard.open({type: id}), ...actionParams }; actions.push(opAction); - registry[id] = Object.assign({}, descriptor, { + registry$.mutate(registry => registry[id] = Object.assign({appearance}, descriptor, { run: (request, services) => runOperation(request, descriptor, services) - }); + })); } function registerOperations(operations) { @@ -34,7 +39,7 @@ export function activate(context) { } function get(id) { - let op = registry[id]; + let op = registry$.value[id]; if (!op) { throw `operation ${id} is not registered`; } @@ -43,7 +48,6 @@ export function activate(context) { services.operation = { registerOperations, - registry, get }; } diff --git a/web/app/cad/craft/ui/HistoryTimeline.jsx b/web/app/cad/craft/ui/HistoryTimeline.jsx new file mode 100644 index 00000000..455be79c --- /dev/null +++ b/web/app/cad/craft/ui/HistoryTimeline.jsx @@ -0,0 +1,126 @@ +import React from 'react'; +import ls from './HistoryTimeline.less'; +import connect from 'ui/connect'; +import decoratorChain from '../../../../../modules/ui/decoratorChain'; +import {finishHistoryEditing, removeAndDropDependants} from '../craftHistoryUtils'; +import mapContext from '../../../../../modules/ui/mapContext'; +import ImgIcon from 'ui/components/ImgIcon'; +import {getDescriptor} from './OperationHistory'; +import cx from 'classnames'; +import Fa from '../../../../../modules/ui/components/Fa'; +import {menuAboveElementHint} from '../../dom/menu/menuUtils'; +import {combine} from 'lstream'; +import {EMPTY_OBJECT} from '../../../../../modules/gems/objects'; +import {VIEWER_SELECTOR} from '../../dom/components/View3d'; +import {aboveElement} from '../../../../../modules/ui/positionUtils'; + +function HistoryTimeline({history, pointer, setHistoryPointer, remove, getOperation}) { + return
+ + {history.map((m, i) => + setHistoryPointer(i-1)} /> + + )} + setHistoryPointer(history.length-1)}/> + + +
; +} + +const InProgressOperation = connect(streams => streams.wizard.map(wizard => ({wizard})))( + function InProgressOperation({wizard, getOperation}) { + if (!wizard) { + return null; + } + let {appearance} = getOperation(wizard.type); + return
+ +
; + + } +); + +function Timesplitter({active, eoh, onClick}) { + + return
+
+ +
+
; +} + +function Handle() { + const w = 12; + const h = 15; + const m = Math.round(w * 0.5); + const t = Math.round(h * 0.5); + return + + ; +} + +function Controls({pointer, eoh, setHistoryPointer}) { + const noB = pointer===-1; + const noF = pointer===eoh; + return +
setHistoryPointer(pointer-1)}> + +
+
setHistoryPointer(pointer+1)}> + +
+
setHistoryPointer(eoh)}> + +
+
; +} + +const HistoryItem = decoratorChain( + connect((streams, props) => streams.ui.craft.modificationSelection.map(s => ({ + selected: s.index === props.index, + }))), + mapContext(({streams}) => ({ + toggle: (index, modification, el) => streams.ui.craft.modificationSelection.update(s => + s.index === index ? EMPTY_OBJECT : {index, locationHint: aboveElement(el)}) +})) +) + ( +function HistoryItem({index, pointer, modification, getOperation, toggle, selected, disabled, inProgress}) { + let {appearance} = getOperation(modification.type); + return
toggle(index, modification, e.currentTarget)}> + + { index + 1 } +
; +}); + +const AddButton = mapContext(({services}) => ({ + showCraftMenu: e => services.action.run('menu.craft', menuAboveElementHint(e.currentTarget)) +}))( + function AddButton({showCraftMenu}) { + return
+ +
; + } +); + + +export default decoratorChain( + connect(streams => combine(streams.craft.modifications, streams.operation.registry) + .map(([modifications, operationRegistry]) => ({ + ...modifications, + operationRegistry, + getOperation: type => operationRegistry[type]||EMPTY_OBJECT + }))), + mapContext(({streams}) => ({ + remove: atIndex => streams.craft.modifications.update(modifications => removeAndDropDependants(modifications, atIndex)), + cancel: () => streams.craft.modifications.update(modifications => finishHistoryEditing(modifications)), + setHistoryPointer: pointer => streams.craft.modifications.update(({history}) => ({history, pointer})) + })) +)(HistoryTimeline); + + + \ No newline at end of file diff --git a/web/app/cad/craft/ui/HistoryTimeline.less b/web/app/cad/craft/ui/HistoryTimeline.less new file mode 100644 index 00000000..45fec798 --- /dev/null +++ b/web/app/cad/craft/ui/HistoryTimeline.less @@ -0,0 +1,143 @@ +@import "~ui/styles/theme.less"; + +.root { + background-color: rgba(0, 0, 0, 0.1); + display: flex; + align-items: stretch; + height: 34px; + //&:hover .timesplitter .handlePoly { + // visibility: visible; + //} +} + +.timesplitter { + width: 4px; + height: 100%; + cursor: pointer; + .handle { + margin-top: -15px; + margin-left: -4px; + font-size: 20px; + } +} + +.timesplitter.active:not(.eoh) { + background-color: #ff940b; + +} + +.handlePoly { + fill: #808080; + stroke: #000; + &:hover { + fill: #ff940b; + stroke: #ff3a1e; + } +} + +.timesplitter.active .handlePoly { + fill: #ff940b; + stroke: #ff3a1e; +} + +.timesplitter.active.eoh .handlePoly { + fill: #BFBFBF; + stroke: #000; +} + + + +//ITEMS + +.item { + margin: 2px 0; + padding: 2px; + border: #2e2e2e 1px solid; + border-radius: 4px; + + display: flex; + align-items: center; + justify-content: center; + width: 30px; + + cursor: pointer; + &:active { + transition: 300ms; + } +} + +.disabled { + background-color: #828282; + border-color: #a7a7a7; + color: #a7a7a7; +} + +.controlBtn { + .item; + margin-left: 2px; + margin-right: 2px; + background-color: #64808b; + &:hover { + background-color: #489; + } + &:active { + background-color: #5dc4da; + } + &.disabled {.disabled;} +} + +.historyItem { + .item; + background-color: #737373; + &:hover { + background-color: #4d4d4d; + } + &:active { + background-color: #9c9c9c; + } + &.disabled {.disabled;} + &.selected { + //background-color: #2B2B2B; + border-color: #00ffe4; + //border-width: 2px; + } + &.inProgress { + background-color: @inProgressColor; + } + + position: relative; + & .opIndex { + position: absolute; + right: 1px; + bottom: 1px; + text-shadow: 0px -1px 0 #000, 0px 1px 0 #000, 1px 0px 0 #000, -1px 0px 0 #000, + -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; + } + & .opIcon { + margin-left: -6px; + margin-top: -3px; + } + .opInfo { + position: absolute; + bottom: 20px; + } +} + +@inProgressColor: #648268; +.inProgressItem { + .item; + background-color: @inProgressColor; + border: #ffffff88 1px dashed; + margin-right: 4px; + cursor: auto; +} + +.add { + .controlBtn; + border: #ffffff88 1px dashed; + color: #ffffff88; + &:hover { + border-color: #fff; + } + &.disabled {.disabled;} +} diff --git a/web/app/cad/dom/components/ObjectExplorer.jsx b/web/app/cad/craft/ui/ObjectExplorer.jsx similarity index 94% rename from web/app/cad/dom/components/ObjectExplorer.jsx rename to web/app/cad/craft/ui/ObjectExplorer.jsx index f01e2e4f..a92b0dcc 100644 --- a/web/app/cad/dom/components/ObjectExplorer.jsx +++ b/web/app/cad/craft/ui/ObjectExplorer.jsx @@ -1,7 +1,6 @@ import React from 'react'; import connect from '../../../../../modules/ui/connect'; import {Section} from '../../../../../modules/ui/components/Section'; -import {MShell} from '../../model/mshell'; export default connect(streams => streams.craft.models.map(models => ({models}))) (function ObjectExplorer({models}) { diff --git a/web/app/cad/dom/components/OperationHistory.jsx b/web/app/cad/craft/ui/OperationHistory.jsx similarity index 83% rename from web/app/cad/dom/components/OperationHistory.jsx rename to web/app/cad/craft/ui/OperationHistory.jsx index f8acf330..04458c3f 100644 --- a/web/app/cad/dom/components/OperationHistory.jsx +++ b/web/app/cad/craft/ui/OperationHistory.jsx @@ -7,17 +7,18 @@ import ls from './OperationHistory.less'; import cx from 'classnames'; import ButtonGroup from 'ui/components/controls/ButtonGroup'; import Button from 'ui/components/controls/Button'; -import {finishHistoryEditing, removeAndDropDependants} from '../../craft/craftHistoryUtils'; +import {finishHistoryEditing, removeAndDropDependants} from '../craftHistoryUtils'; import mapContext from 'ui/mapContext'; import decoratorChain from 'ui/decoratorChain'; +import {EMPTY_OBJECT} from '../../../../../modules/gems/objects'; -function OperationHistory({history, pointer, setHistoryPointer, remove, operationRegistry}) { +function OperationHistory({history, pointer, setHistoryPointer, remove, getOperation}) { let lastMod = history.length - 1; return {history.map(({type, params}, index) => { - let {appearance, paramsInfo} = getDescriptor(type, operationRegistry); + let {appearance, paramsInfo} = getOperation(type)||EMPTY_OBJECT; return
setHistoryPointer(index - 1)} className={cx(ls.item, pointer + 1 === index && ls.selected)}> {appearance && } @@ -35,21 +36,12 @@ function OperationHistory({history, pointer, setHistoryPointer, remove, operatio ; } -const EMPTY_DESCRIPTOR = {}; -function getDescriptor(type, registry) { - let descriptor = registry[type]; - if (!descriptor) { - descriptor = EMPTY_DESCRIPTOR; - } - return descriptor; -} - export default decoratorChain( connect(streams => streams.craft.modifications), mapContext(({streams, services}) => ({ remove: atIndex => streams.craft.modifications.update(modifications => removeAndDropDependants(modifications, atIndex)), cancel: () => streams.craft.modifications.update(modifications => finishHistoryEditing(modifications)), - operationRegistry: services.operation.registry, + getOperation: services.operation.get, setHistoryPointer: pointer => streams.craft.modifications.update(({history}) => ({history, pointer})) })) )(OperationHistory); diff --git a/web/app/cad/dom/components/OperationHistory.less b/web/app/cad/craft/ui/OperationHistory.less similarity index 100% rename from web/app/cad/dom/components/OperationHistory.less rename to web/app/cad/craft/ui/OperationHistory.less diff --git a/web/app/cad/craft/ui/SelectedModificationInfo.jsx b/web/app/cad/craft/ui/SelectedModificationInfo.jsx new file mode 100644 index 00000000..ce0e2452 --- /dev/null +++ b/web/app/cad/craft/ui/SelectedModificationInfo.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import connect from 'ui/connect'; +import Widget from 'ui/components/Widget'; +import decoratorChain from '../../../../../modules/ui/decoratorChain'; +import {combine, merger} from '../../../../../modules/lstream'; +import ls from './SelectedModificationInfo.less'; +import ImgIcon from 'ui/components/ImgIcon'; +import YAML from 'yamljs'; +import mapContext from 'ui/mapContext'; +import {EMPTY_OBJECT} from '../../../../../modules/gems/objects'; +import ButtonGroup from '../../../../../modules/ui/components/controls/ButtonGroup'; +import Button from '../../../../../modules/ui/components/controls/Button'; +import {removeAndDropDependants} from '../craftHistoryUtils'; +import RenderObject from 'ui/components/RenderObject'; + +function SelectedModificationInfo({ history, index, + operationRegistry, + locationHint: lh, + drop, edit, + close}) { + let m = history[index]; + let visible = !!m; + if (!visible) { + return null; + } + let op = operationRegistry[m.type]; + if (!op) { + console.warn('unknown operation ' + m.type); + return; + } + let {appearance} = op; + let indexNumber = index + 1; + return +
+ + + + +
+
+ + + + +
+
; +} + +export default decoratorChain( + connect(streams => combine(streams.ui.craft.modificationSelection, + streams.operation.registry.map(r => ({operationRegistry: r})), + streams.craft.modifications + ).map(merger)), + mapContext((ctx, props) => ({ + close: () => ctx.streams.ui.craft.modificationSelection.next(EMPTY_OBJECT), + drop: () => ctx.streams.craft.modifications.update(modifications => removeAndDropDependants(modifications, props.index)), + edit: () => ctx.streams.craft.modifications.update(({history}) => ({history, pointer: props.index - 1})) + })) +)(SelectedModificationInfo); + diff --git a/web/app/cad/craft/ui/SelectedModificationInfo.less b/web/app/cad/craft/ui/SelectedModificationInfo.less new file mode 100644 index 00000000..84f5289f --- /dev/null +++ b/web/app/cad/craft/ui/SelectedModificationInfo.less @@ -0,0 +1,11 @@ +.requestInfo { + display: flex; + & > * { + margin-right: 5px; + } + margin-bottom: 5px; +} + +.pic { + margin-right: 5px; +} \ No newline at end of file diff --git a/web/app/cad/craft/wizard/components/HistoryWizard.jsx b/web/app/cad/craft/wizard/components/HistoryWizard.jsx index 9b4503e6..05ef7d0e 100644 --- a/web/app/cad/craft/wizard/components/HistoryWizard.jsx +++ b/web/app/cad/craft/wizard/components/HistoryWizard.jsx @@ -5,7 +5,6 @@ import {finishHistoryEditing, stepOverridingParams} from '../../craftHistoryUtil import {NOOP} from 'gems/func'; import decoratorChain from 'ui/decoratorChain'; import mapContext from 'ui/mapContext'; -import {createPreviewer} from '../../../preview/scenePreviewer'; function HistoryWizard({history, pointer, step, cancel, offset, getOperation, previewerCreator, createValidator}) { if (pointer === history.length - 1) { diff --git a/web/app/cad/craft/wizard/components/WizardManager.jsx b/web/app/cad/craft/wizard/components/WizardManager.jsx index 316595c1..3a63476c 100644 --- a/web/app/cad/craft/wizard/components/WizardManager.jsx +++ b/web/app/cad/craft/wizard/components/WizardManager.jsx @@ -11,31 +11,32 @@ import initializeBySchema from '../../intializeBySchema'; import validateParams from '../../validateParams'; class WizardManager extends React.Component { - - render() { - let {wizards, close} = this.props; - return - {wizards.map((wizardRef, wizardIndex) => { - let {type} = wizardRef; - let operation = this.props.getOperation(type); - if (!operation) { - throw 'unknown operation ' + type; - } - let params = this.props.initializeOperation(operation); - let validator = this.props.createValidator(operation); - const closeInstance = () => close(wizardRef); - return - })} - close(wizard); + return + + + + ; @@ -43,15 +44,15 @@ class WizardManager extends React.Component { } function offset(wizardIndex) { - return 70 + (wizardIndex * (250 + 20)); + return 70 + (wizardIndex * (250 + 20)); } export default decoratorChain( - connect(streams => streams.wizards.map(wizards => ({wizards}))), + connect(streams => streams.wizard.map(wizard => ({wizard}))), mapContext(ctx => ({ - close: wizard => ctx.services.wizard.close(wizard), + close: () => ctx.services.wizard.close(), reset: () => { - ctx.streams.wizards.value = []; + ctx.streams.wizard.value = null; ctx.streams.craft.modifications.update(modifications => finishHistoryEditing(modifications)); }, getOperation: type => ctx.services.operation.get(type), diff --git a/web/app/cad/craft/wizard/wizardPlugin.js b/web/app/cad/craft/wizard/wizardPlugin.js index 1ef183eb..691af249 100644 --- a/web/app/cad/craft/wizard/wizardPlugin.js +++ b/web/app/cad/craft/wizard/wizardPlugin.js @@ -2,7 +2,7 @@ import {state} from '../../../../../modules/lstream'; export function activate({streams, services}) { - streams.wizards = state([]); + streams.wizard = state(null); services.wizard = { @@ -12,11 +12,11 @@ export function activate({streams, services}) { type }; - streams.wizards.update(opened => [...opened, wizard]); + streams.wizard.value = wizard; }, close: wizard => { - streams.wizards.update(opened => opened.filter(w => w !== wizard)); + streams.wizard = null; } } } diff --git a/web/app/cad/dom/components/BottomStack.jsx b/web/app/cad/dom/components/BottomStack.jsx new file mode 100644 index 00000000..9185d6b4 --- /dev/null +++ b/web/app/cad/dom/components/BottomStack.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import ls from './BottomStack.less'; + +export default function BottomStack({children}) { + return
+ {children} +
; +} \ No newline at end of file diff --git a/web/app/cad/dom/components/BottomStack.less b/web/app/cad/dom/components/BottomStack.less new file mode 100644 index 00000000..051a63ea --- /dev/null +++ b/web/app/cad/dom/components/BottomStack.less @@ -0,0 +1,9 @@ +.root { + position: absolute; + bottom: 0; + left: 0; + right: 0; + display: flex; + z-index: 150; + flex-direction: column; +} \ No newline at end of file diff --git a/web/app/cad/dom/components/ControlBar.less b/web/app/cad/dom/components/ControlBar.less index d2f946d0..51ada59c 100644 --- a/web/app/cad/dom/components/ControlBar.less +++ b/web/app/cad/dom/components/ControlBar.less @@ -1,12 +1,7 @@ @import "~ui/styles/theme.less"; .root { - position: absolute; - bottom: 0; - left: 0; - right: 0; display: flex; - z-index: 150; justify-content: space-between; background-color: @work-area-control-bar-bg-color; color: @work-area-control-bar-font-color; diff --git a/web/app/cad/dom/components/PartPanel.jsx b/web/app/cad/dom/components/FloatView.jsx similarity index 70% rename from web/app/cad/dom/components/PartPanel.jsx rename to web/app/cad/dom/components/FloatView.jsx index 296a9a86..1cc02262 100644 --- a/web/app/cad/dom/components/PartPanel.jsx +++ b/web/app/cad/dom/components/FloatView.jsx @@ -1,10 +1,10 @@ import React, {Fragment} from 'react'; -import ObjectExplorer from './ObjectExplorer'; -import OperationHistory from './OperationHistory'; +import ObjectExplorer from '../../craft/ui/ObjectExplorer'; +import OperationHistory from '../../craft/ui/OperationHistory'; import Folder from 'ui/components/Folder'; import Fa from '../../../../../modules/ui/components/Fa'; -export default function PartPanel() { +export default function FloatView() { return Model}> diff --git a/web/app/cad/dom/components/PlugableControlBar.jsx b/web/app/cad/dom/components/PlugableControlBar.jsx index c61b0e94..a8ea51d7 100644 --- a/web/app/cad/dom/components/PlugableControlBar.jsx +++ b/web/app/cad/dom/components/PlugableControlBar.jsx @@ -7,6 +7,7 @@ import {isMenuAction} from '../menu/menuPlugin'; import {combine, merger} from 'lstream'; import mapContext from 'ui/mapContext'; import decoratorChain from '../../../../../modules/ui/decoratorChain'; +import {menuAboveElementHint} from '../menu/menuUtils'; export default function PlugableControlBar() { return } right={}/>; @@ -28,7 +29,7 @@ class ActionButton extends React.Component { } if (isMenuAction(actionId)) { let onClick = props.onClick; - props.onClick = e => onClick(getMenuData(this.el)); + props.onClick = e => onClick(menuAboveElementHint(this.el)); } return this.el = el} {...props} > @@ -53,12 +54,3 @@ const ConnectedActionButton = decoratorChain( ) (ActionButton); -function getMenuData(el) { - //TODO: make more generic - return { - orientationUp: true, - flatBottom: true, - x: el.offsetParent.offsetParent.offsetLeft + el.offsetLeft, - y: el.offsetParent.offsetHeight - el.offsetTop - }; -} diff --git a/web/app/cad/dom/components/View3d.jsx b/web/app/cad/dom/components/View3d.jsx index 9b6d6554..1ca8588f 100644 --- a/web/app/cad/dom/components/View3d.jsx +++ b/web/app/cad/dom/components/View3d.jsx @@ -1,15 +1,14 @@ import React from 'react'; import PlugableControlBar from './PlugableControlBar'; - import ls from './View3d.less'; import Abs from 'ui/components/Abs'; -import { - AuxiliaryToolbar, HeadsUpToolbar, PlugableToolbarLeft, PlugableToolbarLeftSecondary, - PlugableToolbarRight -} from './PlugableToolbar'; +import {AuxiliaryToolbar, HeadsUpToolbar} from './PlugableToolbar'; import UISystem from './UISystem'; import WizardManager from '../../craft/wizard/components/WizardManager'; -import PartPanel from './PartPanel'; +import FloatView from './FloatView'; +import HistoryTimeline from '../../craft/ui/HistoryTimeline'; +import BottomStack from './BottomStack'; +import SelectedModificationInfo from '../../craft/ui/SelectedModificationInfo'; export default class View3d extends React.Component { @@ -18,25 +17,29 @@ export default class View3d extends React.Component { //we don't want the dom to be updated under any circumstances or we loose the WEB-GL container return false; } - + render() { - return + return
- +
- + - - + + + + +
-
+ +
; } - + componentWillUnmount() { throw 'big no-no'; } diff --git a/web/app/cad/dom/menu/menuUtils.js b/web/app/cad/dom/menu/menuUtils.js new file mode 100644 index 00000000..e28d4bcf --- /dev/null +++ b/web/app/cad/dom/menu/menuUtils.js @@ -0,0 +1,6 @@ +export const menuAboveElementHint = el => ({ + orientationUp: true, + flatBottom: true, + x: el.offsetParent.offsetParent.offsetLeft + el.offsetLeft, + y: el.offsetParent.offsetHeight - el.offsetTop +}); diff --git a/web/app/cad/init/startApplication.js b/web/app/cad/init/startApplication.js index e6f2f6dd..93123054 100644 --- a/web/app/cad/init/startApplication.js +++ b/web/app/cad/init/startApplication.js @@ -13,6 +13,7 @@ import * as OperationPlugin from '../craft/operationPlugin'; import * as CraftEnginesPlugin from '../craft/enginesPlugin'; import * as CadRegistryPlugin from '../craft/cadRegistryPlugin'; import * as CraftPlugin from '../craft/craftPlugin'; +import * as CraftUiPlugin from '../craft/craftUiPlugin'; import * as StoragePlugin from '../storagePlugin'; import * as ProjectPlugin from '../projectPlugin'; import * as SketcherPlugin from '../sketch/sketcherPlugin'; @@ -42,6 +43,7 @@ export default function startApplication(callback) { CraftEnginesPlugin, OperationPlugin, CraftPlugin, + CraftUiPlugin, CadRegistryPlugin, tpiPlugin ]; diff --git a/web/app/cad/part/uiConfigPlugin.js b/web/app/cad/part/uiConfigPlugin.js index c528ea1b..0edd2504 100644 --- a/web/app/cad/part/uiConfigPlugin.js +++ b/web/app/cad/part/uiConfigPlugin.js @@ -11,7 +11,7 @@ export function activate({services, streams}) { ['ShowSketches', {label: 'sketches'}], ['DeselectAll', {label: null}], ['ToggleCameraMode', {label: null}] ]; - streams.ui.toolbars.headsUp.value = ['PLANE', 'EditFace', 'EXTRUDE', 'CUT', 'REVOLVE', 'INTERSECTION', 'DIFFERENCE', 'UNION']; + streams.ui.toolbars.headsUp.value = ['PLANE', 'EditFace', 'EXTRUDE', 'CUT', 'REVOLVE', 'FILLET', 'INTERSECTION', 'DIFFERENCE', 'UNION']; streams.ui.toolbars.auxiliary.value = ['Save', 'StlExport']; services.action.registerActions(CoreActions); diff --git a/web/img/cad/timeline/handle.svg b/web/img/cad/timeline/handle.svg new file mode 100644 index 00000000..e69de29b diff --git a/web/index.html b/web/index.html index 8b2e477d..ffd07f1d 100644 --- a/web/index.html +++ b/web/index.html @@ -1,7 +1,6 @@ - TCAD - + Web CAD / Part Designer