diff --git a/modules/gems/iterables.js b/modules/gems/iterables.js index 4443f544..b6b55ef2 100644 --- a/modules/gems/iterables.js +++ b/modules/gems/iterables.js @@ -41,4 +41,10 @@ export function flatten(arr, result = [], depth, _currLevel) { return result; } +export function indexArray(array, getKey, getValue = v => v) { + let obj = {}; + array.forEach(item => obj[getKey(item)] = getValue(item)) + return obj; +} + export const EMPTY_ARRAY = Object.freeze([]); diff --git a/modules/ui/bind.js b/modules/ui/bind.js new file mode 100644 index 00000000..c02bc846 --- /dev/null +++ b/modules/ui/bind.js @@ -0,0 +1,41 @@ +import React from 'react'; +import context from 'context'; + +export default function bind(streamProvider) { + return function (Component) { + return class Connected extends React.Component { + + state = {hasError: false, value: null}; + + onChange = value => streamProvider(context.streams, this.props).next(value); + + componentWillMount() { + this.stream = streamProvider(context.streams, this.props); + this.detacher = this.stream.attach(value => { + this.setState({ + hasError: false, + value + }); + }); + } + + componentWillUnmount() { + this.detacher(); + } + + render() { + if (this.state.hasError) { + return null; + } + return ; + + } + + componentDidCatch() { + this.setState({hasError: true}); + } + }; + } +} diff --git a/modules/ui/components/Folder.jsx b/modules/ui/components/Folder.jsx index 7fbcb922..8dba4e03 100644 --- a/modules/ui/components/Folder.jsx +++ b/modules/ui/components/Folder.jsx @@ -2,6 +2,7 @@ import React from 'react'; import ls from './Folder.less' import Fa from "./Fa"; +import cx from 'classnames'; export default class Folder extends React.Component{ @@ -23,11 +24,11 @@ export default class Folder extends React.Component{ }; render() { - let {title, closable, children} = this.props; - return
+ let {title, closable, className, children} = this.props; + return
- {title} + {' '}{title}
{!this.isClosed() && children}
diff --git a/modules/ui/components/Row.jsx b/modules/ui/components/Row.jsx index e69de29b..7ee45324 100644 --- a/modules/ui/components/Row.jsx +++ b/modules/ui/components/Row.jsx @@ -0,0 +1,7 @@ +import React from 'react'; +import ls from './Row.less'; +import cx from 'classnames'; + +export default function Row({className, props, children}) { + return
{children}
; +} \ No newline at end of file diff --git a/modules/ui/components/Row.less b/modules/ui/components/Row.less new file mode 100644 index 00000000..9a2935d5 --- /dev/null +++ b/modules/ui/components/Row.less @@ -0,0 +1,9 @@ +.root { + line-height: 1px; +} + +.root > * { + line-height: 1px; + margin: 3px; + padding: 5px 3px; +} \ No newline at end of file diff --git a/modules/ui/components/ToolButton.jsx b/modules/ui/components/ToolButton.jsx new file mode 100644 index 00000000..96fb9461 --- /dev/null +++ b/modules/ui/components/ToolButton.jsx @@ -0,0 +1,7 @@ +import React from 'react'; +import ls from './ToolButton.less'; +import cx from 'classnames'; + +export default function ToolButton({pressed, type, className, ...props}) { + return {this.state.hasError &&
- performing operation with current parameters leads to an invalid object - (self-intersecting / zero-thickness / complete degeneration or unsupported cases) + {this.state.algorithmError && + performing operation with current parameters leads to an invalid object + (self-intersecting / zero-thickness / complete degeneration or unsupported cases) + } {this.state.code &&
{this.state.code}
} {this.state.userMessage &&
{this.state.userMessage}
}
} - {this.state.validationErrors.length !== 0 &&
- {this.state.validationErrors.map((err, i) =>
{err.path.join(' ')} {err.message}
)} -
} - ; } @@ -110,17 +106,6 @@ export default class Wizard extends React.Component { onOK = () => { try { - let {type, params, resolveOperation, validator} = this.props; - if (!type) { - return null; - } - - let operation = resolveOperation(type); - let validationErrors = validator(params, operation.schema); - if (validationErrors.length !== 0) { - this.setState({validationErrors}); - return; - } this.props.onOK(); } catch (error) { this.handleError(error); @@ -135,6 +120,9 @@ export default class Wizard extends React.Component { if (error.TYPE === CadError) { let {code, userMessage, kind} = error; printError = !code; + if (CadError.ALGORITMTHM_ERROR_KINDS.includes(kind)) { + stateUpdate.algorithmError = true + } if (code && kind === CadError.KIND.INTERNAL_ERROR) { console.warn('Operation Error Code: ' + code); } diff --git a/web/app/cad/craft/wizard/components/Wizard.less b/web/app/cad/craft/wizard/components/Wizard.less index a16baad4..2d532a39 100644 --- a/web/app/cad/craft/wizard/components/Wizard.less +++ b/web/app/cad/craft/wizard/components/Wizard.less @@ -1,15 +1,9 @@ .errorMessage { color: lightgoldenrodyellow; -} - -.userErrorMessage, .errorCode { - font-size: 9px; -} - -.userErrorMessage { - color: white; + white-space: pre-line; } .errorCode { + font-size: 9px; font-style: italic; -} \ No newline at end of file +} diff --git a/web/app/cad/craft/wizard/components/WizardManager.jsx b/web/app/cad/craft/wizard/components/WizardManager.jsx index f6aa720a..43bba870 100644 --- a/web/app/cad/craft/wizard/components/WizardManager.jsx +++ b/web/app/cad/craft/wizard/components/WizardManager.jsx @@ -3,17 +3,13 @@ import Wizard from './Wizard'; import connect from 'ui/connect'; import decoratorChain from 'ui/decoratorChain'; import mapContext from 'ui/mapContext'; -import {finishHistoryEditing, stepOverriding} from '../../craftHistoryUtils'; -import validateParams from '../../validateParams'; -import {NOOP} from 'gems/func'; -import {clone} from 'gems/objects'; +import {finishHistoryEditing} from '../../craftHistoryUtils'; -function WizardManager({type, changingHistory, resolve, cancel, stepHistory, insertOperation, cancelHistoryEdit, applyWorkingRequest, validator}) { +function WizardManager({type, changingHistory, resolve, cancel, stepHistory, insertOperation, cancelHistoryEdit, applyWorkingRequest}) { if (!type) { return null; } return } @@ -22,7 +18,6 @@ export default decoratorChain( connect(streams => streams.wizard.effectiveOperation), mapContext((ctx, props) => ({ cancel: ctx.services.wizard.cancel, - validator: (params, schema) => validateParams(ctx.services, params, schema), resolve: type => ctx.services.operation.get(type), cancelHistoryEdit: () => ctx.streams.craft.modifications.update(modifications => finishHistoryEditing(modifications)), applyWorkingRequest: ctx.services.wizard.applyWorkingRequest diff --git a/web/app/cad/dom/components/FloatView.jsx b/web/app/cad/dom/components/FloatView.jsx index c07baf1c..d738e533 100644 --- a/web/app/cad/dom/components/FloatView.jsx +++ b/web/app/cad/dom/components/FloatView.jsx @@ -1,11 +1,15 @@ import React from 'react'; -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'; import ls from './FloatView.less'; -import cx from 'classnames'; +import connect from 'ui/connect'; +import mapContext from 'ui/mapContext'; +import Fa from 'ui/components/Fa'; +import ToolButton from 'ui/components/ToolButton'; +@connect(state => state.ui.floatViews.map(views => ({views}))) +@mapContext(ctx => ({ + getDescriptor: ctx.services.ui.getFloatView +})) export default class FloatView extends React.Component { state = { @@ -13,33 +17,32 @@ export default class FloatView extends React.Component { }; render() { + let {views, getDescriptor} = this.props; + + function view(id) { + let {title, icon, Component} = getDescriptor(id); + return {title}}> + + ; + } + + function icon(id) { + let {Icon} = getDescriptor(id); + return + } + return
- {['project', 'history'].map(tabId => this.setState({selected: this.state.selected === tabId ? null : tabId})}>{getIcon(tabId)})} + {views.map(tabId => this.setState({selected: this.state.selected === tabId ? null : tabId})}> + {} + )}
{this.state.selected &&
- {this.state.selected === 'project' && Model}> - - } - {this.state.selected === 'history' && Modifications}> - - } - + {view(this.state.selected)}
}
; } -} - -function Tab({children, selected, onClick}) { - return
{children}
; -} - -function getIcon(id) { - if (id === 'history') { - return ; - } else if (id === 'project') { - return ; - } } \ No newline at end of file diff --git a/web/app/cad/dom/components/FloatView.less b/web/app/cad/dom/components/FloatView.less index 4bd320d4..5cc75788 100644 --- a/web/app/cad/dom/components/FloatView.less +++ b/web/app/cad/dom/components/FloatView.less @@ -11,6 +11,11 @@ font-size: 13px; } +.tabs button { + display: block; + font-size: 13px; +} + .main { display: flex; flex-direction: column; @@ -19,24 +24,8 @@ overflow-y: scroll; } -.tab { - - border: 1px solid @bg-color; - margin: 3px; - border-radius: 3px; - - padding: 5px 2px; - cursor: pointer; - background-color: @bg-color; - - &:hover { - background-color: #9c9c9c !important; - } - &:active { - transition: 200ms; - background-color: #BFBFBF !important; - } - &.selected { - background-color: #7d7d7d; - } -} +.folder { + height: 100%; + display: flex; + flex-direction: column; +} \ No newline at end of file diff --git a/web/app/cad/dom/uiPlugin.js b/web/app/cad/dom/uiPlugin.js index 1d06ce50..ab4add98 100644 --- a/web/app/cad/dom/uiPlugin.js +++ b/web/app/cad/dom/uiPlugin.js @@ -15,18 +15,27 @@ export function defineStreams({streams}) { sketcherControl: state([]), sketcherToolbarsVisible: state(false) }, + floatViews: state([]), sockets: {} }; } -export function activate({services}) { +export function activate({streams, services}) { let components = new Map(); const registerComponent = (id, Component) => components.set(id, Component); const getComponent = id => components.get(id); + let floatViewDescriptors = new Map(); + + function registerFloatView(id, Component, title, icon) { + floatViewDescriptors.set(id, {Component, title, icon}); + streams.ui.floatViews.mutate(views => views.push(id)); + } + const getFloatView = id => floatViewDescriptors.get(id); + services.ui = { - registerComponent, getComponent + registerComponent, getComponent, registerFloatView, getFloatView } } diff --git a/web/app/cad/expressions/Expressions.jsx b/web/app/cad/expressions/Expressions.jsx new file mode 100644 index 00000000..fc85bd62 --- /dev/null +++ b/web/app/cad/expressions/Expressions.jsx @@ -0,0 +1,92 @@ +import React, {Fragment} from 'react'; +import ls from './Expressions.less'; +import cmn from 'ui/styles/common.less'; +import ToolButton from 'ui/components/ToolButton'; +import Fa from 'ui/components/Fa'; +import Row from 'ui/components/Row'; +import connect from 'ui/connect'; +import mapContext from 'ui/mapContext'; +import bind from 'ui/bind'; +import cx from 'classnames'; +import {actionDecorator} from '../actions/actionDecorators'; +import {combine} from 'lstream'; +import Folder from 'ui/components/Folder'; +import Stack from '../../../../modules/ui/components/Stack'; + +@connect(streams => combine(streams.expressions.synced, streams.expressions.errors) + .map(([synced, errors])=> ({synced, errors}))) +@mapContext(ctx => ({ + reevaluateExpressions: ctx.services.expressions.reevaluateExpressions +})) +export default class Expressions extends React.Component { + + state = { + activeTab: 'Script' + }; + + render() { + + let {errors, synced, table, reevaluateExpressions} = this.props; + + const tabBtn = (name, icon) => { + return this.setState({activeTab: name})} pressed={this.state.activeTab === name}>{icon} {name}; + }; + + return
+ + {tabBtn('Script', )} + {tabBtn('Table', )} + {errors.length > 0 && } + {!synced && } + + +
+ + {this.state.activeTab === 'Script' &&