mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-10 10:25:36 +01:00
history timeline widget
This commit is contained in:
parent
a4ef761ffe
commit
b8053c5e25
44 changed files with 736 additions and 119 deletions
1
modules/gems/objects.js
Normal file
1
modules/gems/objects.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const EMPTY_OBJECT = Object.freeze({});
|
||||
|
|
@ -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');
|
||||
|
|
|
|||
14
modules/lstream/scan.js
Normal file
14
modules/lstream/scan.js
Normal file
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <AdjustableAbs className={cx(ls.root, flatTop && ls.flatTop, flatBottom && ls.flatBottom, className)} {...props}>
|
||||
export default function AuxWidget({flatTop, flatBottom, flatRight, flatLeft, children, className, ...props}) {
|
||||
return <AdjustableAbs className={cx(ls.root,
|
||||
flatTop && ls.flatTop, flatBottom && ls.flatBottom,
|
||||
flatRight && ls.flatRight, flatLeft && ls.flatLeft,
|
||||
className)} {...props}>
|
||||
{children}
|
||||
</AdjustableAbs>
|
||||
</AdjustableAbs>;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
30
modules/ui/components/RenderObject.jsx
Normal file
30
modules/ui/components/RenderObject.jsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
import ls from './RenderObject.less';
|
||||
|
||||
export default function RenderObject({object}) {
|
||||
return <div className={ls.root}><RenderObjectImpl object={object}/></div>
|
||||
}
|
||||
|
||||
function RenderObjectImpl({object, inner}) {
|
||||
if (object === undefined || object === null) {
|
||||
return <span>{object}</span>;
|
||||
}
|
||||
if (typeof object === 'object') {
|
||||
return <div style={{marginLeft: inner?10:0}}>
|
||||
{Object.keys(object).map(field => <div key={field}>
|
||||
{field}: <RenderObjectImpl object={object[field]} inner/>
|
||||
</div>)}
|
||||
</div>;
|
||||
} else if (Array.isArray(object)) {
|
||||
return <div style={{marginLeft: inner?10:0}}>
|
||||
{Object.map(object).map((item, i) => <div key={i}>
|
||||
<div><RenderObject object={object[field]} inner/></div>
|
||||
</div>)}
|
||||
{Object.keys(object).map(field => <div key={field}>
|
||||
{field}: <RenderObject object={object[field]} inner/>
|
||||
</div>)}
|
||||
</div>;
|
||||
} else {
|
||||
return <span>{object}</span>;
|
||||
}
|
||||
}
|
||||
3
modules/ui/components/RenderObject.less
Normal file
3
modules/ui/components/RenderObject.less
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.root {
|
||||
line-height: 1.5;
|
||||
}
|
||||
24
modules/ui/components/Widget.jsx
Normal file
24
modules/ui/components/Widget.jsx
Normal file
|
|
@ -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 <AdjustableAbs className={cx(ls.root,
|
||||
flatTop && ls.flatTop, flatBottom && ls.flatBottom,
|
||||
flatRight && ls.flatRight, flatLeft && ls.flatLeft,
|
||||
className)} {...props}>
|
||||
<div className={ls.header}>
|
||||
<div className={ls.title}>{title}</div>
|
||||
<span className={ls.headerButtons}>
|
||||
<SymbolButton type='danger' onClick={onClose}><Fa fw icon='close'/></SymbolButton>
|
||||
</span>
|
||||
</div>
|
||||
<div className={ls.content}>
|
||||
{children}
|
||||
</div>
|
||||
</AdjustableAbs>;
|
||||
}
|
||||
50
modules/ui/components/Widget.less
Normal file
50
modules/ui/components/Widget.less
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 <button onClick={onClick} className={ls[type]}>{children}</button>
|
||||
return <button onClick={onClick} className={cx(ls[type], ls.button)}>{children}</button>
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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%);
|
||||
}
|
||||
|
|
|
|||
15
modules/ui/components/controls/SymbolButton.jsx
Normal file
15
modules/ui/components/controls/SymbolButton.jsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
|
||||
import ls from './SymbolButton.less';
|
||||
|
||||
export default function SymbolButton({type, onClick, children}) {
|
||||
return <div className={ls[type]} onClick={onClick}>{children}</div>
|
||||
}
|
||||
|
||||
SymbolButton.defaultProps = {
|
||||
type: 'neutral',
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
30
modules/ui/components/controls/SymbolButton.less
Normal file
30
modules/ui/components/controls/SymbolButton.less
Normal file
|
|
@ -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%);
|
||||
}
|
||||
}
|
||||
20
modules/ui/components/flatEdges.less
Normal file
20
modules/ui/components/flatEdges.less
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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 <Component {...this.streamProps}
|
||||
{...this.props} />;
|
||||
|
||||
}
|
||||
|
||||
componentDidCatch() {
|
||||
this.setState({hasError: true});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <Comp {...this.props} />;
|
||||
}
|
||||
}
|
||||
return ErrorBoundary;
|
||||
}
|
||||
}
|
||||
7
modules/ui/positionUtils.js
Normal file
7
modules/ui/positionUtils.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export function aboveElement(el) {
|
||||
let r = el.getBoundingClientRect();
|
||||
return {
|
||||
x: r.left,
|
||||
y: r.top
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
22
package-lock.json
generated
22
package-lock.json
generated
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
|||
9
web/app/cad/craft/craftUiPlugin.js
Normal file
9
web/app/cad/craft/craftUiPlugin.js
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
126
web/app/cad/craft/ui/HistoryTimeline.jsx
Normal file
126
web/app/cad/craft/ui/HistoryTimeline.jsx
Normal file
|
|
@ -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 <div className={ls.root}>
|
||||
<Controls pointer={pointer} eoh={history.length-1} setHistoryPointer={setHistoryPointer}/>
|
||||
{history.map((m, i) => <React.Fragment key={i}>
|
||||
<Timesplitter active={i-1 === pointer} onClick={() => setHistoryPointer(i-1)} />
|
||||
<HistoryItem index={i} modification={m} getOperation={getOperation}
|
||||
disabled={pointer < i}
|
||||
inProgress={pointer === i-1} />
|
||||
</React.Fragment>)}
|
||||
<Timesplitter eoh active={history.length-1 === pointer} onClick={() => setHistoryPointer(history.length-1)}/>
|
||||
<InProgressOperation getOperation={getOperation}/>
|
||||
<AddButton />
|
||||
</div>;
|
||||
}
|
||||
|
||||
const InProgressOperation = connect(streams => streams.wizard.map(wizard => ({wizard})))(
|
||||
function InProgressOperation({wizard, getOperation}) {
|
||||
if (!wizard) {
|
||||
return null;
|
||||
}
|
||||
let {appearance} = getOperation(wizard.type);
|
||||
return <div className={ls.inProgressItem}>
|
||||
<ImgIcon url={appearance&&appearance.icon96} size={24} />
|
||||
</div>;
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
function Timesplitter({active, eoh, onClick}) {
|
||||
|
||||
return <div className={cx(ls.timesplitter, active&&ls.active, eoh&&ls.eoh)} >
|
||||
<div className={ls.handle} onClick={onClick}>
|
||||
<Handle />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function Handle() {
|
||||
const w = 12;
|
||||
const h = 15;
|
||||
const m = Math.round(w * 0.5);
|
||||
const t = Math.round(h * 0.5);
|
||||
return <svg xmlns="http://www.w3.org/2000/svg" height={h} width={w} >
|
||||
<polygon className={ls.handlePoly} points={`0,0 ${w},0 ${w},${t} ${m},${h} 0,${t}`}
|
||||
style={{strokeWidth:0.5}} />
|
||||
</svg>;
|
||||
}
|
||||
|
||||
function Controls({pointer, eoh, setHistoryPointer}) {
|
||||
const noB = pointer===-1;
|
||||
const noF = pointer===eoh;
|
||||
return <React.Fragment>
|
||||
<div className={cx(ls.controlBtn, noB&&ls.disabled)} onClick={noB?undefined :() => setHistoryPointer(pointer-1)}>
|
||||
<Fa icon='step-backward' fw/>
|
||||
</div>
|
||||
<div className={cx(ls.controlBtn, noF&&ls.disabled)} onClick={noF?undefined :() => setHistoryPointer(pointer+1)}>
|
||||
<Fa icon='step-forward' fw/>
|
||||
</div>
|
||||
<div className={cx(ls.controlBtn, noF&&ls.disabled)} onClick={noF?undefined :() => setHistoryPointer(eoh)}>
|
||||
<Fa icon='fast-forward' fw/>
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
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 <div className={cx(ls.historyItem, selected&&ls.selected, disabled&&ls.disabled, inProgress&&ls.inProgress)}
|
||||
onClick={e => toggle(index, modification, e.currentTarget)}>
|
||||
<ImgIcon className={ls.opIcon} url={appearance&&appearance.icon96} size={24} />
|
||||
<span className={ls.opIndex}>{ index + 1 }</span>
|
||||
</div>;
|
||||
});
|
||||
|
||||
const AddButton = mapContext(({services}) => ({
|
||||
showCraftMenu: e => services.action.run('menu.craft', menuAboveElementHint(e.currentTarget))
|
||||
}))(
|
||||
function AddButton({showCraftMenu}) {
|
||||
return <div className={ls.add} onClick={showCraftMenu}>
|
||||
<Fa icon='plus' fw/>
|
||||
</div>;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
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);
|
||||
|
||||
|
||||
|
||||
143
web/app/cad/craft/ui/HistoryTimeline.less
Normal file
143
web/app/cad/craft/ui/HistoryTimeline.less
Normal file
|
|
@ -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;}
|
||||
}
|
||||
|
|
@ -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}) {
|
||||
|
|
@ -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 <Stack>
|
||||
|
||||
{history.map(({type, params}, index) => {
|
||||
|
||||
let {appearance, paramsInfo} = getDescriptor(type, operationRegistry);
|
||||
let {appearance, paramsInfo} = getOperation(type)||EMPTY_OBJECT;
|
||||
return <div key={index} onClick={() => setHistoryPointer(index - 1)}
|
||||
className={cx(ls.item, pointer + 1 === index && ls.selected)}>
|
||||
{appearance && <ImgIcon url={appearance.icon32} size={16}/>}
|
||||
|
|
@ -35,21 +36,12 @@ function OperationHistory({history, pointer, setHistoryPointer, remove, operatio
|
|||
</Stack>;
|
||||
}
|
||||
|
||||
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);
|
||||
65
web/app/cad/craft/ui/SelectedModificationInfo.jsx
Normal file
65
web/app/cad/craft/ui/SelectedModificationInfo.jsx
Normal file
|
|
@ -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 <Widget visible={visible}
|
||||
left={lh && lh.x}
|
||||
bottom={75}
|
||||
flatRight={!lh}
|
||||
title={m.type + ' operation #' + indexNumber}
|
||||
onClose={close}>
|
||||
<div className={ls.requestInfo}>
|
||||
<ImgIcon className={ls.pic} url={appearance && appearance.icon96} size={48}/>
|
||||
<RenderObject object={m.params}/>
|
||||
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<ButtonGroup>
|
||||
<Button onClick={edit}>EDIT OPERATION</Button>
|
||||
<Button type='danger' onClick={drop}>DROP OPERATION</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</Widget>;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
11
web/app/cad/craft/ui/SelectedModificationInfo.less
Normal file
11
web/app/cad/craft/ui/SelectedModificationInfo.less
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
.requestInfo {
|
||||
display: flex;
|
||||
& > * {
|
||||
margin-right: 5px;
|
||||
}
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.pic {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -11,31 +11,32 @@ import initializeBySchema from '../../intializeBySchema';
|
|||
import validateParams from '../../validateParams';
|
||||
|
||||
class WizardManager extends React.Component {
|
||||
|
||||
render() {
|
||||
let {wizards, close} = this.props;
|
||||
return <React.Fragment>
|
||||
{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 <Wizard key={wizardIndex}
|
||||
type={type}
|
||||
createPreviewer={this.props.previewerCreator(operation)}
|
||||
form={operation.form}
|
||||
params={params}
|
||||
validate={validator}
|
||||
close={closeInstance}
|
||||
left={offset(wizardIndex)} />
|
||||
})}
|
||||
<HistoryWizard offset={offset(wizards.length)}
|
||||
createValidator={this.props.createValidator}
|
||||
render() {
|
||||
let {wizard, close} = this.props;
|
||||
if (!wizard) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let {type} = wizard;
|
||||
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(wizard);
|
||||
return <React.Fragment>
|
||||
|
||||
<Wizard type={type}
|
||||
createPreviewer={this.props.previewerCreator(operation)}
|
||||
form={operation.form}
|
||||
params={params}
|
||||
validate={validator}
|
||||
close={closeInstance}/>
|
||||
|
||||
<HistoryWizard createValidator={this.props.createValidator}
|
||||
getOperation={this.props.getOperation}
|
||||
previewerCreator={this.props.previewerCreator}/>
|
||||
</React.Fragment>;
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
web/app/cad/dom/components/BottomStack.jsx
Normal file
8
web/app/cad/dom/components/BottomStack.jsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import React from 'react';
|
||||
import ls from './BottomStack.less';
|
||||
|
||||
export default function BottomStack({children}) {
|
||||
return <div className={ls.root}>
|
||||
{children}
|
||||
</div>;
|
||||
}
|
||||
9
web/app/cad/dom/components/BottomStack.less
Normal file
9
web/app/cad/dom/components/BottomStack.less
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.root {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
z-index: 150;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 <Fragment>
|
||||
<Folder title={<span> <Fa fw icon='cubes' /> Model</span>}>
|
||||
<ObjectExplorer/>
|
||||
|
|
@ -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 <ControlBar left={<LeftGroup />} right={<RightGroup />}/>;
|
||||
|
|
@ -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 <ControlBarButton disabled={!enabled} onElement={el => 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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <UISystem className={ls.root} >
|
||||
return <UISystem className={ls.root}>
|
||||
<div className={ls.sideBar}>
|
||||
<PartPanel />
|
||||
<FloatView />
|
||||
</div>
|
||||
<div className={ls.viewer} id='viewer-container'>
|
||||
<Abs left='0.8em' top='0.8em'>
|
||||
<HeadsUpToolbar />
|
||||
<HeadsUpToolbar/>
|
||||
</Abs>
|
||||
<Abs right='0.8em' top='0.8em'>
|
||||
<AuxiliaryToolbar small vertical/>
|
||||
</Abs>
|
||||
<PlugableControlBar />
|
||||
<WizardManager />
|
||||
<BottomStack>
|
||||
<HistoryTimeline />
|
||||
<PlugableControlBar/>
|
||||
</BottomStack>
|
||||
<WizardManager/>
|
||||
</div>
|
||||
</UISystem>
|
||||
<SelectedModificationInfo />
|
||||
</UISystem>;
|
||||
}
|
||||
|
||||
|
||||
componentWillUnmount() {
|
||||
throw 'big no-no';
|
||||
}
|
||||
|
|
|
|||
6
web/app/cad/dom/menu/menuUtils.js
Normal file
6
web/app/cad/dom/menu/menuUtils.js
Normal file
|
|
@ -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
|
||||
});
|
||||
|
|
@ -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
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
0
web/img/cad/timeline/handle.svg
Normal file
0
web/img/cad/timeline/handle.svg
Normal file
|
|
@ -1,7 +1,6 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>TCAD</title>
|
||||
<link rel="stylesheet" href="css/toolkit.css?modeler">
|
||||
<title>Web CAD / Part Designer</title>
|
||||
<link rel="stylesheet" href="lib/font-awesome/css/font-awesome.min.css?modeler">
|
||||
|
||||
<script src="lib/pnltri.js"></script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue