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