mirror of
https://github.com/xibyte/jsketcher
synced 2026-01-09 09:12:37 +01:00
model attributes
This commit is contained in:
parent
a37d7dc3f8
commit
ed5b40878d
46 changed files with 874 additions and 134 deletions
22
modules/lstream/lazyStreams.ts
Normal file
22
modules/lstream/lazyStreams.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import {state, StateStream} from "lstream/index";
|
||||
|
||||
export class LazyStreams<T> {
|
||||
|
||||
index = new Map<string, StateStream<T>>();
|
||||
proto: (id: string) => T;
|
||||
|
||||
constructor(proto?: (id: string) => T) {
|
||||
this.proto = proto || ((id: string) => null);
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
|
||||
let state$: StateStream<T> = this.index[id];
|
||||
if (state$ == null) {
|
||||
state$ = state<T>(this.proto(id));
|
||||
this.index[id] = state$;
|
||||
}
|
||||
return state$;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,16 +4,18 @@ import cx from 'classnames';
|
|||
import ButtonGroup from "ui/components/controls/ButtonGroup";
|
||||
import Button from "ui/components/controls/Button";
|
||||
|
||||
export function Dialog({children, className, okText, cancelText, onOK, ...props}: WindowProps & {
|
||||
export function Dialog({children, compact, className, okText, cancelText, onOK, ...props}: WindowProps & {
|
||||
cancelText?: string,
|
||||
okText?: string,
|
||||
onOK?: () => void
|
||||
onOK?: () => void,
|
||||
compact?: boolean,
|
||||
}) {
|
||||
|
||||
return <Window className={cx(className, 'dialog')}
|
||||
return <Window compact={compact}
|
||||
className={cx(className, 'dialog')}
|
||||
footer={
|
||||
<ButtonGroup className='dialog-buttons padded'>
|
||||
<Button onClick={props.onClose}>{cancelText || 'Cancel'}</Button>
|
||||
{!compact && <Button onClick={props.onClose}>{cancelText || 'Cancel'}</Button>}
|
||||
{onOK && <Button type='accent' onClick={onOK}>{okText || 'OK'}</Button>}
|
||||
</ButtonGroup>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,40 @@
|
|||
import React from 'react';
|
||||
import React, {useState} from 'react';
|
||||
|
||||
import ls from './Folder.less'
|
||||
import Fa from "./Fa";
|
||||
import cx from 'classnames';
|
||||
import cx from "classnames";
|
||||
|
||||
export default class Folder extends React.Component{
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
closed: null
|
||||
}
|
||||
}
|
||||
export function InnerFolder(props) {
|
||||
|
||||
isClosed() {
|
||||
let {closable, defaultClosed} = this.props;
|
||||
const [closed, setClosed] = useState(null)
|
||||
|
||||
function isClosed(){
|
||||
let {closable, defaultClosed} = props;
|
||||
if (!closable) return false;
|
||||
return closable && (this.state.closed === null ? defaultClosed : this.state.closed)
|
||||
return closable && (closed === null ? defaultClosed : closed)
|
||||
}
|
||||
|
||||
tweakClose = () => {
|
||||
this.setState({closed: !this.isClosed()});
|
||||
};
|
||||
|
||||
render() {
|
||||
let {title, closable, className, children} = this.props;
|
||||
return <div className={cx(ls.root, className)}>
|
||||
<Title onClick={closable ? this.tweakClose : null} isClosed={this.isClosed()}>{title}</Title>
|
||||
{!this.isClosed() && children}
|
||||
</div>
|
||||
function tweakClose( ) {
|
||||
setClosed(!isClosed())
|
||||
}
|
||||
|
||||
const {title, titleClass, closable, children} = props;
|
||||
return <React.Fragment>
|
||||
<Title onClick={closable ? tweakClose : null} isClosed={isClosed()} className={titleClass}>{title}</Title>
|
||||
{!isClosed() && children}
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
export function Title({children, isClosed, onClick}) {
|
||||
export default function Folder({className, ...props}) {
|
||||
return <div className={className}>
|
||||
<InnerFolder {...props} />
|
||||
</div>
|
||||
}
|
||||
export function Title({children, isClosed, onClick, className}) {
|
||||
const titleCss = onClick ? {
|
||||
cursor: 'pointer'
|
||||
} : {};
|
||||
return <div className={ls.title} onClick={onClick} style={titleCss}>
|
||||
return <div className={cx(ls.title, className)} onClick={onClick} style={titleCss}>
|
||||
<span className={ls.handle}><Fa fw icon={isClosed ? 'chevron-right' : 'chevron-down'}/></span>
|
||||
{' '}{children}
|
||||
</div>;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
@import "~ui/styles/theme.less";
|
||||
|
||||
.root {
|
||||
}
|
||||
|
||||
.title {
|
||||
border-bottom: 1px solid @border-color;
|
||||
background-color: @bg-base-color;
|
||||
|
|
|
|||
72
modules/ui/components/GenericExplorer.less
Normal file
72
modules/ui/components/GenericExplorer.less
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
.objectItem {
|
||||
@itemRadius: 5px;
|
||||
@alt-color: #9c9c9c;
|
||||
|
||||
.button {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #0074D9;
|
||||
}
|
||||
&:active {
|
||||
background-color: #000d7f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
background-color: rgba(0,0,0, 0.6);
|
||||
border: 1px solid #000;
|
||||
border-radius: 5px;
|
||||
margin: 2px @itemRadius;
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
.expandHandleInactive {
|
||||
border-radius: @itemRadius 0 0 @itemRadius;
|
||||
padding: 3px 3px;
|
||||
}
|
||||
.expandHandle {
|
||||
.expandHandleInactive;
|
||||
.button;
|
||||
//background-color: @alt-color;
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
border-radius: 0 @itemRadius @itemRadius 0;
|
||||
background-color: @alt-color;
|
||||
padding: 0 3px;
|
||||
.button;
|
||||
}
|
||||
|
||||
.objectLabel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
padding: 0 5px;
|
||||
.button;
|
||||
|
||||
&.selected {
|
||||
background: linear-gradient(#59acff, #0074D9);
|
||||
}
|
||||
}
|
||||
|
||||
.onOffButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.onOffButton.on {
|
||||
cursor: pointer;
|
||||
background-color: #005e82;
|
||||
.button;
|
||||
}
|
||||
.onOffButton.off {
|
||||
cursor: pointer;
|
||||
background-color: #5f5f5f;
|
||||
.button;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
58
modules/ui/components/GenericExplorer.tsx
Normal file
58
modules/ui/components/GenericExplorer.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import ls from "./GenericExplorer.less";
|
||||
|
||||
import React, {useState} from 'react';
|
||||
import cx from 'classnames';
|
||||
import {AiFillCaretDown, AiFillCaretRight} from "react-icons/ai";
|
||||
import {BsDot} from "react-icons/bs";
|
||||
|
||||
interface GenericExplorerControlProps {
|
||||
title: string;
|
||||
onClick: any;
|
||||
on: boolean;
|
||||
children: any;
|
||||
}
|
||||
|
||||
export function GenericExplorerControl(props: GenericExplorerControlProps) {
|
||||
|
||||
return <span onClick={props.onClick} title={props.title}
|
||||
className={cx(ls.onOffButton, ls[ props.on ? 'on' : 'off' ])}>{props.children}</span>
|
||||
}
|
||||
|
||||
interface GenericExplorerNodeProps {
|
||||
children: any;
|
||||
controls: any;
|
||||
label: any;
|
||||
selected: boolean;
|
||||
defaultExpanded?: boolean;
|
||||
expandable: boolean;
|
||||
select: any;
|
||||
}
|
||||
|
||||
export function GenericExplorerNode(props: GenericExplorerNodeProps) {
|
||||
|
||||
const [expanded, setExpanded] = useState(!!props.defaultExpanded);
|
||||
const expandable = props.expandable;
|
||||
|
||||
const toggle = expandable ? (() => setExpanded(expanded => !expanded)) : undefined;
|
||||
|
||||
return <>
|
||||
<div className={ls.objectItem}>
|
||||
<span className={expandable ? ls.expandHandle : ls.expandHandleInactive} onClick={toggle}>
|
||||
{expandable ? (expanded ? <AiFillCaretDown/> : <AiFillCaretRight/>)
|
||||
: <BsDot/>}
|
||||
</span>
|
||||
{props.controls}
|
||||
|
||||
<span onClick={props.select}
|
||||
className={cx(ls.objectLabel, props.selected && ls.selected)}>
|
||||
{props.label}
|
||||
</span>
|
||||
|
||||
<span className={ls.menuButton}>...</span>
|
||||
|
||||
</div>
|
||||
{expanded && <div style={{paddingLeft: 10}}>
|
||||
{props.children}
|
||||
</div>}
|
||||
</>;
|
||||
}
|
||||
25
modules/ui/components/SceneInlineSection.less
Normal file
25
modules/ui/components/SceneInlineSection.less
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
.titleBar {
|
||||
background: rgba(0,0,0,0.7);
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
padding: 3px 5px;
|
||||
pointer-events: auto;
|
||||
flex: 0 0 ;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.scrollableArea {
|
||||
overflow-y: auto;
|
||||
flex: 1 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.delineation {
|
||||
background: rgba(0,0,0,0.5);
|
||||
color: white;
|
||||
padding: 0;
|
||||
pointer-events: auto;
|
||||
flex: 0 0 ;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
26
modules/ui/components/SceneInlineSection.tsx
Normal file
26
modules/ui/components/SceneInlineSection.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import React from "react";
|
||||
import ls from "./SceneInlineSection.less";
|
||||
|
||||
interface SceneInlineSectionProps {
|
||||
title: any;
|
||||
children: any;
|
||||
}
|
||||
|
||||
export function SceneInlineSection(props: SceneInlineSectionProps) {
|
||||
|
||||
return <React.Fragment>
|
||||
<SceneInlineTitleBar>{props.title}</SceneInlineTitleBar>
|
||||
<div className={ls.scrollableArea}>
|
||||
{props.children}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
|
||||
}
|
||||
|
||||
export function SceneInlineTitleBar({children, ...props}) {
|
||||
return <div className={ls.titleBar} {...props}>{children}</div>;
|
||||
}
|
||||
|
||||
export function SceneInlineDelineation({children, ...props}) {
|
||||
return <div className={ls.delineation} {...props}>{children}</div>;
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
|
||||
import ls from './Stack.less'
|
||||
import cx from "classnames";
|
||||
|
||||
export default function Stack(props) {
|
||||
return <div className={ls.root} {...props} />
|
||||
export default function Stack({className, ...props}) {
|
||||
return <div className={cx(ls.root, className)} {...props} />
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,3 +6,8 @@
|
|||
padding: 3px 6px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.root > .root {
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@
|
|||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.root.compact {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.mandatoryBorder {
|
||||
border: 5px solid @bg-color-4;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export interface WindowProps {
|
|||
onClose: () => void;
|
||||
props?: JSX.IntrinsicAttributes;
|
||||
footer?: JSX.Element;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export default class Window extends React.Component<WindowProps> {
|
||||
|
|
@ -45,9 +46,9 @@ export default class Window extends React.Component<WindowProps> {
|
|||
render() {
|
||||
|
||||
let {initWidth, initHeight, initLeft, initTop, initRight, initBottom, centerScreen, setFocus, className, resizeCapturingBuffer,
|
||||
resize, enableResize, children, title, icon, minimizable = false, onClose, controlButtons, footer, ...props} = this.props;
|
||||
resize, enableResize, children, title, icon, minimizable = false, onClose, controlButtons, footer, compact, ...props} = this.props;
|
||||
|
||||
return <div className={cx(ls.root, this.resizeConfig&&ls.mandatoryBorder, className)} {...props} ref={this.keepRef}>
|
||||
return <div className={cx(ls.root, this.resizeConfig&&ls.mandatoryBorder, compact&&ls.compact, className)} {...props} ref={this.keepRef}>
|
||||
<div className={ls.bar + ' disable-selection'} onMouseDown={this.startDrag} onMouseUp={this.stopDrag}>
|
||||
<div className={ls.title}>{icon}<b>{title.toUpperCase()}</b></div>
|
||||
<div className={ls.controlButtons}>
|
||||
|
|
|
|||
50
modules/ui/components/controls/ColorControl.tsx
Normal file
50
modules/ui/components/controls/ColorControl.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import React, {useState} from 'react';
|
||||
import {SketchPicker} from 'react-color';
|
||||
import Button from "ui/components/controls/Button";
|
||||
import {CgColorPicker} from "react-icons/cg";
|
||||
import {Dialog} from "ui/components/Dialog";
|
||||
import ButtonGroup from "ui/components/controls/ButtonGroup";
|
||||
import {RiDeleteBack2Line} from "react-icons/ri";
|
||||
|
||||
interface ColorControlProps {
|
||||
|
||||
value: string;
|
||||
|
||||
onChange: any;
|
||||
|
||||
dialogTitle?: any
|
||||
|
||||
}
|
||||
|
||||
export function ColorControl(props: ColorControlProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const {onChange, value} = props;
|
||||
const style = value ? { backgroundColor: value } : undefined;
|
||||
|
||||
return <React.Fragment>
|
||||
<ButtonGroup>
|
||||
<span style={{
|
||||
display: 'inline-block',
|
||||
padding: '0 3px',
|
||||
fontFamily: 'monospace',
|
||||
...style
|
||||
}}>{value||<NoValue/>}</span>
|
||||
<Button onClick={() => onChange(null)}><RiDeleteBack2Line /></Button>
|
||||
<Button onClick={() => setOpen(true)}><CgColorPicker /></Button>
|
||||
</ButtonGroup>
|
||||
{open && <ColorDialog value={value||'white'} onChange={onChange} onClose={() => setOpen(false)} title={props.dialogTitle}/>}
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
function ColorDialog({onChange, value, title, onClose}) {
|
||||
const change = (color) => {
|
||||
onChange(color.hex);
|
||||
}
|
||||
return <Dialog title={title||'color'} onClose={onClose} compact>
|
||||
<SketchPicker color={value} onChange={change} onChangeComplete={change}/>
|
||||
</Dialog>
|
||||
}
|
||||
|
||||
function NoValue() {
|
||||
return <span style={{fontStyle: 'italic'}}>{'<not set>'}</span>;
|
||||
}
|
||||
|
|
@ -2,13 +2,14 @@ import {useCallback, useContext, useEffect, useState} from 'react';
|
|||
import {StreamsContext} from "./streamsContext";
|
||||
import {ApplicationContext} from "context";
|
||||
import {Emitter, Stream} from "lstream";
|
||||
import produce from "immer";
|
||||
|
||||
export function useStream<T>(getStream: Stream<T> | ((ctx: ApplicationContext) => Stream<T>)) : T {
|
||||
|
||||
const basicStreams = useContext(StreamsContext);
|
||||
const [state, setState] = useState<{data: T}>();
|
||||
|
||||
const stream = resolveStream(getStream, basicStreams);
|
||||
const stream: Emitter<T> = resolveStream(getStream, basicStreams);
|
||||
|
||||
if (!stream) {
|
||||
console.log(getStream);
|
||||
|
|
@ -41,6 +42,26 @@ export function useStreamWithUpdater<T>(getStream: (ctx: ApplicationContext) =>
|
|||
|
||||
}
|
||||
|
||||
function resolveStream(getStream, basicStreams) {
|
||||
export type Patcher<T> = (draft: T) => void;
|
||||
|
||||
export function useStreamWithPatcher<T>(getStream: (ctx: ApplicationContext) => Emitter<T>) : [T, (patcher: Patcher<T>) => void] {
|
||||
|
||||
const data = useStream(getStream);
|
||||
const basicStreams = useContext(StreamsContext);
|
||||
|
||||
const stream: Emitter<T> = resolveStream(getStream, basicStreams);
|
||||
|
||||
const patch = (patcher: Patcher<T>) => {
|
||||
const newData: T = produce(data, (draft:T) => {
|
||||
patcher(draft)
|
||||
})
|
||||
stream.next(newData);
|
||||
}
|
||||
|
||||
return [data, patch];
|
||||
|
||||
}
|
||||
|
||||
function resolveStream<T>(getStream, basicStreams): Emitter<T> {
|
||||
return typeof getStream === 'function' ? getStream(basicStreams) : getStream
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,4 +36,15 @@ path {
|
|||
|
||||
.warning-text {
|
||||
color: @on-color-highlight-variant-yellow;
|
||||
}
|
||||
}
|
||||
|
||||
.bg-color-0 { background-color: @bg-color-0; }
|
||||
.bg-color-1 { background-color: @bg-color-1; }
|
||||
.bg-color-2 { background-color: @bg-color-2; }
|
||||
.bg-color-3 { background-color: @bg-color-3; }
|
||||
.bg-color-4 { background-color: @bg-color-4; }
|
||||
.bg-color-5 { background-color: @bg-color-5; }
|
||||
.bg-color-6 { background-color: @bg-color-6; }
|
||||
.bg-color-7 { background-color: @bg-color-7; }
|
||||
.bg-color-8 { background-color: @bg-color-8; }
|
||||
.bg-color-9 { background-color: @bg-color-9; }
|
||||
97
package-lock.json
generated
97
package-lock.json
generated
|
|
@ -22,6 +22,7 @@
|
|||
"opencascade.js": "^1.1.1",
|
||||
"prop-types": "15.6.0",
|
||||
"react": "^16.13.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-icons": "^4.2.0",
|
||||
"react-toastify": "^5.5.0",
|
||||
|
|
@ -8915,6 +8916,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
|
|
@ -9173,6 +9179,11 @@
|
|||
"node": ">= 8.16.2"
|
||||
}
|
||||
},
|
||||
"node_modules/material-colors": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
|
||||
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
|
||||
},
|
||||
"node_modules/md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
|
@ -11110,6 +11121,31 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-color": {
|
||||
"version": "2.19.3",
|
||||
"resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz",
|
||||
"integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==",
|
||||
"dependencies": {
|
||||
"@icons/material": "^0.2.4",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash-es": "^4.17.15",
|
||||
"material-colors": "^1.2.1",
|
||||
"prop-types": "^15.5.10",
|
||||
"reactcss": "^1.2.0",
|
||||
"tinycolor2": "^1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-color/node_modules/@icons/material": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
|
||||
"integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
|
||||
|
|
@ -11242,6 +11278,14 @@
|
|||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/reactcss": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
|
||||
"integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
|
||||
"dependencies": {
|
||||
"lodash": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/read-pkg": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
||||
|
|
@ -13036,6 +13080,14 @@
|
|||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinycolor2": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz",
|
||||
"integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
|
|
@ -22536,6 +22588,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
|
|
@ -22731,6 +22788,11 @@
|
|||
"resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz",
|
||||
"integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng=="
|
||||
},
|
||||
"material-colors": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
|
||||
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
|
@ -24344,6 +24406,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"react-color": {
|
||||
"version": "2.19.3",
|
||||
"resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz",
|
||||
"integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==",
|
||||
"requires": {
|
||||
"@icons/material": "^0.2.4",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash-es": "^4.17.15",
|
||||
"material-colors": "^1.2.1",
|
||||
"prop-types": "^15.5.10",
|
||||
"reactcss": "^1.2.0",
|
||||
"tinycolor2": "^1.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@icons/material": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
|
||||
"integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==",
|
||||
"requires": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
|
||||
|
|
@ -24454,6 +24538,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"reactcss": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
|
||||
"integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
|
||||
"requires": {
|
||||
"lodash": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
||||
|
|
@ -25966,6 +26058,11 @@
|
|||
"setimmediate": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"tinycolor2": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz",
|
||||
"integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA=="
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@
|
|||
"opencascade.js": "^1.1.1",
|
||||
"prop-types": "15.6.0",
|
||||
"react": "^16.13.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-icons": "^4.2.0",
|
||||
"react-toastify": "^5.5.0",
|
||||
|
|
|
|||
32
web/app/cad/attributes/attributesPlugin.ts
Normal file
32
web/app/cad/attributes/attributesPlugin.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import {Plugin} from "plugable/pluginSystem";
|
||||
import {AttributesService} from "cad/attributes/attributesService";
|
||||
|
||||
interface AttributesPluginInputContext {
|
||||
}
|
||||
|
||||
export interface AttributesPluginContext {
|
||||
attributesService: AttributesService;
|
||||
}
|
||||
|
||||
type AttributesPluginWorkingContext = AttributesPluginInputContext&AttributesPluginContext;
|
||||
|
||||
declare module 'context' {
|
||||
interface ApplicationContext extends AttributesPluginContext {}
|
||||
}
|
||||
|
||||
export const AttributesPlugin: Plugin<AttributesPluginInputContext, AttributesPluginContext, AttributesPluginWorkingContext> = {
|
||||
|
||||
inputContextSpec: {
|
||||
},
|
||||
|
||||
outputContextSpec: {
|
||||
attributesService: 'required',
|
||||
},
|
||||
|
||||
activate(ctx: AttributesPluginWorkingContext) {
|
||||
ctx.attributesService = new AttributesService();
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
19
web/app/cad/attributes/attributesService.ts
Normal file
19
web/app/cad/attributes/attributesService.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import {LazyStreams} from "lstream/lazyStreams";
|
||||
|
||||
export class AttributesService {
|
||||
|
||||
streams = new LazyStreams<ModelAttributes>(id => ({
|
||||
hidden: false,
|
||||
label: null,
|
||||
color: null
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
export interface ModelAttributes {
|
||||
hidden: boolean;
|
||||
label: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -7,6 +7,7 @@ import {intercept} from "lstream/intercept";
|
|||
import {CoreContext} from "context";
|
||||
import {MFace} from "../model/mface";
|
||||
import {OperationParams} from "cad/craft/schema/schema";
|
||||
import {clearImplicitModels} from "cad/craft/e0/occCommandInterface";
|
||||
|
||||
export function activate(ctx: CoreContext) {
|
||||
|
||||
|
|
@ -69,6 +70,7 @@ export function activate(ctx: CoreContext) {
|
|||
}
|
||||
|
||||
function runRequest(request): Promise<OperationResult> {
|
||||
clearImplicitModels();
|
||||
try {
|
||||
let op = ctx.operationService.get(request.type);
|
||||
if (!op) {
|
||||
|
|
@ -90,6 +92,8 @@ export function activate(ctx: CoreContext) {
|
|||
return result.then ? result : Promise.resolve(result);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
} finally {
|
||||
clearImplicitModels();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,40 @@
|
|||
import React from 'react';
|
||||
import connect from 'ui/connect';
|
||||
import {Section} from 'ui/components/Section';
|
||||
import Fa from 'ui/components/Fa';
|
||||
import {constant} from 'lstream';
|
||||
import ls from './ObjectExplorer.less';
|
||||
import cx from 'classnames';
|
||||
import {MShell} from '../../model/mshell';
|
||||
import {MDatum} from '../../model/mdatum';
|
||||
import {MShell} from 'cad/model/mshell';
|
||||
import {MDatum} from 'cad/model/mdatum';
|
||||
import mapContext from 'ui/mapContext';
|
||||
import decoratorChain from 'ui/decoratorChain';
|
||||
import {MOpenFaceShell} from "cad/model/mopenFace";
|
||||
|
||||
export default connect(streams => streams.craft.models.map(models => ({models})))
|
||||
(function ObjectExplorer({models}) {
|
||||
|
||||
return models.map(m => (m instanceof MShell) ? <ModelSection type='shell' model={m} defaultOpen={true} key={m.id}>
|
||||
<Section label='faces' defaultOpen={true}>
|
||||
{
|
||||
m.faces.map(f => <FaceSection face={f} key={f.id} />)
|
||||
}
|
||||
</Section>
|
||||
<Section label='edges' defaultOpen={true}>
|
||||
{m.edges.map(e => <EdgeSection edge={e} key={e.id} />)}
|
||||
</Section>
|
||||
|
||||
</ModelSection> : (m instanceof MDatum) ? <ModelSection type='datum' model={m} defaultOpen={true}/> : null);
|
||||
return <div> {models.map(m => {
|
||||
if (m instanceof MOpenFaceShell) {
|
||||
return <OpenFaceSection shell={m}/>
|
||||
} else if (m instanceof MShell) {
|
||||
return <ModelSection type='shell' model={m} key={m.id}>
|
||||
<Section label='faces' defaultOpen={true}>
|
||||
{
|
||||
m.faces.map(f => <FaceSection face={f} key={f.id}/>)
|
||||
}
|
||||
</Section>
|
||||
<Section label='edges' defaultOpen={true}>
|
||||
{m.edges.map(e => <EdgeSection edge={e} key={e.id}/>)}
|
||||
</Section>
|
||||
|
||||
</ModelSection>
|
||||
} else if (m instanceof MDatum) {
|
||||
return <ModelSection type='datum' model={m} key={m.id}/>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
});
|
||||
|
||||
function EdgeSection({edge}) {
|
||||
|
|
@ -36,36 +46,44 @@ function EdgeSection({edge}) {
|
|||
function FaceSection({face}) {
|
||||
return <ModelSection type='face' model={face} key={face.id}>
|
||||
|
||||
{(face.productionInfo && face.productionInfo.role) && <Section label={<span>role: {face.productionInfo.role}</span>} />}
|
||||
{(face.productionInfo && face.productionInfo.originatedFromPrimitive) && <Section label={<span>origin: {face.productionInfo.originatedFromPrimitive}</span>} />}
|
||||
<Section label={face.sketchObjects.length ? 'sketch' : <span className={ls.hint}>{'<no sketch assigned>'}</span>}>
|
||||
{face.sketchObjects.map(o => <div key={o.id}>{o.id + ':' + o.sketchPrimitive.constructor.name}</div>)}
|
||||
</Section>
|
||||
{(face.productionInfo && face.productionInfo.role) &&
|
||||
<Section label={<span>role: {face.productionInfo.role}</span>}/>}
|
||||
{(face.productionInfo && face.productionInfo.originatedFromPrimitive) &&
|
||||
<Section label={<span>origin: {face.productionInfo.originatedFromPrimitive}</span>}/>}
|
||||
<SketchesList face={face}/>
|
||||
{face.edges && <Section label='edges' defaultOpen={false}>
|
||||
{face.edges.map(e => <EdgeSection edge={e} key={e.id}/>)}
|
||||
</Section>}
|
||||
</ModelSection>;
|
||||
}
|
||||
|
||||
function SketchesList({face}) {
|
||||
return <Section
|
||||
label={face.sketchObjects.length ? 'sketch' : <span className={ls.hint}>{'<no sketch assigned>'}</span>}>
|
||||
{face.sketchObjects.map(o => <div key={o.id}>{o.id + ':' + o.sketchPrimitive.constructor.name}</div>)}
|
||||
</Section>;
|
||||
}
|
||||
|
||||
const ModelSection = decoratorChain(
|
||||
mapContext((ctx, props) => ({
|
||||
select: () => ctx.services.pickControl.pick(props.model)
|
||||
})),
|
||||
connect((streams, props) => (streams.selection[props.type] || constant([])).map(selection => ({selection}))))
|
||||
(
|
||||
function ModelSection({model, type, selection, select, ...props}) {
|
||||
function ModelSection({model, type, typeLabel, selection, select, ...props}) {
|
||||
let labelClasses = cx(ls.modelLabel, {
|
||||
[ls.selected]: selection.indexOf(model.id) !== -1
|
||||
});
|
||||
let label = <span className={labelClasses}><CommonControls/>
|
||||
<span onClick={select}>{type} {model.id}</span>
|
||||
let label = <span className={labelClasses}>
|
||||
<span onClick={select}>{typeLabel||type} {model.id}</span>
|
||||
</span>;
|
||||
return <Section label={label} {...props}/>;
|
||||
}
|
||||
);
|
||||
|
||||
function CommonControls() {
|
||||
return <React.Fragment>
|
||||
<Fa fw icon='crosshairs'/>
|
||||
</React.Fragment>;
|
||||
function OpenFaceSection({shell}) {
|
||||
return <ModelSection type='face' model={shell.face} key={shell.face.id} typeLabel='surface'>
|
||||
<SketchesList face={shell.face}/>
|
||||
</ModelSection>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
.selected {
|
||||
background-color: #7d7d7d;
|
||||
background-color: #f3fb8a;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.modelLabel {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.hint {
|
||||
|
|
|
|||
152
web/app/cad/craft/ui/SceneInlineObjectExplorer.tsx
Normal file
152
web/app/cad/craft/ui/SceneInlineObjectExplorer.tsx
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import React, {useContext, useState} from 'react';
|
||||
import {MShell} from 'cad/model/mshell';
|
||||
import {MDatum} from 'cad/model/mdatum';
|
||||
import {MOpenFaceShell} from "cad/model/mopenFace";
|
||||
import {useStream, useStreamWithPatcher} from "ui/effects";
|
||||
import {AppContext} from "cad/dom/components/AppContext";
|
||||
import {MObject} from "cad/model/mobject";
|
||||
import {SceneInlineDelineation, SceneInlineSection, SceneInlineTitleBar} from "ui/components/SceneInlineSection";
|
||||
import {GenericExplorerControl, GenericExplorerNode} from "ui/components/GenericExplorer";
|
||||
import ls from "cad/craft/ui/ObjectExplorer.less";
|
||||
import Fa from "ui/components/Fa";
|
||||
import {AiOutlineEye, AiOutlineEyeInvisible} from "react-icons/ai";
|
||||
import {ModelAttributes} from "cad/craft/ui/VisibleSwitch";
|
||||
|
||||
|
||||
export function SceneInlineObjectExplorer() {
|
||||
|
||||
const models = useStream(ctx => ctx.craftService.models$);
|
||||
|
||||
if (!models) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <SceneInlineSection title='OBJECTS'> {models.map(m => {
|
||||
if (m instanceof MOpenFaceShell) {
|
||||
return <OpenFaceSection shell={m} />
|
||||
} else if (m instanceof MShell) {
|
||||
return <ModelSection type='shell' model={m} key={m.id} controlVisibility>
|
||||
<Section label='faces' defaultOpen={true}>
|
||||
{
|
||||
m.faces.map(f => <FaceSection face={f} key={f.id}/>)
|
||||
}
|
||||
</Section>
|
||||
<Section label='edges' defaultOpen={true}>
|
||||
{m.edges.map(e => <EdgeSection edge={e} key={e.id}/>)}
|
||||
</Section>
|
||||
|
||||
</ModelSection>
|
||||
} else if (m instanceof MDatum) {
|
||||
return <ModelSection type='datum' model={m} key={m.id} controlVisibility/>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</SceneInlineSection>
|
||||
}
|
||||
|
||||
function EdgeSection({edge}) {
|
||||
return <ModelSection type='edge' model={edge} key={edge.id}>
|
||||
{edge.adjacentFaces.map(f => <FaceSection face={f} key={f.id}/>)}
|
||||
</ModelSection>
|
||||
}
|
||||
|
||||
function FaceSection({face}) {
|
||||
return <ModelSection type='face' model={face} key={face.id}>
|
||||
|
||||
{(face.productionInfo && face.productionInfo.role) &&
|
||||
<Section label={<span>role: {face.productionInfo.role}</span>}/>}
|
||||
{(face.productionInfo && face.productionInfo.originatedFromPrimitive) &&
|
||||
<Section label={<span>origin: {face.productionInfo.originatedFromPrimitive}</span>}/>}
|
||||
<SketchesList face={face}/>
|
||||
{face.edges && <Section label='edges' defaultOpen={false}>
|
||||
{face.edges.map(e => <EdgeSection edge={e} key={e.id}/>)}
|
||||
</Section>}
|
||||
</ModelSection>;
|
||||
}
|
||||
|
||||
function SketchesList({face}) {
|
||||
return <Section
|
||||
label={face.sketchObjects.length ? 'sketch' : <span className={ls.hint}>{'<no sketch assigned>'}</span>}>
|
||||
{face.sketchObjects.map(o => <ModelSection
|
||||
key={o.id}
|
||||
typeLabel={o.sketchPrimitive.constructor.name}
|
||||
model={o}
|
||||
type={'sketchObject'}
|
||||
expandable={false}
|
||||
/>)}
|
||||
</Section>;
|
||||
}
|
||||
|
||||
|
||||
function ModelSection({model, type, typeLabel, expandable = true, controlVisibility = false, visibilityOf = null, ...props}: {
|
||||
model: MObject,
|
||||
visibilityOf?: MObject,
|
||||
typeLabel?: any,
|
||||
type: string,
|
||||
children?: any,
|
||||
controlVisibility?: boolean,
|
||||
expandable?: boolean,
|
||||
}) {
|
||||
const ctx = useContext(AppContext);
|
||||
const selection: string[] = useStream(ctx => ctx.streams.selection[type]);
|
||||
|
||||
const select = () => ctx.services.pickControl.pick(model);
|
||||
const selected = selection.indexOf(model.id) !== -1;
|
||||
|
||||
let label = <>{typeLabel === undefined ? type:typeLabel} {model.id}</>;
|
||||
visibilityOf = visibilityOf||model;
|
||||
return <GenericExplorerNode defaultExpanded={false}
|
||||
expandable={expandable}
|
||||
label={label}
|
||||
selected={selected}
|
||||
select={select}
|
||||
controls={
|
||||
<>
|
||||
{controlVisibility && <VisibleSwitch modelId={visibilityOf.id}/>}
|
||||
</>
|
||||
|
||||
}>
|
||||
{props.children}
|
||||
</GenericExplorerNode>
|
||||
}
|
||||
|
||||
function OpenFaceSection({shell}) {
|
||||
return <ModelSection type='face' model={shell.face} key={shell.face.id} typeLabel='surface'
|
||||
controlVisibility
|
||||
visibilityOf={shell}>
|
||||
<SketchesList face={shell.face}/>
|
||||
</ModelSection>;
|
||||
}
|
||||
|
||||
function Section(props) {
|
||||
|
||||
const [expanded, setExpanded] = useState(!props.defaultCollapsed);
|
||||
|
||||
const tweakClose = () => {
|
||||
setExpanded(exp => !exp);
|
||||
};
|
||||
|
||||
return <>
|
||||
<SceneInlineDelineation onClick={tweakClose} style={{cursor: 'pointer'}}>
|
||||
<Fa fw icon={'caret-' + (expanded ? 'down' : 'right')}/>
|
||||
<span className={ls.label}>{props.label}</span>
|
||||
</SceneInlineDelineation>
|
||||
{expanded && props.children}
|
||||
</>;
|
||||
}
|
||||
|
||||
export function VisibleSwitch({modelId}) {
|
||||
|
||||
let [attrs, patch] = useStreamWithPatcher<ModelAttributes>(ctx => ctx.attributesService.streams.get(modelId));
|
||||
|
||||
const onClick = () => {
|
||||
patch(attr => {
|
||||
attr.hidden = !attr.hidden
|
||||
})
|
||||
}
|
||||
|
||||
return <GenericExplorerControl onClick={onClick} title={'test'} on={attrs.hidden}>
|
||||
{attrs.hidden ? <AiOutlineEyeInvisible /> : <AiOutlineEye />}
|
||||
</GenericExplorerControl>
|
||||
}
|
||||
34
web/app/cad/craft/ui/VisibleSwitch.tsx
Normal file
34
web/app/cad/craft/ui/VisibleSwitch.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import React from "react";
|
||||
import {AiOutlineEye} from "react-icons/ai";
|
||||
import {state, StateStream} from "lstream";
|
||||
import {useStreamWithPatcher} from "ui/effects";
|
||||
|
||||
class LazyStreams<T> {
|
||||
|
||||
index = new Map<string, StateStream<T>>();
|
||||
proto: (id: string) => T;
|
||||
|
||||
constructor(proto?: (id: string) => T) {
|
||||
this.proto = proto || ((id: string) => null);
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
|
||||
let state$: StateStream<T> = this.index[id];
|
||||
if (state$ == null) {
|
||||
state$ = state<T>(this.proto(id));
|
||||
this.index[id] = state$;
|
||||
}
|
||||
return state$;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface ModelAttributes {
|
||||
|
||||
hidden: boolean
|
||||
|
||||
}
|
||||
|
||||
const modelAttrStreams = new LazyStreams<ModelAttributes>(id => ({} as ModelAttributes));
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ export function attachToForm(Control): any {
|
|||
state.activeParam = isActive ? fullPathFlatten : null;
|
||||
});
|
||||
|
||||
const value = _.get(params, fullPath);
|
||||
const value = params[name];
|
||||
|
||||
return <React.Fragment>
|
||||
<Control value={value}
|
||||
|
|
@ -65,10 +65,10 @@ export function attachToForm(Control): any {
|
|||
|
||||
export function SubForm(props: {name: ParamsPathSegment, children: any}) {
|
||||
|
||||
const formState = useContext(FormStateContext);
|
||||
const formParams = useContext(FormParamsContext);
|
||||
const formPath = useContext(FormPathContext);
|
||||
|
||||
return <FormParamsContext.Provider value={formState[props.name]}>
|
||||
return <FormParamsContext.Provider value={formParams[props.name] as any}>
|
||||
<FormPathContext.Provider value={[...formPath, props.name]}>
|
||||
{props.children}
|
||||
</FormPathContext.Provider>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,9 @@
|
|||
import React from 'react';
|
||||
import decoratorChain from 'ui/decoratorChain';
|
||||
import connect from 'ui/connect';
|
||||
import mapContext from 'ui/mapContext';
|
||||
import {combine} from 'lstream';
|
||||
import ObjectExplorer from '../../craft/ui/ObjectExplorer';
|
||||
import ls from './Explorer.less';
|
||||
|
||||
function Explorer() {
|
||||
export function Explorer() {
|
||||
return <div className={ls.root}>
|
||||
<ObjectExplorer />
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default decoratorChain(
|
||||
connect(streams => combine(
|
||||
|
||||
)),
|
||||
mapContext(ctx => ({}))
|
||||
)(Explorer);
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
.root {
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
bottom:80px;
|
||||
max-width: 300px;
|
||||
//overflow: auto;
|
||||
//position: absolute;
|
||||
//top:0;
|
||||
//left:0;
|
||||
//bottom:80px;
|
||||
//max-width: 300px;
|
||||
}
|
||||
|
||||
.root :global(.sectionHeader) {
|
||||
|
|
@ -15,4 +15,7 @@
|
|||
border-radius: 3px;
|
||||
pointer-events: initial;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
import React from 'react';
|
||||
import {useStream} from "ui/effects";
|
||||
import React, {useContext} from 'react';
|
||||
import {useStream, useStreamWithPatcher} from "ui/effects";
|
||||
import {SELECTABLE_ENTITIES} from "../../scene/entityContextPlugin";
|
||||
import {AppContext} from "cad/dom/components/AppContext";
|
||||
import Field from "ui/components/controls/Field";
|
||||
import {InnerFolder} from "ui/components/Folder";
|
||||
import Label from "ui/components/controls/Label";
|
||||
import TextControl from "ui/components/controls/TextControl";
|
||||
import Stack from "ui/components/Stack";
|
||||
import {ColorControl} from "ui/components/controls/ColorControl";
|
||||
|
||||
|
||||
|
||||
export function SelectionView() {
|
||||
|
|
@ -11,6 +19,7 @@ export function SelectionView() {
|
|||
});
|
||||
|
||||
|
||||
|
||||
return <div className='selection-view'>
|
||||
|
||||
{SELECTABLE_ENTITIES.map((entity, i) => {
|
||||
|
|
@ -20,18 +29,44 @@ export function SelectionView() {
|
|||
if (selection.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return <div>
|
||||
<b>{entity}</b>
|
||||
<ul data-entity={entity} key={entity} style={{marginLeft: 10}}>
|
||||
|
||||
{selection.map(id => <li>{id}</li>)}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
return <React.Fragment key={i}>
|
||||
<Stack>
|
||||
{selection.map(id => <SelectedModelView key={id} modelId={id} type={entity}/>)}
|
||||
</Stack>
|
||||
</React.Fragment>
|
||||
})}
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
interface SelectedModelViewProps {
|
||||
|
||||
modelId,
|
||||
type
|
||||
|
||||
}
|
||||
|
||||
function SelectedModelView(props: SelectedModelViewProps) {
|
||||
|
||||
const ctx = useContext(AppContext);
|
||||
const model = ctx.cadRegistry.find(props.modelId);
|
||||
const [attrs, patchAttrs] = useStreamWithPatcher(ctx => ctx.attributesService.streams.get(props.modelId));
|
||||
|
||||
const DO_NOTHING = ()=>{};
|
||||
return <InnerFolder title={props.type + ' ' + model.id} closable titleClass='bg-color-3'>
|
||||
<Stack className='bg-color-4'>
|
||||
<Field active={false} name='label' onFocus={DO_NOTHING} onClick={DO_NOTHING}>
|
||||
<Label>Label</Label>
|
||||
<TextControl value={attrs.label||''} onChange={val => patchAttrs(attrs => attrs.label = val)}/>
|
||||
</Field>
|
||||
<Field active={false} name='label' onFocus={DO_NOTHING} onClick={DO_NOTHING}>
|
||||
<Label>Color</Label>
|
||||
<ColorControl
|
||||
dialogTitle={`Color for ${props.type} ${props.modelId}`}
|
||||
value={attrs.color} onChange={val => patchAttrs(attrs => attrs.color = val)}/>
|
||||
</Field>
|
||||
</Stack>
|
||||
</InnerFolder>;
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import SketcherOperationWizard from "../../../sketcher/components/SketcherOperat
|
|||
import {ToastContainer} from "react-toastify";
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import {ContributedComponents} from "./ContributedComponents";
|
||||
import {SceneInlineObjectExplorer} from "cad/craft/ui/SceneInlineObjectExplorer";
|
||||
|
||||
export default class View3d extends React.Component {
|
||||
|
||||
|
|
@ -41,7 +42,11 @@ export default class View3d extends React.Component {
|
|||
</div>
|
||||
|
||||
<div className={ls.middleSection + ' small-typography'}>
|
||||
<SketcherMode>
|
||||
<SketcherMode whenOff={
|
||||
<div className={ls.overlayingPanel} >
|
||||
<SceneInlineObjectExplorer />
|
||||
</div>
|
||||
}>
|
||||
<InplaceSketcher>
|
||||
<div className={ls.overlayingPanel} >
|
||||
<Scope><SketchObjectExplorer /></Scope>
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.overlayingPanel {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import * as LocationPlugin from "../location/LocationPlugin";
|
|||
import * as AssemblyPlugin from "../assembly/assemblyPlugin";
|
||||
import {WorkbenchesLoaderPlugin} from "cad/workbench/workbenchesLoaderPlugin";
|
||||
import {PluginSystem} from "plugable/pluginSystem";
|
||||
import {AttributesPlugin} from "cad/attributes/attributesPlugin";
|
||||
|
||||
export default function startApplication(callback) {
|
||||
|
||||
|
|
@ -83,7 +84,8 @@ export default function startApplication(callback) {
|
|||
AssemblyPlugin,
|
||||
RemotePartsPlugin,
|
||||
ViewSyncPlugin,
|
||||
WizardSelectionPlugin
|
||||
WizardSelectionPlugin,
|
||||
AttributesPlugin
|
||||
];
|
||||
|
||||
let allPlugins = [...preUIPlugins, ...plugins];
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export class MEdge extends MObject {
|
|||
tan = he.tangentAtStart();
|
||||
origin = he.vertexA.point;
|
||||
} else {
|
||||
tan = he.tangentAtEnd();
|
||||
tan = he.tangentAtEnd().negate();
|
||||
origin = he.vertexB.point;
|
||||
}
|
||||
return new Axis(origin, tan);
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ export class MFace extends MObject {
|
|||
|
||||
get productionInfo() {
|
||||
if (this._productionInfo === undefined) {
|
||||
this._productionInfo = !this.brepFace.data.productionInfo ? null :
|
||||
this._productionInfo = !this.brepFace?.data?.productionInfo ? null :
|
||||
ProductionInfo.fromRawData(this.brepFace.data.productionInfo);
|
||||
}
|
||||
return this._productionInfo;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {MShell} from '../model/mshell';
|
|||
import {MDatum} from '../model/mdatum';
|
||||
import DatumView from './views/datumView';
|
||||
import {View} from './views/view';
|
||||
import {SketchingView} from "cad/scene/views/faceView";
|
||||
|
||||
export function activate(context) {
|
||||
let {streams} = context;
|
||||
|
|
@ -14,7 +15,8 @@ export function activate(context) {
|
|||
streams.sketcher.update.attach(mFace => mFace.ext.view.updateSketch());
|
||||
}
|
||||
|
||||
function sceneSynchronizer({services: {cadScene, cadRegistry, viewer, wizard, action, pickControl}}) {
|
||||
function sceneSynchronizer(ctx) {
|
||||
const {services: {cadScene, cadRegistry, viewer, wizard, action, pickControl}} = ctx;
|
||||
return function() {
|
||||
let wgChildren = cadScene.workGroup.children;
|
||||
let existent = new Set();
|
||||
|
|
@ -48,6 +50,22 @@ function sceneSynchronizer({services: {cadScene, cadRegistry, viewer, wizard, ac
|
|||
} else {
|
||||
console.warn('unsupported model ' + model);
|
||||
}
|
||||
const attrStream = ctx.attributesService.streams.get(model.id);
|
||||
const disposer = attrStream.attach(attrs => {
|
||||
modelView.rootGroup.visible = !attrs.hidden
|
||||
viewer.requestRender();
|
||||
});
|
||||
|
||||
modelView.traverse(view => {
|
||||
if (view instanceof SketchingView) {
|
||||
const stream = ctx.attributesService.streams.get(view.model.id);
|
||||
modelView.addDisposer(stream.attach(attr => {
|
||||
view.setColor(attr.color);
|
||||
viewer.requestRender();
|
||||
}))
|
||||
}
|
||||
});
|
||||
modelView.addDisposer(disposer)
|
||||
SceneGraph.addToGroup(cadScene.workGroup, modelView.rootGroup);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ export class EdgeView extends CurveBasedView {
|
|||
constructor(edge) {
|
||||
let brepEdge = edge.brepEdge;
|
||||
let tess = brepEdge.data.tessellation ? brepEdge.data.tessellation : brepEdge.curve.tessellateToData();
|
||||
super(edge, tess, 2, 3, 0x2B3856, 0xd1726c);
|
||||
super(edge, tess, 2, 3, 0x2B3856, 0xc42720);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,15 +69,15 @@ export class FaceView extends SketchingView {
|
|||
}
|
||||
|
||||
mark(color) {
|
||||
this.setColor(color || SELECTION_COLOR);
|
||||
this.updateColor(color || SELECTION_COLOR);
|
||||
}
|
||||
|
||||
withdraw(color) {
|
||||
this.setColor(null);
|
||||
this.updateColor(null);
|
||||
}
|
||||
|
||||
setColor(color) {
|
||||
setFacesColor(this.meshFaces, color);
|
||||
|
||||
updateColor(color) {
|
||||
setFacesColor(this.meshFaces, color||this.color);
|
||||
this.geometry.colorsNeedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -93,4 +93,4 @@ export function setFacesColor(faces, color) {
|
|||
}
|
||||
|
||||
export const NULL_COLOR = new THREE.Color();
|
||||
export const SELECTION_COLOR = 0xFAFAD2;
|
||||
export const SELECTION_COLOR = 0xffff80;
|
||||
|
|
|
|||
|
|
@ -28,14 +28,14 @@ export class OpenFaceView extends SketchingView {
|
|||
super(mFace);
|
||||
this.material = new THREE.MeshPhongMaterial({
|
||||
vertexColors: THREE.FaceColors,
|
||||
color: 0xB0C4DE,
|
||||
// color: 0xB0C4DE,
|
||||
shininess: 0,
|
||||
polygonOffset : true,
|
||||
polygonOffsetFactor : 1,
|
||||
polygonOffsetUnits : 2,
|
||||
side : THREE.DoubleSide,
|
||||
transparent: true,
|
||||
opacity: 0.5
|
||||
opacity: 0.3
|
||||
});
|
||||
this.updateBounds();
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ export class OpenFaceView extends SketchingView {
|
|||
}
|
||||
|
||||
updateBounds() {
|
||||
let markedColor = this.color;
|
||||
let markedColor = this.markedColor;
|
||||
this.dropGeometry();
|
||||
|
||||
let bounds2d = [];
|
||||
|
|
@ -72,9 +72,11 @@ export class OpenFaceView extends SketchingView {
|
|||
surface.northEastPoint(), surface.northWestPoint()];
|
||||
|
||||
this.createGeometry();
|
||||
if (markedColor) {
|
||||
this.mark(markedColor);
|
||||
}
|
||||
this.updateColor(markedColor || this.color);
|
||||
}
|
||||
|
||||
traverse(visitor) {
|
||||
super.traverse(visitor);
|
||||
}
|
||||
|
||||
updateSketch() {
|
||||
|
|
@ -83,19 +85,19 @@ export class OpenFaceView extends SketchingView {
|
|||
}
|
||||
|
||||
mark(color) {
|
||||
this.setColor(color || SELECTION_COLOR);
|
||||
this.updateColor(color || SELECTION_COLOR);
|
||||
}
|
||||
|
||||
withdraw(color) {
|
||||
this.setColor(null);
|
||||
this.updateColor(null);
|
||||
}
|
||||
|
||||
setColor(color) {
|
||||
setFacesColor(this.mesh.geometry.faces, color);
|
||||
updateColor(color) {
|
||||
setFacesColor(this.mesh.geometry.faces, color||this.color);
|
||||
this.mesh.geometry.colorsNeedUpdate = true;
|
||||
}
|
||||
|
||||
get color() {
|
||||
get markedColor() {
|
||||
let face = this.mesh && this.mesh.geometry && this.mesh.geometry.faces[0];
|
||||
if (face) {
|
||||
return face.color === NULL_COLOR ? null : face.color;
|
||||
|
|
|
|||
|
|
@ -71,6 +71,13 @@ export class ShellView extends View {
|
|||
this.faceViews.forEach(faceView => faceView.setColor(null));
|
||||
}
|
||||
|
||||
traverse(visitor) {
|
||||
super.traverse(visitor);
|
||||
this.faceViews.forEach(f => f.traverse(visitor));
|
||||
this.edgeViews.forEach(e => e.traverse(visitor));
|
||||
this.vertexViews.forEach(e => e.traverse(visitor));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.mesh.material.dispose();
|
||||
this.mesh.geometry.dispose();
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import {CurveBasedView} from './curveBasedView';
|
|||
export class SketchObjectView extends CurveBasedView {
|
||||
|
||||
constructor(mSketchObject, sketchToWorldTransformation) {
|
||||
const color = mSketchObject.construction ? 0x777777 : 0xFFFFFF;
|
||||
const color = mSketchObject.construction ? 0x777777 : 0x0000FF;
|
||||
const tess = mSketchObject.sketchPrimitive.tessellate(10).map(sketchToWorldTransformation.apply).map(v => v.data());
|
||||
super(mSketchObject, tess, 1, 2, color, 0x49FFA5);
|
||||
super(mSketchObject, tess, 3, 4, color, 0x49FFA5);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import {createFunctionList} from "gems/func";
|
||||
import {Color} from "three";
|
||||
|
||||
export class View {
|
||||
|
||||
static MARKER = 'ModelView';
|
||||
|
||||
|
||||
disposers = createFunctionList();
|
||||
color = new Color();
|
||||
|
||||
constructor(model) {
|
||||
this.model = model;
|
||||
model.ext.view = this;
|
||||
|
|
@ -15,8 +21,25 @@ export class View {
|
|||
|
||||
withdraw(priority) {
|
||||
}
|
||||
|
||||
|
||||
setColor(color) {
|
||||
if (!color) {
|
||||
this.color = new Color();
|
||||
} else {
|
||||
this.color.setStyle(color);
|
||||
}
|
||||
}
|
||||
|
||||
traverse(visitor) {
|
||||
visitor(this);
|
||||
}
|
||||
|
||||
addDisposer(disposer) {
|
||||
this.disposers.add(disposer);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposers.call();
|
||||
this.model.ext.view = null;
|
||||
this.model = null;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export class SceneSolid {
|
|||
export function createSolidMaterial(skin) {
|
||||
return new THREE.MeshPhongMaterial(Object.assign({
|
||||
vertexColors: THREE.FaceColors,
|
||||
color: 0xB0C4DE,
|
||||
// color: 0xB0C4DE,
|
||||
shininess: 0,
|
||||
polygonOffset : true,
|
||||
polygonOffsetFactor : 1,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import {useStream} from "ui/effects";
|
||||
|
||||
export default function SketcherMode({children}) {
|
||||
export default function SketcherMode({children, whenOff}) {
|
||||
|
||||
const visible = useStream(ctx => ctx.streams.sketcher.sketchingMode);
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
return whenOff;
|
||||
}
|
||||
|
||||
return children;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import OperationHistory from '../craft/ui/OperationHistory';
|
|||
import Expressions from '../expressions/Expressions';
|
||||
import {SelectionView} from "../dom/components/SelectionView";
|
||||
import {GrSelect} from "react-icons/gr";
|
||||
import {Explorer} from "cad/dom/components/Explorer";
|
||||
|
||||
export function activate(ctx) {
|
||||
const {services, streams} = ctx;
|
||||
|
|
@ -28,7 +29,7 @@ export function activate(ctx) {
|
|||
|
||||
services.menu.registerMenus(menuConfig);
|
||||
|
||||
services.ui.registerFloatView('project', ObjectExplorer, 'Model', 'cubes');
|
||||
services.ui.registerFloatView('project', Explorer, 'Model', 'cubes');
|
||||
services.ui.registerFloatView('history', OperationHistory, 'Modifications', 'history');
|
||||
services.ui.registerFloatView('expressions', Expressions, 'Expressions', 'percent');
|
||||
services.ui.registerFloatView('selection', SelectionView, 'Selection', GrSelect);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import "inSceneUI";
|
||||
@import "inSceneUI-sketcher";
|
||||
|
||||
.objectItem {
|
||||
background-color: rgba(0,0,0, 0.6);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@import "inSceneUI";
|
||||
@import "inSceneUI-sketcher";
|
||||
|
||||
.objectItem {
|
||||
background-color: rgba(0,0,0, 0.6);
|
||||
|
|
|
|||
Loading…
Reference in a new issue