From 66c4707a83f9104029fbe982ef129cc4b1f192e9 Mon Sep 17 00:00:00 2001 From: "Val Erastov (xibyte)" Date: Tue, 9 Jun 2020 21:52:49 -0700 Subject: [PATCH] part import from repository --- modules/context/index.ts | 8 +- modules/gems/{iterables.js => iterables.ts} | 2 +- modules/lstream/emitter.js | 12 +- modules/lstream/state.js | 4 +- modules/math/{vector.js => vector.ts} | 73 ++-- modules/ui/bind.js | 39 +- modules/ui/components/{Fa.jsx => Fa.tsx} | 9 +- modules/ui/components/Tree.tsx | 6 +- package-lock.json | 356 ++++++++---------- package.json | 2 +- .../integration/part3d/partImport.spec.ts | 25 +- test/cypress/support/commands.js | 14 + test/cypress/support/index.d.ts | 7 +- web/app/cad/actions/actionSystemPlugin.ts | 6 +- web/app/cad/actions/coreActions.js | 4 +- web/app/cad/craft/cadRegistryPlugin.ts | 87 ++--- web/app/cad/craft/craftPlugin.js | 183 --------- web/app/cad/craft/craftPlugin.ts | 263 +++++++++++++ web/app/cad/craft/cutExtrude/cutExtrude.js | 9 +- web/app/cad/craft/e0/common.js | 6 +- web/app/cad/craft/e0/craftMethods.js | 4 +- web/app/cad/craft/e0/operationHandler.js | 8 +- web/app/cad/craft/materializeParams.js | 10 +- web/app/cad/craft/operationPlugin.ts | 44 +-- web/app/cad/craft/ui/HistoryTimeline.less | 2 +- .../cad/craft/wizard/components/Wizard.jsx | 17 +- web/app/cad/craft/wizard/wizardPlugin.js | 2 +- web/app/cad/debugPlugin.js | 6 +- .../dom/components/ContributedComponents.jsx | 2 +- web/app/cad/dom/components/FloatView.jsx | 2 +- web/app/cad/exportPlugin.js | 6 +- web/app/cad/expressions/Expressions.jsx | 92 ----- web/app/cad/expressions/Expressions.tsx | 89 +++++ ...ressionsPlugin.js => expressionsPlugin.ts} | 88 +++-- web/app/cad/init/lifecyclePlugin.js | 5 +- web/app/cad/init/startApplication.js | 18 +- web/app/cad/model/{mdatum.js => mdatum.ts} | 19 +- web/app/cad/model/{medge.js => medge.ts} | 4 +- web/app/cad/model/{mface.js => mface.ts} | 28 +- web/app/cad/model/{mloop.js => mloop.ts} | 5 + web/app/cad/model/mobject.ts | 44 ++- .../cad/model/{mopenFace.js => mopenFace.ts} | 2 + web/app/cad/model/{mshell.js => mshell.ts} | 15 +- .../{msketchObject.js => msketchObject.ts} | 4 + web/app/cad/model/{mvertex.js => mvertex.ts} | 3 + web/app/cad/part/partModelerPlugins.js | 13 - .../importPartOperation/ImportPartForm.tsx | 2 +- .../importPartOperation.ts | 28 +- web/app/cad/partImport/partCatalogConfig.ts | 16 - web/app/cad/partImport/partImportPlugin.ts | 90 ----- web/app/cad/partImport/partRepository.ts | 19 + web/app/cad/partImport/remotePartsConfig.ts | 19 + web/app/cad/partImport/remotePartsPlugin.ts | 188 +++++++++ .../cad/partImport/ui/CatalogPartChooser.tsx | 30 +- web/app/cad/partImport/ui/PartCatalog.tsx | 19 +- web/app/cad/partImport/ui/PartRefControl.tsx | 12 +- ...nagerPlugin.js => projectManagerPlugin.ts} | 98 ++++- .../{projectPlugin.js => projectPlugin.ts} | 78 +++- .../cad/scene/selectionMarker/markerPlugin.js | 2 +- web/app/cad/sketch/inPlaceSketcher.js | 6 +- web/app/cad/sketch/sketchStoragePlugin.ts | 77 ++++ web/app/cad/sketch/sketcherPlugin.ts | 72 +--- web/app/cad/storage/storagePlugin.ts | 6 +- web/app/math/{csys.js => csys.ts} | 12 +- .../sketcher/generators/sketchGenerator.js | 1 + web/app/utils/errors.js | 2 +- 66 files changed, 1432 insertions(+), 992 deletions(-) rename modules/gems/{iterables.js => iterables.ts} (95%) rename modules/math/{vector.js => vector.ts} (65%) rename modules/ui/components/{Fa.jsx => Fa.tsx} (76%) delete mode 100644 web/app/cad/craft/craftPlugin.js create mode 100644 web/app/cad/craft/craftPlugin.ts delete mode 100644 web/app/cad/expressions/Expressions.jsx create mode 100644 web/app/cad/expressions/Expressions.tsx rename web/app/cad/expressions/{expressionsPlugin.js => expressionsPlugin.ts} (56%) rename web/app/cad/model/{mdatum.js => mdatum.ts} (64%) rename web/app/cad/model/{medge.js => medge.ts} (87%) rename web/app/cad/model/{mface.js => mface.ts} (87%) rename web/app/cad/model/{mloop.js => mloop.ts} (71%) rename web/app/cad/model/{mopenFace.js => mopenFace.ts} (92%) rename web/app/cad/model/{mshell.js => mshell.ts} (79%) rename web/app/cad/model/{msketchObject.js => msketchObject.ts} (76%) rename web/app/cad/model/{mvertex.js => mvertex.ts} (77%) delete mode 100644 web/app/cad/part/partModelerPlugins.js delete mode 100644 web/app/cad/partImport/partCatalogConfig.ts delete mode 100644 web/app/cad/partImport/partImportPlugin.ts create mode 100644 web/app/cad/partImport/partRepository.ts create mode 100644 web/app/cad/partImport/remotePartsConfig.ts create mode 100644 web/app/cad/partImport/remotePartsPlugin.ts rename web/app/cad/projectManager/{projectManagerPlugin.js => projectManagerPlugin.ts} (71%) rename web/app/cad/{projectPlugin.js => projectPlugin.ts} (58%) create mode 100644 web/app/cad/sketch/sketchStoragePlugin.ts rename web/app/math/{csys.js => csys.ts} (88%) diff --git a/modules/context/index.ts b/modules/context/index.ts index 149d02dd..55d0df58 100644 --- a/modules/context/index.ts +++ b/modules/context/index.ts @@ -1,5 +1,11 @@ -export interface ApplicationContext { +/** + * CoreContext shouldn't contain any UI services because it can be potentially used in the headless mode + */ +export interface CoreContext { +} + +export interface ApplicationContext extends CoreContext { services: any, streams: any, } diff --git a/modules/gems/iterables.js b/modules/gems/iterables.ts similarity index 95% rename from modules/gems/iterables.js rename to modules/gems/iterables.ts index 4dd04bba..618e4c78 100644 --- a/modules/gems/iterables.js +++ b/modules/gems/iterables.ts @@ -84,7 +84,7 @@ export function removeInPlace(arr, val) { return arr; } -export function indexById(array) { +export function indexById(array: T[]): {[id: string]: T} { const out = {}; array.forEach(i => out[i.id] = i); return out; diff --git a/modules/lstream/emitter.js b/modules/lstream/emitter.js index 3a0d9444..633c13d0 100644 --- a/modules/lstream/emitter.js +++ b/modules/lstream/emitter.js @@ -32,10 +32,20 @@ export class Emitter extends StreamBase { try { this.state = EMITTING; for (let i = 0; i < this.observers.length; i++) { - this.observers[i](value); + callObserver(this.observers[i], value); } } finally { this.state = READY; } } } + +export function callObserver(observer, value) { + try { + observer(value); + } catch (e) { + console.error('Error while observer call:'); + console.error(observer); + console.error(e); + } +} diff --git a/modules/lstream/state.js b/modules/lstream/state.js index bc0cc0a1..fd7f324a 100644 --- a/modules/lstream/state.js +++ b/modules/lstream/state.js @@ -1,4 +1,4 @@ -import {Emitter} from './emitter'; +import {callObserver, Emitter} from './emitter'; export class StateStream extends Emitter { @@ -30,7 +30,7 @@ export class StateStream extends Emitter { } attach(observer) { - observer(this._value); + callObserver(observer, this._value); return super.attach(observer); } } diff --git a/modules/math/vector.js b/modules/math/vector.ts similarity index 65% rename from modules/math/vector.js rename to modules/math/vector.ts index 908e634b..76937c71 100644 --- a/modules/math/vector.js +++ b/modules/math/vector.ts @@ -1,100 +1,104 @@ export default class Vector { - constructor(x, y, z) { + x: number; + y: number; + z: number; + + constructor(x = 0, y = 0, z = 0) { this.x = x || 0; this.y = y || 0; this.z = z || 0; } - set(x, y, z) { + set(x, y, z): Vector { this.x = x || 0; this.y = y || 0; this.z = z || 0; return this; } - set3(data) { + set3(data: [number, number, number]): Vector { this.x = data[0] || 0; this.y = data[1] || 0; this.z = data[2] || 0; return this; } - setV(data) { + setV(data: Vector): Vector { this.x = data.x; this.y = data.y; this.z = data.z; return this; } - multiply(scalar) { + multiply(scalar: number): Vector { return new Vector(this.x * scalar, this.y * scalar, this.z * scalar); } - _multiply(scalar) { + _multiply(scalar: number): Vector { return this.set(this.x * scalar, this.y * scalar, this.z * scalar); } - divide(scalar) { + divide(scalar: number): Vector { return new Vector(this.x / scalar, this.y / scalar, this.z / scalar); } - _divide(scalar) { + _divide(scalar: number): Vector { return this.set(this.x / scalar, this.y / scalar, this.z / scalar); } - dot(vector) { + dot(vector: Vector): number { return this.x * vector.x + this.y * vector.y + this.z * vector.z; } - copy() { + copy(): Vector { return new Vector(this.x, this.y, this.z); } - length() { + length(): number { return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z); }; - lengthSquared() { + lengthSquared(): number { return this.dot(this); } - distanceToSquared(a) { + distanceToSquared(a: Vector): number { return this.minus(a).lengthSquared(); } - distanceTo(a) { + distanceTo(a: Vector): number { return Math.sqrt(this.distanceToSquared(a)); } - minus(vector) { + minus(vector: Vector): Vector { return new Vector(this.x - vector.x, this.y - vector.y, this.z - vector.z); } - _minus(vector) { + _minus(vector: Vector): Vector { this.x -= vector.x; this.y -= vector.y; this.z -= vector.z; return this; } - _minusXYZ(x, y, z) { + _minusXYZ(x, y, z): Vector { this.x -= x; this.y -= y; this.z -= z; return this; } - plusXYZ(x, y, z) { + plusXYZ(x, y, z): Vector { return new Vector(this.x + x, this.y + y, this.z + z); } - plus(vector) { + plus(vector: Vector): Vector { return new Vector(this.x + vector.x, this.y + vector.y, this.z + vector.z); } - _plus(vector) { + _plus(vector: Vector): Vector { this.x += vector.x; this.y += vector.y; this.z += vector.z; @@ -117,11 +121,11 @@ export default class Vector { return this.set(this.x / mag, this.y / mag, this.z / mag) }; - cross(a) { + cross(a: Vector): Vector { return this.copy()._cross(a); }; - _cross(a) { + _cross(a: Vector): Vector { return this.set( this.y * a.z - this.z * a.y, this.z * a.x - this.x * a.z, @@ -129,19 +133,19 @@ export default class Vector { ); }; - negate() { + negate(): Vector { return this.multiply(-1); } - _negate() { + _negate(): Vector { return this._multiply(-1); } - _perpXY() { + _perpXY(): Vector { return this.set(-this.y, this.x, this.z); } - toArray() { + toArray(): [number, number, number] { return [this.x, this.y, this.z]; } @@ -151,15 +155,14 @@ export default class Vector { data[2] = this.z; } - static fromData(arr) { + static fromData(arr: [number, number, number]): Vector { return new Vector().set3(arr); } + + data: () => [number, number, number] = Vector.prototype.toArray; + + unit: () => (Vector) = Vector.prototype.normalize; + _unit: () => (Vector) = Vector.prototype._normalize; + scale: (scalar: number) => Vector = Vector.prototype.multiply; + _scale: (scalar: number) => Vector = Vector.prototype._multiply; } - -Vector.prototype.data = Vector.prototype.toArray; - -Vector.prototype.unit = Vector.prototype.normalize; -Vector.prototype._unit = Vector.prototype._normalize; - -Vector.prototype.scale = Vector.prototype.multiply; -Vector.prototype._scale = Vector.prototype._multiply; diff --git a/modules/ui/bind.js b/modules/ui/bind.js index 21e8a5de..cd59bd87 100644 --- a/modules/ui/bind.js +++ b/modules/ui/bind.js @@ -1,41 +1,16 @@ -import React from 'react'; -import context from 'context'; +import React, {useContext} from 'react'; +import {useStreamWithUpdater} from "./effects"; +import {AppContext} from "../../web/app/cad/dom/components/AppContext"; 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); - - UNSAFE_componentWillMount() { - this.stream = streamProvider(context.streams, this.props); - this.detacher = this.stream.attach(value => { - this.setState({ - hasError: false, - value - }); - }); - } + return function Connected (props) { - componentWillUnmount() { - this.detacher(); - } - - render() { - if (this.state.hasError) { - return null; - } - return ; + const context = useContext(AppContext); + const [value, updater] = useStreamWithUpdater(streamProvider(context, props)); - } - componentDidCatch() { - this.setState({hasError: true}); - } + return ; }; }; } diff --git a/modules/ui/components/Fa.jsx b/modules/ui/components/Fa.tsx similarity index 76% rename from modules/ui/components/Fa.jsx rename to modules/ui/components/Fa.tsx index 1865a972..4acb3b06 100644 --- a/modules/ui/components/Fa.jsx +++ b/modules/ui/components/Fa.tsx @@ -1,7 +1,14 @@ import React from 'react'; import cx from 'classnames'; -export default function Fa({icon, fw, fa, stack, className, ...props}) { +export default function Fa({icon, fw, fa, stack, className, ...props}: { + icon: string, + fw?: boolean, + fa?: string[], + stack?: string, + className?: string, + props?: any +}) { let faCss = fa ? fa.map(s => 'fa-' + s) : []; if (icon) { icon = 'fa-' + icon; diff --git a/modules/ui/components/Tree.tsx b/modules/ui/components/Tree.tsx index a1d56188..b0ea0b3c 100644 --- a/modules/ui/components/Tree.tsx +++ b/modules/ui/components/Tree.tsx @@ -2,12 +2,14 @@ import React, {useState, ReactNode} from "react"; import cx from 'classnames'; import {GoPrimitiveDot, GoTriangleDown, GoTriangleRight} from "react-icons/go"; -export function Tree({children, icon, label, initCollapsed = false, className} : { +export function Tree({children, icon, label, initCollapsed = false, className, onClick, ...props} : { initCollapsed?: boolean children?: ReactNode, icon?: ReactNode, label?: ReactNode, className?: string + onClick?: (e: any) => void, + props?: JSX.IntrinsicAttributes }) { const headless = !label; @@ -22,7 +24,7 @@ export function Tree({children, icon, label, initCollapsed = false, className} : } {icon} - {label} + {label} } {children &&
{children}
} diff --git a/package-lock.json b/package-lock.json index b591f7f4..12a12ef1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1529,41 +1529,49 @@ "cli-cursor": "^1.0.2", "date-fns": "^1.27.2", "figures": "^1.7.0" + } + }, + "@cypress/request": { + "version": "2.88.5", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.5.tgz", + "integrity": "sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" }, "dependencies": { - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", "dev": true }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", "dev": true, "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" + "mime-db": "1.44.0" } } } @@ -1700,6 +1708,12 @@ "@types/react": "*" } }, + "@types/sinonjs__fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz", + "integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==", + "dev": true + }, "@types/sizzle": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", @@ -1980,9 +1994,9 @@ "dev": true }, "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true }, "ansi-html": { @@ -3104,12 +3118,12 @@ "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=" }, "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "^1.0.1" } }, "cli-table3": { @@ -3644,13 +3658,15 @@ "dev": true }, "cypress": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.2.0.tgz", - "integrity": "sha512-8LdreL91S/QiTCLYLNbIjLL8Ht4fJmu/4HGLxUI20Tc7JSfqEfCmXELrRfuPT0kjosJwJJZacdSji9XSRkPKUw==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.7.0.tgz", + "integrity": "sha512-Vav6wUFhPRlImIND/2lOQlUnAWzgCC/iXyJlJjX9nJOJul5LC1vUpf/m8Oiae870PFPyT0ZLLwPHKTXZNdXmHw==", "dev": true, "requires": { "@cypress/listr-verbose-renderer": "0.4.1", + "@cypress/request": "2.88.5", "@cypress/xvfb": "1.2.4", + "@types/sinonjs__fake-timers": "6.0.1", "@types/sizzle": "2.3.2", "arch": "2.1.1", "bluebird": "3.7.2", @@ -3664,7 +3680,7 @@ "eventemitter2": "4.1.2", "execa": "1.0.0", "executable": "4.1.1", - "extract-zip": "1.6.7", + "extract-zip": "1.7.0", "fs-extra": "8.1.0", "getos": "3.1.4", "is-ci": "2.0.0", @@ -3673,12 +3689,11 @@ "listr": "0.14.3", "lodash": "4.17.15", "log-symbols": "3.0.0", - "minimist": "1.2.2", + "minimist": "1.2.5", "moment": "2.24.0", "ospath": "1.2.2", "pretty-bytes": "5.3.0", "ramda": "0.26.1", - "request": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16", "request-progress": "3.0.0", "supports-color": "7.1.0", "tmp": "0.1.0", @@ -3724,19 +3739,6 @@ "integrity": "sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==", "dev": true }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -3752,30 +3754,6 @@ "integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU=", "dev": true }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -3790,66 +3768,12 @@ "path-is-absolute": "^1.0.0" } }, - "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "dev": true, - "requires": { - "mime-db": "1.43.0" - } - }, - "minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA==", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "request": { - "version": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16", - "from": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -3884,16 +3808,6 @@ "requires": { "rimraf": "^2.6.3" } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } } } }, @@ -5051,24 +4965,24 @@ } }, "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", "dev": true, "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" }, "dependencies": { - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "fd-slicer": "~1.0.1" + "minimist": "^1.2.5" } } } @@ -5119,9 +5033,9 @@ } }, "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, "requires": { "pend": "~1.2.0" @@ -5134,12 +5048,13 @@ "dev": true }, "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" } }, "file-entry-cache": { @@ -5387,9 +5302,9 @@ }, "dependencies": { "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true } } @@ -7581,16 +7496,6 @@ "strip-ansi": "^3.0.1" }, "dependencies": { - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, "indent-string": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", @@ -7640,6 +7545,43 @@ "supports-color": "^5.3.0" } }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -7774,6 +7716,34 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -8604,13 +8574,10 @@ } }, "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true }, "opn": { "version": "5.5.0", @@ -10018,13 +9985,13 @@ "dev": true }, "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", "dev": true, "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" } }, "ret": { @@ -11275,7 +11242,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "optional": true, "requires": { "psl": "^1.1.28", "punycode": "^2.1.1" @@ -11284,8 +11250,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "optional": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, @@ -12704,17 +12669,6 @@ "requires": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" - }, - "dependencies": { - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - } } } } diff --git a/package.json b/package.json index 464f245c..e1a50221 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "babel-plugin-transform-decorators-legacy": "^1.3.5", "babel-polyfill": "^6.26.0", "css-loader": "^3.4.2", - "cypress": "^4.2.0", + "cypress": "^4.7.0", "cypress-wait-until": "^1.7.1", "eslint": "^6.8.0", "eslint-plugin-babel": "^5.3.0", diff --git a/test/cypress/integration/part3d/partImport.spec.ts b/test/cypress/integration/part3d/partImport.spec.ts index e64a2f15..2eb7991d 100644 --- a/test/cypress/integration/part3d/partImport.spec.ts +++ b/test/cypress/integration/part3d/partImport.spec.ts @@ -5,9 +5,32 @@ describe("Part Import", () => { cy.openModeller(); }); - it("import from web-cad.org smoke test", () => { + it("import from web-cad.org basic flow", () => { cy.getActionButton('IMPORT_PART').click(); cy.get('.wizard').should('have.attr', 'data-operation-id', 'IMPORT_PART'); + cy.get('.part-catalog-chooser').should('exist'); + cy.get('.part-catalog-chooser [data-part-ref="web-cad.org/primitives.box"]').click(); + cy.wizardOK(); + cy.simulateClickByRayCast([-84, 242, 415], [84, 232, 307]); + + cy.showEntitySelection(); + cy.getEntitySelection('face').should('have.text', 'web-cad.org/primitives.box:0:S:0/F:0'); + }); + + it("should refer to right face while extrude operation of external part", () => { + cy.getActionButton('IMPORT_PART').click(); + cy.get('.wizard').should('have.attr', 'data-operation-id', 'IMPORT_PART'); + + cy.get('.part-catalog-chooser').should('exist'); + cy.get('.part-catalog-chooser [data-part-ref="web-cad.org/lumber.2x4"]').click(); + cy.wizardOK(); + cy.simulateClickByRayCast([-84, 242, 415], [84, 232, 307]); + + cy.showEntitySelection(); + cy.getEntitySelection('face').should('have.text', 'web-cad.org/lumber.2x4:0:S:2/F:0'); + + }); + }); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 6d533da4..4f344544 100644 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -45,6 +45,20 @@ Cypress.Commands.add("commitSketch", () => { }); +Cypress.Commands.add("wizardOK", () => { + cy.getModellerTPI().then(tpi => tpi.wizardOK()) +}); + Cypress.Commands.add("getModellerTPI", () => { return cy.window().then(win => modellerUISubject(win.__CAD_APP)); }); + +Cypress.Commands.add("showEntitySelection", () => { + return cy.get('.float-view-btn[data-view="selection"]').click(); +}); + +Cypress.Commands.add("getEntitySelection", (type) => { + return cy.get(`.selection-view [data-entity="${type}"] li`); +}); + + diff --git a/test/cypress/support/index.d.ts b/test/cypress/support/index.d.ts index cb6f17ac..61953d7b 100644 --- a/test/cypress/support/index.d.ts +++ b/test/cypress/support/index.d.ts @@ -9,15 +9,20 @@ declare namespace Cypress { simulateClickByRayCast(from :vec3, to: vec3): Chainable; openSketcher(): Chainable; commitSketch(): Chainable; + wizardOK(): Chainable; getModellerTPI(): Chainable; + showEntitySelection(): Chainable; + getEntitySelection(type: string): Chainable; + } export interface ModellerTPI { + wizardOK(): void; } export interface SketcherTPI { - addRectangle(x1: number, y1: number, x2: number, y2: number); + addRectangle(x1: number, y1: number, x2: number, y2: number): any; } diff --git a/web/app/cad/actions/actionSystemPlugin.ts b/web/app/cad/actions/actionSystemPlugin.ts index 005869d0..dc96791f 100644 --- a/web/app/cad/actions/actionSystemPlugin.ts +++ b/web/app/cad/actions/actionSystemPlugin.ts @@ -2,10 +2,10 @@ import {enableAnonymousActionHint} from './anonHint'; import * as stream from 'lstream'; import {state, StateStream, Stream} from 'lstream'; import {LOG_FLAGS} from '../logFlags'; -import {ApplicationContext} from "context"; +import {CoreContext} from "context"; import {IconType} from "react-icons"; -export function activate(context: ApplicationContext) { +export function activate(context: CoreContext) { let {streams} = context; @@ -157,7 +157,7 @@ export interface ActionService { } declare module 'context' { - interface ApplicationContext { + interface CoreContext { actionService: ActionService; } diff --git a/web/app/cad/actions/coreActions.js b/web/app/cad/actions/coreActions.js index 4f067226..8ea7c787 100644 --- a/web/app/cad/actions/coreActions.js +++ b/web/app/cad/actions/coreActions.js @@ -34,7 +34,7 @@ export default [ label: 'save', info: 'save project to storage', }, - invoke: (context) => context.services.project.save() + invoke: (context) => context.projectService.save() }, { @@ -104,7 +104,7 @@ export default [ label: 'Clone Project...', info: 'clone current project and open in a new tab', }, - invoke: (context) => context.services.projectManager.cloneProject(context.services.project.id) + invoke: (context) => context.services.projectManager.cloneProject(context.projectService.id) }, { diff --git a/web/app/cad/craft/cadRegistryPlugin.ts b/web/app/cad/craft/cadRegistryPlugin.ts index 7d48890c..76e250ef 100644 --- a/web/app/cad/craft/cadRegistryPlugin.ts +++ b/web/app/cad/craft/cadRegistryPlugin.ts @@ -1,105 +1,60 @@ -import {DATUM, DATUM_AXIS, EDGE, FACE, LOOP, SHELL, SKETCH_OBJECT} from '../scene/entites'; import {MShell} from '../model/mshell'; -import {Emitter} from "lstream"; -import {ShowDialogRequest} from "ui/showDialogRequest"; -import {CatalogCategory, CatalogPart, PartsCatalog} from "../partImport/partImportPlugin"; import {MObject} from "../model/mobject"; +import {ApplicationContext} from "context"; -export function activate(ctx) { +export function activate(ctx: ApplicationContext) { const {streams, services} = ctx; + const shells$ = streams.craft.models.map(models => models.filter(m => m instanceof MShell)).remember(); + const modelIndex$ = streams.craft.models.map(models => { + const index = new Map(); + models.forEach(model => model.traverse(m => index.set(m.id, m))); + return index; + }).remember(); + streams.cadRegistry = { - shells: streams.craft.models.map(models => models.filter(m => m instanceof MShell)).remember(), - modelIndex: streams.craft.models.map(models => models.reduce((i, v)=> i.set(v.id, v), new Map())).remember() + shells: shells$, modelIndex: modelIndex$ }; streams.cadRegistry.update = streams.cadRegistry.modelIndex; - + + const index = () => modelIndex$.value; + function getAllShells() { return streams.cadRegistry.shells.value; } function findShell(shellId) { - let shells = getAllShells(); - for (let shell of shells) { - if (shell.id === shellId) { - return shell; - } - } - return null; + return index().get(shellId); } function findFace(faceId) { - let shells = getAllShells(); - for (let shell of shells) { - for (let face of shell.faces) { - if (face.id === faceId) { - return face; - } - } - } - return null; + return index().get(faceId); } function findEdge(edgeId) { - let shells = getAllShells(); - for (let shell of shells) { - for (let edge of shell.edges) { - if (edge.id === edgeId) { - return edge; - } - } - } - return null; + return index().get(edgeId); } function findSketchObject(sketchObjectGlobalId) { - let [shellId, faceId, sketchObjectId] = sketchObjectGlobalId.split('/'); - let face = findFace(shellId+'/'+faceId); - if (face) { - return face.findSketchObjectById(sketchObjectGlobalId); - } - return null; + return index().get(sketchObjectGlobalId); } function findDatum(datumId) { - return streams.cadRegistry.modelIndex.value.get(datumId)||null; + return index().get(datumId); } function findDatumAxis(datumAxisId) { - const [datumId, axisLiteral] = datumAxisId.split('/'); - let datum = streams.cadRegistry.modelIndex.value.get(datumId); - if (!datum) { - return null; - } - return datum.getAxisByLiteral(axisLiteral); + return index().get(datumAxisId); } function findLoop(loopId) { - let [shellId, faceId, loopLocalId] = loopId.split('/'); - let face = findFace(shellId+'/'+faceId); - if (face) { - for (let loop of face.sketchLoops) { - if (loop.id === loopId) { - return loop; - } - } - } - return null; + return index().get(loopId); } function findEntity(entity, id) { - switch (entity) { - case FACE: return findFace(id); - case SHELL: return findShell(id); - case EDGE: return findEdge(id); - case SKETCH_OBJECT: return findSketchObject(id); - case DATUM: return findDatum(id); - case DATUM_AXIS: return findDatumAxis(id); - case LOOP: return findLoop(id); - default: throw 'unsupported'; - } + return index().get(id); } services.cadRegistry = { diff --git a/web/app/cad/craft/craftPlugin.js b/web/app/cad/craft/craftPlugin.js deleted file mode 100644 index 8775c198..00000000 --- a/web/app/cad/craft/craftPlugin.js +++ /dev/null @@ -1,183 +0,0 @@ -import {addModification, stepOverriding} from './craftHistoryUtils'; -import {state, stream} from 'lstream'; -import materializeParams from './materializeParams'; -import CadError from '../../utils/errors'; -import {MObjectIdGenerator} from '../model/mobject'; -import {intercept} from "../../../../modules/lstream/intercept"; - -export function activate(ctx) { - const {streams, services} = ctx; - streams.craft = { - - modifications: state({ - history: [], - pointer: -1 - }), - - models: state([]), - update: stream() - }; - - let preRun = null; - - function modifyWithPreRun(request, modificationsUpdater, onAccepted, onError) { - - runRequest(request).then(result => { - onAccepted(); - preRun = { - request, - result - }; - modificationsUpdater(request); - }).catch(onError); - } - - function modify(request, onAccepted, onError) { - modifyWithPreRun(request, - request => streams.craft.modifications.update(modifications => addModification(modifications, request)), onAccepted, onError); - } - - function modifyInHistoryAndStep(request, onAccepted, onError) { - modifyWithPreRun(request, - request => streams.craft.modifications.update(modifications => stepOverriding(modifications, request)), onAccepted, onError); - } - - function reset(modifications) { - streams.craft.modifications.next({ - history: modifications, - pointer: modifications.length - 1 - }); - } - - function rebuild() { - const mods = streams.craft.modifications.value; - reset([]); - streams.craft.modifications.next(mods); - } - - function runRequest(request) { - let op = services.operation.get(request.type); - if (!op) { - return Promise.reject(new Error(`unknown operation ${request.type}`)); - } - - let params = {}; - let errors = []; - materializeParams(services, request.params, op.schema, params, errors); - if (errors.length) { - return Promise.reject(new CadError({ - kind: CadError.KIND.INVALID_PARAMS, - userMessage: errors.map(err => `${err.path.join('.')}: ${err.message}`).join('\n') - })); - } - - const result = op.run(params, ctx); - return result.then ? result : Promise.resolve(result); - } - - function runOrGetPreRunResults(request) { - if (preRun !== null && preRun.request === request) { - const result = preRun.result; - preRun = null; - return Promise.resolve(result); - } else { - return runRequest(request); - } - } - - services.craft = { - modify, modifyInHistoryAndStep, reset, runRequest, rebuild, - historyTravel: historyTravel(streams.craft.modifications) - }; - - let locked = false; - intercept(streams.craft.modifications, (curr, stream, next) => { - const prev = stream.value; - if (locked) { - console.error('concurrent modification'); - } - locked = true; - let models; - let beginIndex; - if (isAdditiveChange(prev, curr)) { - beginIndex = prev.pointer + 1; - } else { - MObjectIdGenerator.reset(); - beginIndex = 0; - streams.craft.models.next([]); - } - - models = new Set(streams.craft.models.value); - let {history, pointer} = curr; - - function runPromise(i) { - if (i > pointer) { - locked = false; - next(curr); - return; - } - - let request = history[i]; - const promise = runOrGetPreRunResults(request) - promise.then(({consumed, created}) => { - - consumed.forEach(m => models.delete(m)); - created.forEach(m => models.add(m)); - streams.craft.models.next(Array.from(models).sort(m => m.id)); - - runPromise(i + 1); - }).catch(e => { - locked = false; - console.error(e); - //TODO: need to find a way to propagate the error to the wizard. - next({ - ...curr, - pointer: i-1 - }); - }) - } - runPromise(beginIndex); - }) -} - -function isAdditiveChange({history:oldHistory, pointer:oldPointer}, {history, pointer}) { - 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; -} - -function historyTravel(modifications$) { - - return { - setPointer: function(pointer, hints) { - let mod = modifications$.value; - if (pointer >= mod.history.length || pointer < -1) { - return; - } - modifications$.update(({history}) => ({history, pointer, hints})); - }, - begin: function(hints) { - this.setPointer(-1, hints); - }, - end: function(hints) { - this.setPointer(modifications$.value.history.length - 1, hints); - }, - forward: function(hints) { - this.setPointer(modifications$.value.pointer + 1, hints); - }, - backward: function (hints) { - this.setPointer(modifications$.value.pointer - 1, hints); - }, - } - -} - diff --git a/web/app/cad/craft/craftPlugin.ts b/web/app/cad/craft/craftPlugin.ts new file mode 100644 index 00000000..a1c0c42c --- /dev/null +++ b/web/app/cad/craft/craftPlugin.ts @@ -0,0 +1,263 @@ +import {addModification, stepOverriding} from './craftHistoryUtils'; +import {Emitter, state, StateStream, stream} from 'lstream'; +import materializeParams from './materializeParams'; +import CadError from '../../utils/errors'; +import {MObject, MObjectIdGenerator} from '../model/mobject'; +import {intercept} from "lstream/intercept"; +import {CoreContext} from "context"; + +export function activate(ctx: CoreContext) { + + + const modifications$ = state({ + history: [], + pointer: -1 + }); + + const models$ = state([]); + const update$ = stream(); + + let preRun = null; + + function modifyWithPreRun(request, modificationsUpdater, onAccepted, onError) { + + runRequest(request).then(result => { + onAccepted(); + preRun = { + request, + result + }; + modificationsUpdater(request); + }).catch(error => { + console.error(error); + onError(error); + }); + } + + function modify(request, onAccepted, onError) { + modifyWithPreRun(request, + request => modifications$.update(modifications => addModification(modifications, request)), onAccepted, onError); + } + + function modifyInHistoryAndStep(request, onAccepted, onError) { + modifyWithPreRun(request, + request => modifications$.update(modifications => stepOverriding(modifications, request)), onAccepted, onError); + } + + function reset(modifications: OperationRequest[]) { + modifications$.next({ + history: modifications, + pointer: modifications.length - 1 + }); + } + + function rebuild() { + const mods = modifications$.value; + reset([]); + modifications$.next(mods); + } + + function runRequest(request): Promise { + let op = ctx.operationService.get(request.type); + if (!op) { + return Promise.reject(new Error(`unknown operation ${request.type}`)); + } + + let params = {}; + let errors = []; + materializeParams(ctx, request.params, op.schema, params, errors); + if (errors.length) { + return Promise.reject(new CadError({ + kind: CadError.KIND.INVALID_PARAMS, + userMessage: errors.map(err => `${err.path.join('.')}: ${err.message}`).join('\n') + })); + } + + const result = op.run(params, ctx); + // @ts-ignore + return result.then ? result : Promise.resolve(result); + } + + function runOrGetPreRunResults(request) { + if (preRun !== null && preRun.request === request) { + const result = preRun.result; + preRun = null; + return Promise.resolve(result); + } else { + return runRequest(request); + } + } + + ctx.craftService = { + modify, modifyInHistoryAndStep, reset, rebuild, runRequest, runPipeline, + historyTravel: historyTravel(modifications$), + modifications$, models$, update$ + }; + + // @ts-ignore + ctx.services.craft = ctx.craftService; + // @ts-ignore + ctx.streams.craft = { + modifications: modifications$, + models: models$, + update: update$ + }; + + function runPipeline(history: OperationRequest[], beginIndex: number, endIndex: number): Promise { + + const models: Set = new Set(models$.value); + + return new Promise((resolve, reject) => { + function runPromise(i) { + if (i > endIndex) { + resolve(); + return; + } + + const request = history[i]; + const promise = runOrGetPreRunResults(request); + promise.then(({consumed, created}) => { + + consumed.forEach(m => models.delete(m)); + created.forEach(m => models.add(m)); + models$.next(Array.from(models).sort((m1, m2) => (m1.id||'').localeCompare(m2.id))); + + runPromise(i + 1); + }).catch(error => { + reject({ + failIndex: i, + error + }); + }) + } + runPromise(beginIndex); + }); + } + + let locked = false; + + intercept(modifications$, (curr, stream, next) => { + if (locked) { + console.error('[CRAFT] concurrent modification'); + } + locked = true; + + const prev = stream.value; + let beginIndex; + if (isAdditiveChange(prev, curr)) { + beginIndex = prev.pointer + 1; + } else { + MObjectIdGenerator.reset(); + beginIndex = 0; + models$.next([]); + } + + let {history, pointer} = curr; + + runPipeline(history, beginIndex, pointer) + .then(() => next(curr)) + .finally(() => locked = false) + .catch(reason => { + console.error(reason.error); + //TODO: need to find a way to propagate the error to the wizard. + next({ + ...curr, + pointer: reason.failIndex + }); + }); + }) +} + +function isAdditiveChange({history:oldHistory, pointer:oldPointer}, {history, pointer}) { + 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; +} + +function historyTravel(modifications$) { + + return { + setPointer: function(pointer, hints) { + let mod = modifications$.value; + if (pointer >= mod.history.length || pointer < -1) { + return; + } + modifications$.update(({history}) => ({history, pointer, hints})); + }, + begin: function(hints) { + this.setPointer(-1, hints); + }, + end: function(hints) { + this.setPointer(modifications$.value.history.length - 1, hints); + }, + forward: function(hints) { + this.setPointer(modifications$.value.pointer + 1, hints); + }, + backward: function (hints) { + this.setPointer(modifications$.value.pointer - 1, hints); + }, + } + +} + +export interface OperationRequest { + type: string; + params: any; +} + +export interface OperationResult { + + consumed: MObject[]; + created: MObject[]; + +} + +interface CraftHistory { + history: OperationRequest[]; + pointer: number; +} + +interface CraftService { + + modifications$: StateStream; + models$: StateStream; + update$: Emitter; + + modify(request: OperationRequest, onAccepted: () => void, onError: () => Error); + + modifyInHistoryAndStep(request: OperationRequest, onAccepted: () => void, onError: () => Error); + + reset(modifications: OperationRequest[]); + + rebuild(): void; + + historyTravel: HistoryTravel; + + runRequest(request: OperationRequest): Promise; + + runPipeline(history: OperationRequest[], beginIndex: number, endIndex: number): Promise +} + +interface HistoryTravel { + setPointer(pointer, hints:any); + begin(hints: any); + end(hints: any); + forward(hints: any); + backward(hints: any); +} + +declare module 'context' { + interface CoreContext { + + craftService: CraftService; + } +} diff --git a/web/app/cad/craft/cutExtrude/cutExtrude.js b/web/app/cad/craft/cutExtrude/cutExtrude.js index 49804ff3..095cf473 100644 --- a/web/app/cad/craft/cutExtrude/cutExtrude.js +++ b/web/app/cad/craft/cutExtrude/cutExtrude.js @@ -1,4 +1,4 @@ -import {Matrix3, ORIGIN} from '../../../math/l3space' +import {Matrix3} from '../../../math/l3space' import * as math from '../../../math/math' import {enclose} from '../../../brep/brep-enclose' import {BooleanOperation, combineShells} from '../booleanOperation' @@ -12,12 +12,13 @@ export function Cut(params, ctx) { return doOperation(params, ctx, true); } -export function doOperation(params, {cadRegistry, sketcherService}, cut) { +export function doOperation(params, ctx, cut) { + const {cadRegistry, sketchStorageService} = ctx; const face = cadRegistry.findFace(params.face); const solid = face.solid; - let sketch = sketcherService.readSketch(face.id); - if (!sketch) throw 'illegal state'; + let sketch = sketchStorageService.readSketch(face.id); + if (!sketch) throw 'sketch not found for the face ' + face.id; let vector = resolveExtrudeVector(cadRegistry, face, params, !cut); const details = getEncloseDetails(params, sketch.fetchContours(), vector, face.csys, face.surface, !cut, false); diff --git a/web/app/cad/craft/e0/common.js b/web/app/cad/craft/e0/common.js index bd2d031a..886bbac1 100644 --- a/web/app/cad/craft/e0/common.js +++ b/web/app/cad/craft/e0/common.js @@ -77,9 +77,9 @@ export function readSketchContour(contour, face) { return path; } -export function readSketch(face, request, sketcher) { - let sketch = sketcher.readSketch(face.id); - if (!sketch) throw 'illegal state'; +export function readSketch(face, request, sketchStorageService) { + let sketch = sketchStorageService.readSketch(face.id); + if (!sketch) throw 'sketch not found for the face ' + face.id; return sketch.fetchContours().map(c => readSketchContour(c, face)); } diff --git a/web/app/cad/craft/e0/craftMethods.js b/web/app/cad/craft/e0/craftMethods.js index bdd4abe5..7d923a86 100644 --- a/web/app/cad/craft/e0/craftMethods.js +++ b/web/app/cad/craft/e0/craftMethods.js @@ -91,9 +91,9 @@ function booleanBasedOperation(engineParams, params, impl) { function cutExtrude(isCut, request) { - function createExtrudeCommand(request, {cadRegistry, sketcherService}, invert) { + function createExtrudeCommand(request, {cadRegistry, sketchStorageService}, invert) { const face = cadRegistry.findFace(request.face); - const paths = readSketch(face, request, sketcherService); + const paths = readSketch(face, request, sketchStorageService); return { face, diff --git a/web/app/cad/craft/e0/operationHandler.js b/web/app/cad/craft/e0/operationHandler.js index a0f03822..8162d4d9 100644 --- a/web/app/cad/craft/e0/operationHandler.js +++ b/web/app/cad/craft/e0/operationHandler.js @@ -41,9 +41,9 @@ export default function operationHandler(id, request, services) { } } -function createExtrudeCommand(request, {cadRegistry, sketcherService}, invert) { +function createExtrudeCommand(request, {cadRegistry, sketchStorageService}, invert) { const face = cadRegistry.findFace(request.face); - const paths = readSketch(face, request, sketcherService); + const paths = readSketch(face, request, sketchStorageService); return { face, @@ -56,9 +56,9 @@ function createExtrudeCommand(request, {cadRegistry, sketcherService}, invert) { }; } -function createRevolveCommand(request, {cadRegistry, sketcherService}) { +function createRevolveCommand(request, {cadRegistry, sketchStorageService}) { const face = cadRegistry.findFace(request.face); - const paths = readSketch(face, request, sketcherService); + const paths = readSketch(face, request, sketchStorageService); let pivot = cadRegistry.findSketchObject(request.axis).sketchPrimitive; let tr = face.csys.outTransformation; diff --git a/web/app/cad/craft/materializeParams.js b/web/app/cad/craft/materializeParams.js index d4e5e473..2a64ac36 100644 --- a/web/app/cad/craft/materializeParams.js +++ b/web/app/cad/craft/materializeParams.js @@ -1,6 +1,6 @@ import {isEntityType} from './schemaUtils'; -export default function materializeParams(services, params, schema, result, errors, parentPath) { +export default function materializeParams(ctx, params, schema, result, errors, parentPath) { parentPath = parentPath || ROOT_PATH; @@ -19,7 +19,7 @@ export default function materializeParams(services, params, schema, result, erro try { const valueType = typeof value; if (valueType === 'string') { - value = services.expressions.evaluateExpression(value); + value = ctx.expressionService.evaluateExpression(value); } else if (valueType !== 'number') { errors.push({path: [...parentPath, field], message: 'invalid value'}); } @@ -55,7 +55,7 @@ export default function materializeParams(services, params, schema, result, erro if (!ref && !md.optional) { errors.push({path: [...parentPath, field], message: 'required'}); } - let model = services.cadRegistry.findEntity(md.type, ref); + let model = ctx.cadRegistry.findEntity(md.type, ref); if (!model) { errors.push({path: [...parentPath, field], message: 'referrers to nonexistent ' + md.type}); } @@ -73,13 +73,13 @@ export default function materializeParams(services, params, schema, result, erro if (md.itemType === 'object') { value = value.map((item , i) => { let itemResult = {}; - materializeParams(services, item, md.schema, itemResult, errors, [...parentPath, i]); + materializeParams(ctx, item, md.schema, itemResult, errors, [...parentPath, i]); return itemResult; }); } else { if (isEntityType(md.itemType)) { value.forEach(ref => { - if (!services.cadRegistry.findEntity(md.itemType, ref)) { + if (!ctx.cadRegistry.findEntity(md.itemType, ref)) { errors.push({path: [...parentPath, field], message: 'referrers to nonexistent ' + md.itemType}); } }) diff --git a/web/app/cad/craft/operationPlugin.ts b/web/app/cad/craft/operationPlugin.ts index 923b37d4..bd16ba08 100644 --- a/web/app/cad/craft/operationPlugin.ts +++ b/web/app/cad/craft/operationPlugin.ts @@ -3,18 +3,17 @@ import {state} from 'lstream'; import {IconType} from "react-icons"; import {isEntityType} from './schemaUtils'; import {ActionAppearance} from "../actions/actionSystemPlugin"; -import {MObject} from "../model/mobject"; -import {ApplicationContext} from "context"; +import {ApplicationContext, CoreContext} from "context"; +import {OperationResult} from "./craftPlugin"; -export function activate(context) { - let {services} = context; +export function activate(ctx: ApplicationContext) { - context.streams.operation = { - registry: state({}) + const registry$ = state({}); + + ctx.streams.operation = { + registry:registry$ }; - let registry$ = context.streams.operation.registry; - function addOperation(descriptor, actions) { let {id, label, info, icon, actionParams} = descriptor; let appearance: ActionAppearance = { @@ -30,7 +29,7 @@ export function activate(context) { let opAction = { id: id, appearance, - invoke: () => services.wizard.open(id), + invoke: () => ctx.services.wizard.open(id), ...actionParams }; actions.push(opAction); @@ -47,10 +46,10 @@ export function activate(context) { for (let op of operations) { addOperation(op, actions); } - services.action.registerActions(actions); + ctx.actionService.registerActions(actions); } - function get(id) { + function get(id: string): Operation { let op = registry$.value[id]; if (!op) { throw `operation ${id} is not registered`; @@ -70,13 +69,13 @@ export function activate(context) { return descriptor.run(request, opContext); } - services.operation = { + ctx.operationService = { registerOperations, get, handlers }; - context.operationService = services.operation; + ctx.services.operation = ctx.operationService; } export interface Operation extends OperationDescriptor{ @@ -128,7 +127,7 @@ export interface OperationDescriptor { info: string; icon: IconType | string; actionParams?: any; - run: (request: R, opContext: OperationContext) => OperationResult; + run: (request: R, opContext: CoreContext) => OperationResult | Promise; paramsInfo: (params: R) => string, previewGeomProvider: (params: R) => OperationGeometryProvider, form: () => React.ReactNode, @@ -137,11 +136,11 @@ export interface OperationDescriptor { export interface OperationService { registerOperations(descriptior: OperationDescriptor[]); - get(operationId: string): Operation; + get(operationId: string): Operation; handlers: (( id: string, request: any, - opContext: OperationContext + opContext: CoreContext ) => void)[] } @@ -149,19 +148,8 @@ export interface OperationGeometryProvider { } -export interface OperationResult { - - consumed: MObject[]; - created: MObject[]; - -} - -export interface OperationContext { - cadRegistry: any -} - declare module 'context' { - interface ApplicationContext { + interface CoreContext { operationService: OperationService; } diff --git a/web/app/cad/craft/ui/HistoryTimeline.less b/web/app/cad/craft/ui/HistoryTimeline.less index 5feef31e..6eb4cc18 100644 --- a/web/app/cad/craft/ui/HistoryTimeline.less +++ b/web/app/cad/craft/ui/HistoryTimeline.less @@ -28,7 +28,7 @@ align-items: flex-end; height: 100%; overflow-x: hidden; - padding: 0 5px; + padding: 0 5px 0 10px; } .scroller { padding: 0 5px; diff --git a/web/app/cad/craft/wizard/components/Wizard.jsx b/web/app/cad/craft/wizard/components/Wizard.jsx index a3736984..40896ffa 100644 --- a/web/app/cad/craft/wizard/components/Wizard.jsx +++ b/web/app/cad/craft/wizard/components/Wizard.jsx @@ -57,14 +57,14 @@ export default class Wizard extends React.Component { const error = this.props.error; return + initLeft={left || 15} + title={title} + onClose={this.cancel} + onKeyDown={this.onKeyDown} + setFocus={this.focusFirstInput} + className='Wizard mid-typography' + data-operation-id={operation.id} + controlButtons={<> DocumentationTopic$.next({ topic: operation.id, x: e.pageX + 40, @@ -88,6 +88,7 @@ export default class Wizard extends React.Component { } {error.code &&
{error.code}
} {error.userMessage &&
{error.userMessage}
} + {!error.userMessage &&
internal error processing operation, check the log
} }
; diff --git a/web/app/cad/craft/wizard/wizardPlugin.js b/web/app/cad/craft/wizard/wizardPlugin.js index 0b853a38..d6296522 100644 --- a/web/app/cad/craft/wizard/wizardPlugin.js +++ b/web/app/cad/craft/wizard/wizardPlugin.js @@ -75,7 +75,7 @@ export function activate(ctx) { let materializedWorkingRequest$ = workingRequest$.map(req => { let params = {}; let errors = []; - materializeParams(ctx.services, req.params, operation.schema, params, errors, []); + materializeParams(ctx, req.params, operation.schema, params, errors, []); if (errors.length !== 0) { return INVALID_REQUEST; } diff --git a/web/app/cad/debugPlugin.js b/web/app/cad/debugPlugin.js index 7aa0cd4a..9dfe27ff 100644 --- a/web/app/cad/debugPlugin.js +++ b/web/app/cad/debugPlugin.js @@ -388,12 +388,12 @@ const DebugActions = [ label: 'generate unit test', info: 'it will generate a unit code code containing sketches and operation sequence and output it to terminal', }, - invoke: ({bus, services: {project, storage, sketcher, cadRegistry}}) => { + invoke: ({bus, services: {project, storage, sketchStorageService, cadRegistry}}) => { const pt = ({x, y}) => [x, y]; - let sketches = sketcher.getAllSketches().reduce((sketches, {id, url}) => { - let sketch = sketcher.readSketch(id).getAllObjects().reduce((byType, obj) => { + let sketches = sketchStorageService.getAllSketches().reduce((sketches, {id, url}) => { + let sketch = sketchStorageService.readSketch(id).getAllObjects().reduce((byType, obj) => { let type = obj.constructor.name; diff --git a/web/app/cad/dom/components/ContributedComponents.jsx b/web/app/cad/dom/components/ContributedComponents.jsx index 8222832a..0d67c525 100644 --- a/web/app/cad/dom/components/ContributedComponents.jsx +++ b/web/app/cad/dom/components/ContributedComponents.jsx @@ -7,7 +7,7 @@ const CONTRIBUTED_COMPONENTS$ = state([]); export function ContributedComponents() { const contrib = useStream(CONTRIBUTED_COMPONENTS$); - return contrib.map((Comp, i) => ); + return contrib.map((Comp, i) => ); } export function contributeComponent(comp) { diff --git a/web/app/cad/dom/components/FloatView.jsx b/web/app/cad/dom/components/FloatView.jsx index 74fa766c..5fc658db 100644 --- a/web/app/cad/dom/components/FloatView.jsx +++ b/web/app/cad/dom/components/FloatView.jsx @@ -9,7 +9,7 @@ import ToolButton from 'ui/components/ToolButton'; @connect(state => state.ui.floatViews.map(views => ({views}))) @mapContext(ctx => ({ getDescriptor: ctx.services.ui.getFloatView, - initialView: ctx.services.project.hints.FloatView || null + initialView: ctx.projectService.hints.FloatView || null })) export default class FloatView extends React.Component { diff --git a/web/app/cad/exportPlugin.js b/web/app/cad/exportPlugin.js index 08052260..d5066f5a 100644 --- a/web/app/cad/exportPlugin.js +++ b/web/app/cad/exportPlugin.js @@ -9,7 +9,7 @@ export function activate(ctx) { } function stlAscii() { - exportTextData(toStlAsciiString(), ctx.services.project.id + ".stl"); + exportTextData(toStlAsciiString(), ctx.projectService.id + ".stl"); } function imagePng() { @@ -23,7 +23,7 @@ export function activate(ctx) { let link = document.getElementById("downloader"); link.href = renderer.domElement.toDataURL('image/png'); - link.download = ctx.services.project.id + "-snapshot.png"; + link.download = ctx.projectService.id + "-snapshot.png"; link.click(); renderer.preserveDrawingBuffer = false; @@ -34,7 +34,7 @@ export function activate(ctx) { } function nativeFormat() { - ctx.services.projectManager.exportProject(ctx.services.project.id); + ctx.services.projectManager.exportProject(ctx.projectService.id); } ctx.services.export = { diff --git a/web/app/cad/expressions/Expressions.jsx b/web/app/cad/expressions/Expressions.jsx deleted file mode 100644 index 1afe7bbf..00000000 --- a/web/app/cad/expressions/Expressions.jsx +++ /dev/null @@ -1,92 +0,0 @@ -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' &&