mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-22 16:33:38 +01:00
basic implementation of expressions
This commit is contained in:
parent
86786662f6
commit
0efdb74888
31 changed files with 539 additions and 133 deletions
|
|
@ -41,4 +41,10 @@ export function flatten(arr, result = [], depth, _currLevel) {
|
||||||
return result;
|
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([]);
|
export const EMPTY_ARRAY = Object.freeze([]);
|
||||||
|
|
|
||||||
41
modules/ui/bind.js
Normal file
41
modules/ui/bind.js
Normal file
|
|
@ -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 <Component value={this.state.value}
|
||||||
|
onChange={this.onChange}
|
||||||
|
{...this.props} />;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch() {
|
||||||
|
this.setState({hasError: true});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
|
|
||||||
import ls from './Folder.less'
|
import ls from './Folder.less'
|
||||||
import Fa from "./Fa";
|
import Fa from "./Fa";
|
||||||
|
import cx from 'classnames';
|
||||||
|
|
||||||
export default class Folder extends React.Component{
|
export default class Folder extends React.Component{
|
||||||
|
|
||||||
|
|
@ -23,11 +24,11 @@ export default class Folder extends React.Component{
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let {title, closable, children} = this.props;
|
let {title, closable, className, children} = this.props;
|
||||||
return <div className={ls.root}>
|
return <div className={cx(ls.root, className)}>
|
||||||
<div className={ls.title} onClick={closable ? this.tweakClose : null}>
|
<div className={ls.title} onClick={closable ? this.tweakClose : null}>
|
||||||
<span className={ls.handle}><Fa fw icon={this.isClosed() ? 'chevron-right' : 'chevron-down'}/></span>
|
<span className={ls.handle}><Fa fw icon={this.isClosed() ? 'chevron-right' : 'chevron-down'}/></span>
|
||||||
{title}
|
{' '}{title}
|
||||||
</div>
|
</div>
|
||||||
{!this.isClosed() && children}
|
{!this.isClosed() && children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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 <div className={cx(ls.root, className)} {...props} >{children}</div>;
|
||||||
|
}
|
||||||
9
modules/ui/components/Row.less
Normal file
9
modules/ui/components/Row.less
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
.root {
|
||||||
|
line-height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root > * {
|
||||||
|
line-height: 1px;
|
||||||
|
margin: 3px;
|
||||||
|
padding: 5px 3px;
|
||||||
|
}
|
||||||
7
modules/ui/components/ToolButton.jsx
Normal file
7
modules/ui/components/ToolButton.jsx
Normal file
|
|
@ -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 <button tabIndex="-1" className={cx(ls.root, ls[type], pressed && ls.pressed, className)} {...props}/>;
|
||||||
|
}
|
||||||
38
modules/ui/components/ToolButton.less
Normal file
38
modules/ui/components/ToolButton.less
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
@import "../styles/theme.less";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
|
||||||
|
border: 1px solid @bg-color;
|
||||||
|
margin: 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
padding: 5px 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: @bg-color;
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #9c9c9c !important;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transition: 200ms;
|
||||||
|
background-color: #BFBFBF !important;
|
||||||
|
}
|
||||||
|
&.pressed {
|
||||||
|
background-color: #7d7d7d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.accent {
|
||||||
|
background-color: @color-accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background-color: @color-highlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
background-color: @color-danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -17,6 +17,10 @@
|
||||||
.button-behavior(@color-accent)
|
.button-behavior(@color-accent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.highlihgt {
|
||||||
|
.button-behavior(@color-highlight)
|
||||||
|
}
|
||||||
|
|
||||||
.danger {
|
.danger {
|
||||||
.button-behavior(@color-danger)
|
.button-behavior(@color-danger)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,30 +8,22 @@ export default class NumberControl extends React.Component {
|
||||||
let {onChange, value} = this.props;
|
let {onChange, value} = this.props;
|
||||||
return <InputControl type='number'
|
return <InputControl type='number'
|
||||||
onWheel={this.onWheel}
|
onWheel={this.onWheel}
|
||||||
value={ Math.round(value * 1000) / 1000 }
|
value={ value }
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
inputRef={input => this.input = input} />
|
inputRef={input => this.input = input} />
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = e => {
|
onChange = e => {
|
||||||
let val;
|
this.props.onChange(e.target.value);
|
||||||
try {
|
|
||||||
val = parseFloat(e.target.value)
|
|
||||||
} catch (ignore) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isNaN(val)) {
|
|
||||||
this.props.onChange(val);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onWheel = (e) => {
|
onWheel = (e) => {
|
||||||
let {baseStep, round, min, max, onChange, accelerator} = this.props;
|
let {baseStep, round, min, max, onChange, accelerator} = this.props;
|
||||||
let delta = e.deltaY;
|
let delta = e.deltaY;
|
||||||
let val = e.target.value;
|
|
||||||
if (!val) val = 0;
|
|
||||||
let step = baseStep * (e.shiftKey ? accelerator : 1);
|
let step = baseStep * (e.shiftKey ? accelerator : 1);
|
||||||
val = parseFloat(val) + (delta < 0 ? -step : step);
|
let val = parseFloat(e.target.value);
|
||||||
|
if (isNaN(val)) val = 0;
|
||||||
|
val = val + (delta < 0 ? -step : step);
|
||||||
if (min !== undefined && val < min) {
|
if (min !== undefined && val < min) {
|
||||||
val = min;
|
val = min;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
modules/ui/styles/common.less
Normal file
26
modules/ui/styles/common.less
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
@import "./theme.less";
|
||||||
|
@import "./table.less";
|
||||||
|
|
||||||
|
*.autoMarginLeft {
|
||||||
|
margin-left: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
*.floatRight {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullWidth {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dangerColor {
|
||||||
|
color: @color-danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dangerBg {
|
||||||
|
background-color: @color-danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inlineBlock {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
@import "../theme.less";
|
@import "../theme.less";
|
||||||
@import "../mixins.less";
|
@import "../mixins.less";
|
||||||
|
|
||||||
|
@fontSize: 11px;
|
||||||
|
|
||||||
html, pre {
|
html, pre {
|
||||||
font: 11px 'Lucida Grande', sans-serif;
|
font: @fontSize 'Lucida Grande', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
@ -31,3 +33,7 @@ button {
|
||||||
pre {
|
pre {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
font-size: @fontSize;
|
||||||
|
}
|
||||||
37
modules/ui/styles/table.less
Normal file
37
modules/ui/styles/table.less
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
@import "./theme.less";
|
||||||
|
|
||||||
|
.stripedTable {
|
||||||
|
& tr:nth-child(even), & th {
|
||||||
|
background-color: @bg-color
|
||||||
|
}
|
||||||
|
& tr:nth-child(odd) {
|
||||||
|
background-color: @bg-color-alt
|
||||||
|
}
|
||||||
|
& tr:hover {
|
||||||
|
background-color: @color-highlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delineatedTable {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
& td, & th {
|
||||||
|
padding: 5px 2px;
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
& tr:first-child th {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
//& tr:last-child td {
|
||||||
|
// border-bottom: 0;
|
||||||
|
//}
|
||||||
|
& tr td:first-child,
|
||||||
|
& tr th:first-child {
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
& tr td:last-child,
|
||||||
|
& tr th:last-child {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
@color-danger: #b00;
|
@color-danger: #b00;
|
||||||
@color-accent: #2B7D2B;
|
@color-accent: #2B7D2B;
|
||||||
@color-neutral: #66727d;
|
@color-neutral: #66727d;
|
||||||
|
@color-highlight: #003f5d;
|
||||||
|
|
||||||
//@work-area-toolbar-bg-color: ;
|
//@work-area-toolbar-bg-color: ;
|
||||||
//@work-area-toolbar-font-color: ;
|
//@work-area-toolbar-font-color: ;
|
||||||
|
|
|
||||||
12
web/app/cad/actions/actionDecorators.jsx
Normal file
12
web/app/cad/actions/actionDecorators.jsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React, {Fragment} from 'react';
|
||||||
|
|
||||||
|
import {mapActionBehavior} from './actionButtonBehavior';
|
||||||
|
|
||||||
|
export function actionDecorator(actionId) {
|
||||||
|
let actionBehavior = mapActionBehavior(actionId);
|
||||||
|
return function (Component) {
|
||||||
|
return function ActionDecorator(props) {
|
||||||
|
return <Component {...actionBehavior} {...props}/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,8 @@ import {addModification, stepOverriding} from './craftHistoryUtils';
|
||||||
import {state, stream} from 'lstream';
|
import {state, stream} from 'lstream';
|
||||||
import {MShell} from '../model/mshell';
|
import {MShell} from '../model/mshell';
|
||||||
import {MDatum} from '../model/mdatum';
|
import {MDatum} from '../model/mdatum';
|
||||||
|
import materializeParams from './materializeParams';
|
||||||
|
import CadError from '../../utils/errors';
|
||||||
|
|
||||||
export function activate({streams, services}) {
|
export function activate({streams, services}) {
|
||||||
|
|
||||||
|
|
@ -54,7 +56,17 @@ export function activate({streams, services}) {
|
||||||
throw(`unknown operation ${request.type}`);
|
throw(`unknown operation ${request.type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return op.run(request.params, services);
|
let params = {};
|
||||||
|
let errors = [];
|
||||||
|
materializeParams(services, request.params, op.schema, params, errors);
|
||||||
|
if (errors.length) {
|
||||||
|
throw new CadError({
|
||||||
|
kind: CadError.KIND.INVALID_PARAMS,
|
||||||
|
userMessage: errors.map(err => `${err.path.join('.')}: ${err.message}`).join('\n')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return op.run(params, services);
|
||||||
}
|
}
|
||||||
|
|
||||||
function runOrGetPreRunResults(request) {
|
function runOrGetPreRunResults(request) {
|
||||||
|
|
@ -92,6 +104,7 @@ export function activate({streams, services}) {
|
||||||
streams.craft.models.next(Array.from(models).sort(m => m.id));
|
streams.craft.models.next(Array.from(models).sort(m => m.id));
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
//TODO: need to find a way to propagate the error to the wizard.
|
||||||
setTimeout(() => streams.craft.modifications.next({
|
setTimeout(() => streams.craft.modifications.next({
|
||||||
...curr,
|
...curr,
|
||||||
pointer: i-1
|
pointer: i-1
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ export default function initializeBySchema(schema, context) {
|
||||||
val = context.streams.selection[md.type].value[0];
|
val = context.streams.selection[md.type].value[0];
|
||||||
} else if (md.type === 'object') {
|
} else if (md.type === 'object') {
|
||||||
val = initializeBySchema(md.schema, context);
|
val = initializeBySchema(md.schema, context);
|
||||||
|
} else if (md.type === 'number') {
|
||||||
|
val = md.defaultValue + '';
|
||||||
} else {
|
} else {
|
||||||
val = md.defaultValue;
|
val = md.defaultValue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import {ENTITIES} from '../scene/entites';
|
import {ENTITIES} from '../scene/entites';
|
||||||
|
|
||||||
export default function validateParams(services, params, schema, errors, parentPath) {
|
export default function materializeParams(services, params, schema, result, errors, parentPath) {
|
||||||
|
|
||||||
errors = errors || [];
|
|
||||||
parentPath = parentPath || ROOT_PATH;
|
parentPath = parentPath || ROOT_PATH;
|
||||||
|
|
||||||
for (let field of Object.keys(schema)) {
|
for (let field of Object.keys(schema)) {
|
||||||
|
|
@ -17,18 +16,20 @@ export default function validateParams(services, params, schema, errors, parentP
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (md.type === 'number') {
|
if (md.type === 'number') {
|
||||||
if (typeof value !== 'number') {
|
try {
|
||||||
errors.push({path: [...parentPath, field], message: 'not a number type'});
|
value = services.expressions.evaluateExpression(value);
|
||||||
} else {
|
} catch (e) {
|
||||||
if (md.min !== undefined ) {
|
errors.push({path: [...parentPath, field], message: 'unable to evaluate expression'});
|
||||||
if (value < md.min) {
|
}
|
||||||
errors.push({path: [...parentPath, field], message: 'less than allowed'});
|
|
||||||
}
|
if (md.min !== undefined ) {
|
||||||
|
if (value < md.min) {
|
||||||
|
errors.push({path: [...parentPath, field], message: 'less than allowed'});
|
||||||
}
|
}
|
||||||
if (md.max !== undefined ) {
|
}
|
||||||
if (value > md.max) {
|
if (md.max !== undefined ) {
|
||||||
errors.push({path: [...parentPath, field], message: 'greater than allowed'});
|
if (value > md.max) {
|
||||||
}
|
errors.push({path: [...parentPath, field], message: 'greater than allowed'});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (md.type === 'string') {
|
} else if (md.type === 'string') {
|
||||||
|
|
@ -55,12 +56,14 @@ export default function validateParams(services, params, schema, errors, parentP
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
errors.push({path: [...parentPath, field], message: 'not an array type'});
|
errors.push({path: [...parentPath, field], message: 'not an array type'});
|
||||||
}
|
}
|
||||||
value.forEach((item , i) => {
|
value = value.map((item , i) => {
|
||||||
validateParams(services, item, md.schema, errors, [...parentPath, i]);
|
let itemResult = {};
|
||||||
|
materializeParams(services, item, md.schema, item, errors, [...parentPath, i]);
|
||||||
|
return itemResult;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
result[field] = value;
|
||||||
}
|
}
|
||||||
return errors;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +23,6 @@ export default class Wizard extends React.Component {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
hasError: false,
|
hasError: false,
|
||||||
validationErrors: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -51,8 +50,7 @@ export default class Wizard extends React.Component {
|
||||||
|
|
||||||
let formContext = {
|
let formContext = {
|
||||||
data: params,
|
data: params,
|
||||||
validationErrors: this.state.validationErrors,
|
updateParam
|
||||||
updateParam,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let Form = operation.form;
|
let Form = operation.form;
|
||||||
|
|
@ -72,15 +70,13 @@ export default class Wizard extends React.Component {
|
||||||
<Button type='accent' onClick={this.onOK}>OK</Button>
|
<Button type='accent' onClick={this.onOK}>OK</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
{this.state.hasError && <div className={ls.errorMessage}>
|
{this.state.hasError && <div className={ls.errorMessage}>
|
||||||
performing operation with current parameters leads to an invalid object
|
{this.state.algorithmError && <span>
|
||||||
(self-intersecting / zero-thickness / complete degeneration or unsupported cases)
|
performing operation with current parameters leads to an invalid object
|
||||||
|
(self-intersecting / zero-thickness / complete degeneration or unsupported cases)
|
||||||
|
</span>}
|
||||||
{this.state.code && <div className={ls.errorCode}>{this.state.code}</div>}
|
{this.state.code && <div className={ls.errorCode}>{this.state.code}</div>}
|
||||||
{this.state.userMessage && <div className={ls.userErrorMessage}>{this.state.userMessage}</div>}
|
{this.state.userMessage && <div className={ls.userErrorMessage}>{this.state.userMessage}</div>}
|
||||||
</div>}
|
</div>}
|
||||||
{this.state.validationErrors.length !== 0 && <div className={ls.errorMessage}>
|
|
||||||
{this.state.validationErrors.map((err, i) => <div key={i}> {err.path.join(' ')} {err.message}</div>)}
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Window>;
|
</Window>;
|
||||||
}
|
}
|
||||||
|
|
@ -110,17 +106,6 @@ export default class Wizard extends React.Component {
|
||||||
|
|
||||||
onOK = () => {
|
onOK = () => {
|
||||||
try {
|
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();
|
this.props.onOK();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
|
|
@ -135,6 +120,9 @@ export default class Wizard extends React.Component {
|
||||||
if (error.TYPE === CadError) {
|
if (error.TYPE === CadError) {
|
||||||
let {code, userMessage, kind} = error;
|
let {code, userMessage, kind} = error;
|
||||||
printError = !code;
|
printError = !code;
|
||||||
|
if (CadError.ALGORITMTHM_ERROR_KINDS.includes(kind)) {
|
||||||
|
stateUpdate.algorithmError = true
|
||||||
|
}
|
||||||
if (code && kind === CadError.KIND.INTERNAL_ERROR) {
|
if (code && kind === CadError.KIND.INTERNAL_ERROR) {
|
||||||
console.warn('Operation Error Code: ' + code);
|
console.warn('Operation Error Code: ' + code);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
.errorMessage {
|
.errorMessage {
|
||||||
color: lightgoldenrodyellow;
|
color: lightgoldenrodyellow;
|
||||||
}
|
white-space: pre-line;
|
||||||
|
|
||||||
.userErrorMessage, .errorCode {
|
|
||||||
font-size: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.userErrorMessage {
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.errorCode {
|
.errorCode {
|
||||||
|
font-size: 9px;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,13 @@ import Wizard from './Wizard';
|
||||||
import connect from 'ui/connect';
|
import connect from 'ui/connect';
|
||||||
import decoratorChain from 'ui/decoratorChain';
|
import decoratorChain from 'ui/decoratorChain';
|
||||||
import mapContext from 'ui/mapContext';
|
import mapContext from 'ui/mapContext';
|
||||||
import {finishHistoryEditing, stepOverriding} from '../../craftHistoryUtils';
|
import {finishHistoryEditing} from '../../craftHistoryUtils';
|
||||||
import validateParams from '../../validateParams';
|
|
||||||
import {NOOP} from 'gems/func';
|
|
||||||
import {clone} from 'gems/objects';
|
|
||||||
|
|
||||||
function WizardManager({type, changingHistory, resolve, cancel, stepHistory, insertOperation, cancelHistoryEdit, applyWorkingRequest, validator}) {
|
function WizardManager({type, changingHistory, resolve, cancel, stepHistory, insertOperation, cancelHistoryEdit, applyWorkingRequest}) {
|
||||||
if (!type) {
|
if (!type) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return <Wizard resolveOperation={resolve}
|
return <Wizard resolveOperation={resolve}
|
||||||
validator={validator}
|
|
||||||
onCancel={changingHistory ? cancelHistoryEdit : cancel}
|
onCancel={changingHistory ? cancelHistoryEdit : cancel}
|
||||||
onOK={applyWorkingRequest} />
|
onOK={applyWorkingRequest} />
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +18,6 @@ export default decoratorChain(
|
||||||
connect(streams => streams.wizard.effectiveOperation),
|
connect(streams => streams.wizard.effectiveOperation),
|
||||||
mapContext((ctx, props) => ({
|
mapContext((ctx, props) => ({
|
||||||
cancel: ctx.services.wizard.cancel,
|
cancel: ctx.services.wizard.cancel,
|
||||||
validator: (params, schema) => validateParams(ctx.services, params, schema),
|
|
||||||
resolve: type => ctx.services.operation.get(type),
|
resolve: type => ctx.services.operation.get(type),
|
||||||
cancelHistoryEdit: () => ctx.streams.craft.modifications.update(modifications => finishHistoryEditing(modifications)),
|
cancelHistoryEdit: () => ctx.streams.craft.modifications.update(modifications => finishHistoryEditing(modifications)),
|
||||||
applyWorkingRequest: ctx.services.wizard.applyWorkingRequest
|
applyWorkingRequest: ctx.services.wizard.applyWorkingRequest
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ObjectExplorer from '../../craft/ui/ObjectExplorer';
|
|
||||||
import OperationHistory from '../../craft/ui/OperationHistory';
|
|
||||||
import Folder from 'ui/components/Folder';
|
import Folder from 'ui/components/Folder';
|
||||||
import Fa from '../../../../../modules/ui/components/Fa';
|
|
||||||
import ls from './FloatView.less';
|
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 {
|
export default class FloatView extends React.Component {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
|
@ -13,33 +17,32 @@ export default class FloatView extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let {views, getDescriptor} = this.props;
|
||||||
|
|
||||||
|
function view(id) {
|
||||||
|
let {title, icon, Component} = getDescriptor(id);
|
||||||
|
return <Folder className={ls.folder} title={<span> <Fa fw icon={icon}/> {title}</span>}>
|
||||||
|
<Component/>
|
||||||
|
</Folder>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function icon(id) {
|
||||||
|
let {Icon} = getDescriptor(id);
|
||||||
|
return <Icon />
|
||||||
|
}
|
||||||
|
|
||||||
return <div className={ls.root}>
|
return <div className={ls.root}>
|
||||||
<div className={ls.tabs}>
|
<div className={ls.tabs}>
|
||||||
{['project', 'history'].map(tabId => <Tab selected={this.state.selected === tabId} key={tabId}
|
{views.map(tabId => <ToolButton pressed={this.state.selected === tabId}
|
||||||
onClick={() => this.setState({selected: this.state.selected === tabId ? null : tabId})}>{getIcon(tabId)}</Tab>)}
|
key={tabId}
|
||||||
|
onClick={() => this.setState({selected: this.state.selected === tabId ? null : tabId})}>
|
||||||
|
{<Fa fw icon={getDescriptor(tabId).icon}/>}
|
||||||
|
</ToolButton>)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{this.state.selected && <div className={ls.main}>
|
{this.state.selected && <div className={ls.main}>
|
||||||
{this.state.selected === 'project' && <Folder title={<span> <Fa fw icon='cubes'/> Model</span>}>
|
{view(this.state.selected)}
|
||||||
<ObjectExplorer/>
|
|
||||||
</Folder>}
|
|
||||||
{this.state.selected === 'history' && <Folder title={<span> <Fa fw icon='history'/> Modifications</span>}>
|
|
||||||
<OperationHistory/>
|
|
||||||
</Folder>}
|
|
||||||
|
|
||||||
</div>}
|
</div>}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function Tab({children, selected, onClick}) {
|
|
||||||
return <div className={cx(ls.tab, selected && ls.selected)} onClick={onClick}>{children}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIcon(id) {
|
|
||||||
if (id === 'history') {
|
|
||||||
return <Fa fw icon='history'/>;
|
|
||||||
} else if (id === 'project') {
|
|
||||||
return <Fa fw icon='cubes'/>;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -11,6 +11,11 @@
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabs button {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -19,24 +24,8 @@
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
.folder {
|
||||||
|
height: 100%;
|
||||||
border: 1px solid @bg-color;
|
display: flex;
|
||||||
margin: 3px;
|
flex-direction: column;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -15,18 +15,27 @@ export function defineStreams({streams}) {
|
||||||
sketcherControl: state([]),
|
sketcherControl: state([]),
|
||||||
sketcherToolbarsVisible: state(false)
|
sketcherToolbarsVisible: state(false)
|
||||||
},
|
},
|
||||||
|
floatViews: state([]),
|
||||||
sockets: {}
|
sockets: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function activate({services}) {
|
export function activate({streams, services}) {
|
||||||
|
|
||||||
let components = new Map();
|
let components = new Map();
|
||||||
const registerComponent = (id, Component) => components.set(id, Component);
|
const registerComponent = (id, Component) => components.set(id, Component);
|
||||||
const getComponent = id => components.get(id);
|
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 = {
|
services.ui = {
|
||||||
registerComponent, getComponent
|
registerComponent, getComponent, registerFloatView, getFloatView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
92
web/app/cad/expressions/Expressions.jsx
Normal file
92
web/app/cad/expressions/Expressions.jsx
Normal file
|
|
@ -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 <ToolButton onClick={() => this.setState({activeTab: name})} pressed={this.state.activeTab === name}>{icon} {name}</ToolButton>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <div className={ls.root}>
|
||||||
|
<Row className={ls.switcher}>
|
||||||
|
{tabBtn('Script', <Fa fw icon='pencil' />)}
|
||||||
|
{tabBtn('Table', <Fa fw icon='table' />)}
|
||||||
|
{errors.length > 0 && <span><Fa icon='warning' className={cx(cmn.dangerColor, cmn.inlineBlock)} /></span>}
|
||||||
|
{!synced && <ReevaluateActionButton type='accent' className={cmn.floatRight}><Fa fw icon='check'/></ReevaluateActionButton>}
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<div className={ls.workingArea}>
|
||||||
|
|
||||||
|
{this.state.activeTab === 'Script' && <Script reevaluateExpressions={reevaluateExpressions}/>}
|
||||||
|
|
||||||
|
{this.state.activeTab === 'Table' && <VarTable table={table} errors={errors}/>}
|
||||||
|
|
||||||
|
{errors.length > 0 && <Folder title={<Fragment><Fa icon='warning' className={cx(cmn.dangerColor)} /> Script Errors</Fragment>}>
|
||||||
|
<Stack>
|
||||||
|
{errors.map(err => <div key={err.line}>
|
||||||
|
line {err.line + 1}: {err.message}
|
||||||
|
</div>)}
|
||||||
|
</Stack>
|
||||||
|
</Folder>}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReevaluateActionButton = actionDecorator('expressionsUpdateTable')(ToolButton);
|
||||||
|
|
||||||
|
const Script = bind(streams => streams.expressions.script)(
|
||||||
|
function Script({value, onChange, reevaluateExpressions}) {
|
||||||
|
return <textarea placeholder='for example: A = 50'
|
||||||
|
className={ls.script}
|
||||||
|
value={value}
|
||||||
|
onChange={e => onChange(e.target.value)}
|
||||||
|
onBlur={e => reevaluateExpressions(e.target.value)} />
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const VarTable = bind(streams => streams.expressions.list)(
|
||||||
|
function VarTable({value}) {
|
||||||
|
return <table className={cx(cmn.fullWidth, cmn.stripedTable, cmn.delineatedTable)}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{value.map(({name, value}, i) => <tr key={i}>
|
||||||
|
<td>{name}</td>
|
||||||
|
<td>{value}</td>
|
||||||
|
</tr>)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
);
|
||||||
33
web/app/cad/expressions/Expressions.less
Normal file
33
web/app/cad/expressions/Expressions.less
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
@import "~ui/styles/theme.less";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.script {
|
||||||
|
flex: 1;
|
||||||
|
resize: none;
|
||||||
|
background: inherit;
|
||||||
|
border: none;
|
||||||
|
color: #C4E1A4;
|
||||||
|
padding: 2px;
|
||||||
|
outline: none;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workingArea {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switcher {
|
||||||
|
border-bottom: 1px solid @border-color;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolBar {
|
||||||
|
|
||||||
|
}
|
||||||
85
web/app/cad/expressions/expressionsPlugin.js
Normal file
85
web/app/cad/expressions/expressionsPlugin.js
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import {state, stream, merge} from 'lstream';
|
||||||
|
import {indexArray} from '../../../../modules/gems/iterables';
|
||||||
|
import {NOOP} from '../../../../modules/gems/func';
|
||||||
|
|
||||||
|
export function defineStreams(ctx) {
|
||||||
|
const script = state('');
|
||||||
|
const list = state([]);
|
||||||
|
const table = list.map(varList => indexArray(varList, i => i.name, i => i.value)).remember();
|
||||||
|
const synced = merge(script.map(() => false), list.map(() => true));
|
||||||
|
ctx.streams.expressions = {
|
||||||
|
script, list, table, synced,
|
||||||
|
errors: state([])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function activate(ctx) {
|
||||||
|
let _evaluateExpression = NOOP;
|
||||||
|
function reevaluateExpressions() {
|
||||||
|
let {varList, errors, evaluateExpression} = rebuildVariableTable(ctx.streams.expressions.script.value);
|
||||||
|
ctx.streams.expressions.list.next(varList);
|
||||||
|
ctx.streams.expressions.errors.next(errors);
|
||||||
|
_evaluateExpression = evaluateExpression;
|
||||||
|
}
|
||||||
|
function load(script) {
|
||||||
|
ctx.streams.expressions.script.next(script);
|
||||||
|
reevaluateExpressions();
|
||||||
|
}
|
||||||
|
function evaluateExpression(expr) {
|
||||||
|
if (typeof expr === 'number') {
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
let value = ctx.streams.expressions.table.value[expr];
|
||||||
|
if (value === undefined) {
|
||||||
|
value = parseFloat(expr);
|
||||||
|
if (isNaN(value)) {
|
||||||
|
value = _evaluateExpression(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
ctx.services.expressions = {
|
||||||
|
reevaluateExpressions, load, evaluateExpression
|
||||||
|
};
|
||||||
|
ctx.services.action.registerAction({
|
||||||
|
id: 'expressionsUpdateTable',
|
||||||
|
appearance: {
|
||||||
|
info: 'reevaluate expression script (happens automatically on script focus lost)',
|
||||||
|
label: 'update expressions',
|
||||||
|
},
|
||||||
|
invoke: ({services}) => {
|
||||||
|
services.extension.reevaluateExpressions();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuildVariableTable(script) {
|
||||||
|
let varList = [];
|
||||||
|
let errors = [];
|
||||||
|
if (script == null) return;
|
||||||
|
let lines = script.split('\n');
|
||||||
|
let evalContext = "(function() { \n";
|
||||||
|
function evaluateExpression(expr) {
|
||||||
|
return eval(evalContext + "return " + expr + "; \n})()");
|
||||||
|
}
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
let line = lines[i];
|
||||||
|
let m = line.match(/^\s*([^\s]+)\s*=(.+)$/);
|
||||||
|
if (m != null && m.length === 3) {
|
||||||
|
let name = m[1];
|
||||||
|
try {
|
||||||
|
let value = evaluateExpression(m[2]);
|
||||||
|
varList.push({name, value});
|
||||||
|
evalContext += "const " + name + " = " + value + ";\n"
|
||||||
|
} catch (e) {
|
||||||
|
errors.push({
|
||||||
|
line: i,
|
||||||
|
message: e.message
|
||||||
|
});
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {varList, errors, evaluateExpression};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -21,17 +21,17 @@ import * as ProjectPlugin from '../projectPlugin';
|
||||||
import * as SketcherPlugin from '../sketch/sketcherPlugin';
|
import * as SketcherPlugin from '../sketch/sketcherPlugin';
|
||||||
import * as ExportPlugin from '../exportPlugin';
|
import * as ExportPlugin from '../exportPlugin';
|
||||||
import * as TpiPlugin from '../tpi/tpiPlugin';
|
import * as TpiPlugin from '../tpi/tpiPlugin';
|
||||||
|
|
||||||
import * as PartModellerPlugin from '../part/partModellerPlugin';
|
|
||||||
import * as ViewSyncPlugin from '../scene/viewSyncPlugin';
|
import * as ViewSyncPlugin from '../scene/viewSyncPlugin';
|
||||||
|
|
||||||
|
import PartModellerPlugins from '../part/partModelerPlugins';
|
||||||
|
|
||||||
import context from 'context';
|
import context from 'context';
|
||||||
|
|
||||||
import startReact from "../dom/startReact";
|
import startReact from "../dom/startReact";
|
||||||
|
|
||||||
export default function startApplication(callback) {
|
export default function startApplication(callback) {
|
||||||
|
|
||||||
let applicationPlugins = [PartModellerPlugin];
|
let applicationPlugins = PartModellerPlugins;
|
||||||
|
|
||||||
let preUIPlugins = [
|
let preUIPlugins = [
|
||||||
LifecyclePlugin,
|
LifecyclePlugin,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import * as UIConfigPlugin from './uiConfigPlugin';
|
import * as UIConfigPlugin from './uiConfigPlugin';
|
||||||
import * as PartOperationsPlugin from './partOperationsPlugin';
|
import * as PartOperationsPlugin from './partOperationsPlugin';
|
||||||
import * as DebugPlugin from '../debugPlugin';
|
import * as DebugPlugin from '../debugPlugin';
|
||||||
import {activatePlugins} from "../init/startApplication";
|
import * as ExpressionsPlugin from '../expressions/expressionsPlugin';
|
||||||
|
|
||||||
const PART_MODELLER_PLUGINS = [
|
export default [
|
||||||
UIConfigPlugin,
|
UIConfigPlugin,
|
||||||
DebugPlugin,
|
DebugPlugin,
|
||||||
|
ExpressionsPlugin,
|
||||||
PartOperationsPlugin
|
PartOperationsPlugin
|
||||||
];
|
];
|
||||||
|
|
||||||
export function activate(context) {
|
|
||||||
activatePlugins(PART_MODELLER_PLUGINS, context);
|
|
||||||
}
|
|
||||||
|
|
@ -2,6 +2,10 @@ import CoreActions from '../actions/coreActions';
|
||||||
import OperationActions from '../actions/operationActions';
|
import OperationActions from '../actions/operationActions';
|
||||||
import HistoryActions from '../actions/historyActions';
|
import HistoryActions from '../actions/historyActions';
|
||||||
import menuConfig from './menuConfig';
|
import menuConfig from './menuConfig';
|
||||||
|
import ObjectExplorer from '../craft/ui/ObjectExplorer';
|
||||||
|
import React from 'react';
|
||||||
|
import OperationHistory from '../craft/ui/OperationHistory';
|
||||||
|
import Expressions from '../expressions/Expressions';
|
||||||
|
|
||||||
export function activate({services, streams}) {
|
export function activate({services, streams}) {
|
||||||
streams.ui.controlBars.left.value = ['menu.file', 'menu.craft', 'menu.boolean', 'menu.primitives', 'Donate', 'GitHub'];
|
streams.ui.controlBars.left.value = ['menu.file', 'menu.craft', 'menu.boolean', 'menu.primitives', 'Donate', 'GitHub'];
|
||||||
|
|
@ -19,4 +23,8 @@ export function activate({services, streams}) {
|
||||||
services.action.registerActions(HistoryActions);
|
services.action.registerActions(HistoryActions);
|
||||||
|
|
||||||
services.menu.registerMenus(menuConfig);
|
services.menu.registerMenus(menuConfig);
|
||||||
|
|
||||||
|
services.ui.registerFloatView('project', ObjectExplorer, 'Model', 'cubes');
|
||||||
|
services.ui.registerFloatView('history', OperationHistory, 'Modifications', 'history');
|
||||||
|
services.ui.registerFloatView('expressions', Expressions, 'Expressions', 'percent');
|
||||||
}
|
}
|
||||||
|
|
@ -31,16 +31,20 @@ export function activate(context) {
|
||||||
function save() {
|
function save() {
|
||||||
let data = {};
|
let data = {};
|
||||||
data.history = streams.craft.modifications.value.history;
|
data.history = streams.craft.modifications.value.history;
|
||||||
|
data.expressions = streams.expressions.script.value;
|
||||||
services.storage.set(projectStorageKey(), JSON.stringify(data));
|
services.storage.set(projectStorageKey(), JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
try {
|
try {
|
||||||
let data = services.storage.get(services.project.projectStorageKey());
|
let dataStr = services.storage.get(services.project.projectStorageKey());
|
||||||
if (data) {
|
if (dataStr) {
|
||||||
let history = JSON.parse(data).history;
|
let data = JSON.parse(dataStr);
|
||||||
if (history) {
|
if (data.history) {
|
||||||
services.craft.reset(history);
|
services.craft.reset(data.history);
|
||||||
|
}
|
||||||
|
if (data.expressions) {
|
||||||
|
services.expressions.load(data.expressions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,8 @@ CadError.KIND = {
|
||||||
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
||||||
UNSUPPORTED_CASE: 'UNSUPPORTED_CASE',
|
UNSUPPORTED_CASE: 'UNSUPPORTED_CASE',
|
||||||
INVALID_INPUT: 'INVALID_INPUT',
|
INVALID_INPUT: 'INVALID_INPUT',
|
||||||
};
|
INVALID_PARAMS: 'INVALID_PARAMS'
|
||||||
|
};
|
||||||
|
|
||||||
|
CadError.ALGORITMTHM_ERROR_KINDS = ['INTERNAL_ERROR', 'UNSUPPORTED_CASE', 'INVALID_INPUT'];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue