mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-09 18:02:50 +01:00
refactor storage plugin - type safety
This commit is contained in:
parent
5493cd0edd
commit
41ca9a51e8
26 changed files with 456 additions and 359 deletions
|
|
@ -1,8 +0,0 @@
|
|||
import Bus from '../bus';
|
||||
|
||||
export default {
|
||||
services: {},
|
||||
streams: {},
|
||||
//@deprecated
|
||||
bus: new Bus()
|
||||
};
|
||||
13
modules/context/index.ts
Normal file
13
modules/context/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export interface ApplicationContext {
|
||||
|
||||
services: any,
|
||||
streams: any,
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
services: {},
|
||||
streams: {}
|
||||
|
||||
} as ApplicationContext;
|
||||
|
||||
|
|
@ -21,6 +21,7 @@ export function DocumentationWindow() {
|
|||
const [content, setContent] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
window.__CAD_APP.DocumentationTopic$ = DocumentationTopic$;
|
||||
if (!request) {
|
||||
setContent(null);
|
||||
|
|
@ -76,6 +77,7 @@ export function DocumentationWindow() {
|
|||
|
||||
}
|
||||
DocumentationUIState$.throttle(3000).attach(el => {
|
||||
// @ts-ignore
|
||||
const rect = el.getBoundingClientRect();
|
||||
sessionStorage.setItem('DocumentationWindow', JSON.stringify({
|
||||
width: rect.width,
|
||||
|
|
|
|||
27
modules/lstream/index.d.ts
vendored
27
modules/lstream/index.d.ts
vendored
|
|
@ -1,6 +1,9 @@
|
|||
interface StreamBase<T> {
|
||||
|
||||
interface Observable<T> {
|
||||
attach(callback: (value: T) => any): () => void
|
||||
}
|
||||
|
||||
interface Stream<T> extends Observable<T> {
|
||||
|
||||
map<T, V>(fn: (value: T) => V);
|
||||
|
||||
|
|
@ -19,23 +22,25 @@ interface StreamBase<T> {
|
|||
pipe(otherStream): () => void;
|
||||
}
|
||||
|
||||
interface Stream<T> extends StreamBase<T> {
|
||||
|
||||
|
||||
}
|
||||
|
||||
interface StateStream<T> extends Stream<T> {
|
||||
|
||||
value: T;
|
||||
interface Emitter<T> extends Stream<T> {
|
||||
|
||||
next(value?: T) : void;
|
||||
|
||||
}
|
||||
|
||||
interface StateStream<T> extends Emitter<T> {
|
||||
|
||||
export function stream<T>(): Stream<T>;
|
||||
value: T;
|
||||
|
||||
export function eventStream<T>(): Stream<T>;
|
||||
update(updater: (T) => T): void;
|
||||
|
||||
mutate(mutator: (T) => void): void;
|
||||
}
|
||||
|
||||
|
||||
export function stream<T>(): Emitter<T>;
|
||||
|
||||
export function eventStream<T>(): Emitter<T>;
|
||||
|
||||
export function combine(...streams: Stream<any>[]): Stream<any[]>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import {StreamBase} from './base';
|
||||
import {Emitter} from './emitter';
|
||||
|
||||
export class ThrottleStream extends StreamBase {
|
||||
|
||||
|
|
@ -13,7 +12,7 @@ export class ThrottleStream extends StreamBase {
|
|||
attach(observer) {
|
||||
let scheduled = false;
|
||||
let value = undefined;
|
||||
this.stream.attach(val => {
|
||||
return this.stream.attach(val => {
|
||||
value = this.accumulator(val);
|
||||
if (!scheduled) {
|
||||
setTimeout(() => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import React from 'react';
|
||||
import React, {useContext, useEffect, useState} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ls from './Menu.less';
|
||||
import AuxWidget from "./AuxWidget";
|
||||
import cx from 'classnames';
|
||||
import Fa from './Fa';
|
||||
import {UISystemContext} from "../../../web/app/cad/dom/components/UISystem";
|
||||
import {useStream} from "../effects";
|
||||
|
||||
export default function Menu({children, x, y, orientationUp, centered, menuId, ...props}) {
|
||||
return <AuxWidget
|
||||
|
|
@ -24,7 +26,9 @@ export function MenuSeparator() {
|
|||
}
|
||||
|
||||
|
||||
export function MenuItem({icon, label, hotKey, style, disabled, onClick, children, ...props}, {closeAllUpPopups}) {
|
||||
export function MenuItem({icon, label, hotKey, style, disabled, onClick, children, ...props}) {
|
||||
|
||||
const {closeAllUpPopups} = useContext(UISystemContext);
|
||||
|
||||
if (hotKey) {
|
||||
hotKey = hotKey.replace(/\s/g, '');
|
||||
|
|
@ -44,69 +48,46 @@ export function MenuItem({icon, label, hotKey, style, disabled, onClick, childre
|
|||
</div>;
|
||||
}
|
||||
|
||||
export class ContextMenu extends React.Component {
|
||||
const ContextMenuContext = React.createContext(null);
|
||||
|
||||
state = {
|
||||
export function ContextMenu(props) {
|
||||
|
||||
const [state, setState] = useState({
|
||||
active: false
|
||||
};
|
||||
});
|
||||
|
||||
onClick = e => {
|
||||
const {onCloseAll} = useContext(UISystemContext);
|
||||
|
||||
useEffect(() => onCloseAll.attach(close));
|
||||
|
||||
const onClick = e => {
|
||||
e.preventDefault();
|
||||
this.setState({
|
||||
setState({
|
||||
active: true,
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
});
|
||||
};
|
||||
|
||||
close = () => {
|
||||
this.setState({active: false})
|
||||
const close = () => {
|
||||
setState({active: false})
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.detacher = this.context.onCloseAll.attach(this.close);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.detacher();
|
||||
}
|
||||
|
||||
render() {
|
||||
return <span className={ls.contextMenu}>
|
||||
<span onContextMenu={this.onClick}>{this.props.children}</span>
|
||||
<span onClick={this.onClick} className={ls.contextMenuBtn}><Fa fw icon='ellipsis-h'/></span>
|
||||
{this.state.active && <Menu x={this.state.x} y={this.state.y}>
|
||||
{this.props.items}
|
||||
return <ContextMenuContext.Provider value={close}>
|
||||
<span className={ls.contextMenu}>
|
||||
<span onContextMenu={onClick}>{props.children}</span>
|
||||
<span onClick={onClick} className={ls.contextMenuBtn}><Fa fw icon='ellipsis-h'/></span>
|
||||
{state.active && <Menu x={state.x} y={state.y}>
|
||||
{props.items}
|
||||
</Menu>}
|
||||
</span>
|
||||
}
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
closeMenu: this.close
|
||||
};
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
onCloseAll: PropTypes.object
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
closeMenu: PropTypes.func
|
||||
};
|
||||
</ContextMenuContext.Provider>;
|
||||
}
|
||||
|
||||
export function ContextMenuItem({onClick, ...props}, {closeMenu}) {
|
||||
export function ContextMenuItem({onClick, ...props}) {
|
||||
const closeMenu = useContext(ContextMenuContext);
|
||||
return <MenuItem onClick={() => {
|
||||
closeMenu();
|
||||
onClick();
|
||||
}} {...props}/>;
|
||||
}
|
||||
|
||||
ContextMenuItem.contextTypes = {
|
||||
closeMenu: PropTypes.func
|
||||
};
|
||||
|
||||
MenuItem.contextTypes = {
|
||||
closeAllUpPopups: PropTypes.func
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const PROPAGATE_SELF_PROPS = v => v;
|
||||
|
||||
export default function connect(WrappedComponent, tokens, config) {
|
||||
if (!config) {
|
||||
config = DEFAULT_CONFIG;
|
||||
}
|
||||
let {staticProps, mapProps, mapActions, mapSelfProps} = config;
|
||||
|
||||
mapProps = createMapper(mapProps);
|
||||
|
||||
mapActions = mapActions || function({dispatch}) {
|
||||
return dispatch;
|
||||
};
|
||||
|
||||
mapSelfProps = PROPAGATE_SELF_PROPS;
|
||||
|
||||
return class StateConnector extends React.PureComponent {
|
||||
|
||||
constructor(props, {bus}) {
|
||||
super();
|
||||
this.mounted = false;
|
||||
this.stateProps = {};
|
||||
this.dispatchProps = mapActions(bus.externalAPI, props);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.externalStateConnection = this.context.bus.connectToState(this.getTokens(), this.setExternalState);
|
||||
this.externalStateConnection();
|
||||
}
|
||||
|
||||
getTokens() {
|
||||
let tokensArr = tokens instanceof Function ? tokens(this.props) : tokens;
|
||||
if (!Array.isArray(tokensArr)) {
|
||||
tokensArr = [tokensArr];
|
||||
}
|
||||
return tokensArr;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
this.context.bus.disconnectFromState(this.externalStateConnection);
|
||||
}
|
||||
|
||||
setExternalState = (state) => {
|
||||
this.stateProps = mapProps(state, this.props);
|
||||
if (this.mounted) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <WrappedComponent {...this.stateProps}
|
||||
{...this.dispatchProps}
|
||||
{...staticProps}
|
||||
{...mapSelfProps(this.props)}/>
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
bus: PropTypes.object
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function createMapper(mapper, comp) {
|
||||
if (!mapper) {
|
||||
return DEFAULT_MAPPER;
|
||||
} else if (Array.isArray(mapper)) {
|
||||
return function (state) {
|
||||
let props = {};
|
||||
for (let i = 0; i < state.length; i++) {
|
||||
let stateItem = state[i];
|
||||
let mapperItem = mapper[i];
|
||||
Object.assign(props, mapperItem ? mapperItem(stateItem) : stateItem)
|
||||
}
|
||||
return props;
|
||||
};
|
||||
}
|
||||
return mapper;
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG = {};
|
||||
|
||||
export function DEFAULT_MAPPER(state) {
|
||||
let props = {};
|
||||
state.forEach(stateItem => Object.assign(props, stateItem));
|
||||
return props;
|
||||
}
|
||||
|
|
@ -1,15 +1,11 @@
|
|||
import React from 'react';
|
||||
import Window from 'ui/components/Window';
|
||||
import BrepDebugger from './brepDebugger';
|
||||
import connect, {PROPAGATE_SELF_PROPS} from 'ui/connectLegacy';
|
||||
import {addToGroup, clearGroup, createGroup, removeFromGroup} from 'scene/sceneGraph';
|
||||
import {createToken} from 'bus';
|
||||
import Fa from 'ui/components/Fa';
|
||||
|
||||
import ls from './BrepDebuggerWindow.less';
|
||||
|
||||
export const BREP_DEBUG_WINDOW_VISIBLE = createToken('debug', 'brepDebugWindowVisible')
|
||||
|
||||
class BrepDebuggerWindow extends React.Component {
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
|
|
@ -35,10 +31,3 @@ class BrepDebuggerWindow extends React.Component {
|
|||
</Window>;
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(BrepDebuggerWindow, BREP_DEBUG_WINDOW_VISIBLE, {
|
||||
mapProps: ([visible]) => ({visible}),
|
||||
mapActions: ({dispatch}) => ({
|
||||
close: () => dispatch(BREP_DEBUG_WINDOW_VISIBLE, false)
|
||||
})
|
||||
});
|
||||
|
|
@ -7,13 +7,13 @@ import * as vec from '../math/vec';
|
|||
import React from 'react';
|
||||
import {readSketchFloat} from './sketch/sketchReader';
|
||||
import {toLoops} from '../brep/io/brepLoopsFormat';
|
||||
import {contributeComponent} from './dom/components/ContributedComponents';
|
||||
import BrepDebuggerWindow, {BREP_DEBUG_WINDOW_VISIBLE} from '../brep/debug/debugger/BrepDebuggerWindow';
|
||||
import curveTess from '../brep/geom/impl/curve/curve-tess';
|
||||
import {LOG_FLAGS} from './logFlags';
|
||||
import {state} from "lstream";
|
||||
|
||||
const BREP_DEBUG_WINDOW_VISIBLE$ = state(false);
|
||||
|
||||
export function activate({bus, services, streams}) {
|
||||
export function activate({services, streams}) {
|
||||
addGlobalDebugActions(services);
|
||||
addDebugSelectors(services);
|
||||
services.action.registerActions(DebugActions);
|
||||
|
|
@ -24,8 +24,7 @@ export function activate({bus, services, streams}) {
|
|||
};
|
||||
streams.ui.controlBars.left.update(actions => [...actions, 'menu.debug']);
|
||||
|
||||
bus.enableState(BREP_DEBUG_WINDOW_VISIBLE, false);
|
||||
contributeComponent(<BrepDebuggerWindow key='debug.BrepDebuggerWindow' auxGroup={services.cadScene.auxGroup} />);
|
||||
// contributeComponent(<BrepDebuggerWindow key='debug.BrepDebuggerWindow' auxGroup={services.cadScene.auxGroup} />);
|
||||
}
|
||||
|
||||
function addGlobalDebugActions({viewer, cadScene, cadRegistry}) {
|
||||
|
|
@ -434,7 +433,7 @@ const DebugActions = [
|
|||
info: 'open the BREP debugger in a window',
|
||||
},
|
||||
invoke: ({bus}) => {
|
||||
bus.dispatch(BREP_DEBUG_WINDOW_VISIBLE, true);
|
||||
BREP_DEBUG_WINDOW_VISIBLE$.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
import {createToken} from 'bus';
|
||||
|
||||
export function activate({services, bus}) {
|
||||
bus.enableState(TOKENS.TABS, {
|
||||
tabs: [],
|
||||
activeTab: -1
|
||||
});
|
||||
|
||||
let detachedViews = {};
|
||||
|
||||
function show(id, label, url) {
|
||||
let index = bus.state[TOKENS.TABS].tabs.findIndex(tab => tab.id === id);
|
||||
if (index === -1) {
|
||||
let detachedView = detachedViews[id];
|
||||
if (detachedView !== undefined) {
|
||||
if (!detachedView.closed) {
|
||||
detachedView.focus();
|
||||
return;
|
||||
} else {
|
||||
delete detachedViews[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
bus.updateState(TOKENS.TABS, ({tabs, activeTab}) => {
|
||||
if (index === -1) {
|
||||
return {
|
||||
activeTab: tabs.length,
|
||||
tabs: [...tabs, {
|
||||
id, label, url
|
||||
}],
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
tabs,
|
||||
activeTab: index
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bus.subscribe(TOKENS.DETACH, index => {
|
||||
let {id, url} = bus.state[TOKENS.TABS].tabs[index];
|
||||
detachedViews[id] = window.open(url, id, "height=900,width=1200")
|
||||
bus.updateState(TOKENS.TABS, ({tabs}) => {
|
||||
tabs.splice(index, 1);
|
||||
return {
|
||||
tabs,
|
||||
activeTab: -1
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
services.appTabs = {
|
||||
show
|
||||
}
|
||||
}
|
||||
|
||||
export const TOKENS = {
|
||||
TABS: createToken('appTabs', 'tabs'),
|
||||
OPEN: createToken('appTabs', 'open'),
|
||||
DETACH: createToken('appTabs', 'detach')
|
||||
};
|
||||
86
web/app/cad/dom/appTabsPlugin.ts
Normal file
86
web/app/cad/dom/appTabsPlugin.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import {state, StateStream} from "lstream";
|
||||
import {ApplicationContext} from "context";
|
||||
|
||||
export function activate(ctx: ApplicationContext) {
|
||||
|
||||
const tabs$ = state<AppTabsState>({
|
||||
tabs: [] as AppTab[],
|
||||
activeTab: -1
|
||||
});
|
||||
|
||||
const detachedViews = {};
|
||||
|
||||
function show(id, label, url) {
|
||||
let index = tabs$.value.tabs.findIndex(tab => tab.id === id);
|
||||
if (index === -1) {
|
||||
let detachedView = detachedViews[id];
|
||||
if (detachedView !== undefined) {
|
||||
if (!detachedView.closed) {
|
||||
detachedView.focus();
|
||||
return;
|
||||
} else {
|
||||
delete detachedViews[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
tabs$.update(({tabs, activeTab}) => {
|
||||
if (index === -1) {
|
||||
return {
|
||||
activeTab: tabs.length,
|
||||
tabs: [...tabs, {
|
||||
id, label, url
|
||||
}],
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
tabs,
|
||||
activeTab: index
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function detach(index) {
|
||||
let {id, url} = tabs$.value.tabs[index];
|
||||
detachedViews[id] = window.open(url, id, "height=900,width=1200")
|
||||
tabs$.update(({tabs}) => {
|
||||
tabs.splice(index, 1);
|
||||
return {
|
||||
tabs,
|
||||
activeTab: -1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ctx.appTabsService = {
|
||||
show, detach, tabs$
|
||||
}
|
||||
}
|
||||
|
||||
export interface AppTab {
|
||||
id: string;
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface AppTabsState {
|
||||
tabs: AppTab[];
|
||||
activeTab: number;
|
||||
}
|
||||
|
||||
export interface AppTabsService {
|
||||
|
||||
tabs$: StateStream<AppTabsState>;
|
||||
|
||||
show(id:string, label:string, url:string);
|
||||
|
||||
detach(index: number)
|
||||
}
|
||||
|
||||
declare module 'context' {
|
||||
interface ApplicationContext {
|
||||
|
||||
appTabsService: AppTabsService;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
export const AppContext = React.createContext({});
|
||||
4
web/app/cad/dom/components/AppContext.ts
Normal file
4
web/app/cad/dom/components/AppContext.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import React from "react";
|
||||
import {ApplicationContext} from "context";
|
||||
|
||||
export const AppContext: React.Context<ApplicationContext> = React.createContext(null);
|
||||
|
|
@ -1,16 +1,31 @@
|
|||
import React, {Fragment} from 'react';
|
||||
import React, {useContext} from 'react';
|
||||
|
||||
import View3d from './View3d';
|
||||
|
||||
import ls from './AppTabs.less';
|
||||
|
||||
import TabSwitcher, {Tab} from 'ui/components/TabSwticher';
|
||||
import connect from 'ui/connectLegacy';
|
||||
|
||||
import {TOKENS as APP_TABS_TOKENS} from "../appTabsPlugin";
|
||||
import Card from "ui/components/Card";
|
||||
import {useStreamWithUpdater} from "../../../../../modules/ui/effects";
|
||||
import {AppContext} from "./AppContext";
|
||||
|
||||
export default function AppTabs({}) {
|
||||
|
||||
const [{tabs, activeTab}, updateTabs] = useStreamWithUpdater(ctx => ctx.appTabsService.tabs$);
|
||||
const ctx = useContext(AppContext);
|
||||
|
||||
const switchTo = index => updateTabs(({tabs}) => ({tabs, activeTab: index}));
|
||||
|
||||
const close = index => updateTabs(({activeTab, tabs}) => {
|
||||
tabs.splice(index, 1);
|
||||
return {
|
||||
activeTab: (activeTab === index ? -1 : activeTab),
|
||||
tabs
|
||||
};
|
||||
});
|
||||
|
||||
const detach = index => ctx.detach(index);
|
||||
|
||||
function AppTabs({activeTab, tabs, switchTo, close, detach}) {
|
||||
return <div className={ls.root}>
|
||||
|
||||
<div className={ls.content}>
|
||||
|
|
@ -44,20 +59,6 @@ function AppTabs({activeTab, tabs, switchTo, close, detach}) {
|
|||
</div>
|
||||
}
|
||||
|
||||
export default connect(AppTabs, APP_TABS_TOKENS.TABS, {
|
||||
mapActions: ({dispatch, updateState}) => ({
|
||||
switchTo: index => updateState(APP_TABS_TOKENS.TABS, ({tabs}) => ({tabs, activeTab: index})),
|
||||
close: index => updateState(APP_TABS_TOKENS.TABS, ({activeTab, tabs}) => {
|
||||
tabs.splice(index, 1);
|
||||
return {
|
||||
activeTab: (activeTab === index ? -1 : activeTab),
|
||||
tabs
|
||||
};
|
||||
}),
|
||||
detach: index => dispatch(APP_TABS_TOKENS.DETACH, index)
|
||||
})
|
||||
});
|
||||
|
||||
export function FrameView({url}) {
|
||||
return <iframe src={url} style={{width: '100%', height: '100%'}}/>
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MenuHolder from '../menu/MenuHolder';
|
||||
|
||||
import ActionInfo from '../actionInfo/ActionInfo';
|
||||
import ContributedComponents from './ContributedComponents';
|
||||
import {stream} from 'lstream';
|
||||
import {DocumentationWindow} from 'doc/DocumentationWindow';
|
||||
import {Scope} from "../../../sketcher/components/Scope";
|
||||
import {ContextualControls} from "../../../sketcher/components/ContextualControls";
|
||||
import {ConstraintEditor} from "../../../sketcher/components/ConstraintEditor";
|
||||
import SketcherOperationWizard from "../../../sketcher/components/SketcherOperationWizard";
|
||||
import {StageControl} from "../../../sketcher/components/StageControl";
|
||||
|
||||
export default class UISystem extends React.Component {
|
||||
|
||||
onCloseAll = stream();
|
||||
|
||||
render() {
|
||||
return <div {...this.props} onMouseDown={this.closeAllUpPopups} >
|
||||
<MenuHolder />
|
||||
<ActionInfo />
|
||||
{this.props.children}
|
||||
<ContributedComponents />
|
||||
<Scope><DocumentationWindow /></Scope>
|
||||
</div>
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
closeAllUpPopups = () => {
|
||||
this.context.services.menu.closeAll();
|
||||
this.context.services.action.showHintFor(null);
|
||||
this.onCloseAll.next();
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
closeAllUpPopups: this.closeAllUpPopups,
|
||||
onCloseAll: this.onCloseAll
|
||||
}
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
services: PropTypes.object
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
closeAllUpPopups: PropTypes.func,
|
||||
onCloseAll: PropTypes.object
|
||||
};
|
||||
}
|
||||
|
||||
39
web/app/cad/dom/components/UISystem.tsx
Normal file
39
web/app/cad/dom/components/UISystem.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import React, {useContext} from 'react';
|
||||
import MenuHolder from '../menu/MenuHolder';
|
||||
|
||||
import ActionInfo from '../actionInfo/ActionInfo';
|
||||
import ContributedComponents from './ContributedComponents';
|
||||
import {stream} from 'lstream';
|
||||
import {DocumentationWindow} from 'doc/DocumentationWindow';
|
||||
import {Scope} from "../../../sketcher/components/Scope";
|
||||
import {AppContext} from "./AppContext";
|
||||
|
||||
|
||||
const onCloseAll = stream<void>();
|
||||
|
||||
export const UISystemContext = React.createContext(null);
|
||||
|
||||
export default function UISystem({children, ...props}) {
|
||||
|
||||
const ctx = useContext(AppContext);
|
||||
|
||||
const uiCxt = {
|
||||
closeAllUpPopups: () => {
|
||||
ctx.services.menu.closeAll();
|
||||
ctx.services.action.showHintFor(null);
|
||||
onCloseAll.next();
|
||||
},
|
||||
onCloseAll
|
||||
};
|
||||
|
||||
return <UISystemContext.Provider value={uiCxt}>
|
||||
<div {...props} onMouseDown={uiCxt.closeAllUpPopups}>
|
||||
<MenuHolder/>
|
||||
<ActionInfo/>
|
||||
{children}
|
||||
<ContributedComponents/>
|
||||
<Scope><DocumentationWindow/></Scope>
|
||||
</div>
|
||||
</UISystemContext.Provider>;
|
||||
}
|
||||
|
||||
|
|
@ -1,36 +1,15 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import 'ui/styles/init/index.less';
|
||||
import AppTabs from "./AppTabs";
|
||||
import {StreamsContext} from "../../../../../modules/ui/streamsContext";
|
||||
import {AppContext} from "./AppContext";
|
||||
|
||||
|
||||
export default class WebApplication extends React.Component {
|
||||
|
||||
constructor({appContext}) {
|
||||
super();
|
||||
this.appContext = appContext;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const {appContext} = this.props;
|
||||
return <StreamsContext.Provider value={appContext}>
|
||||
<AppContext.Provider value={appContext}>
|
||||
<AppTabs />
|
||||
</AppContext.Provider>
|
||||
</StreamsContext.Provider>
|
||||
}
|
||||
|
||||
getChildContext() {
|
||||
return this.appContext;
|
||||
}
|
||||
|
||||
static childContextTypes = {
|
||||
bus: PropTypes.object,
|
||||
services: PropTypes.object,
|
||||
streams: PropTypes.object
|
||||
};
|
||||
export default function WebApplication(props) {
|
||||
const {appContext} = props;
|
||||
return <StreamsContext.Provider value={appContext}>
|
||||
<AppContext.Provider value={appContext}>
|
||||
<AppTabs/>
|
||||
</AppContext.Provider>
|
||||
</StreamsContext.Provider>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import {TOKENS as APP_TABS_TOKENS} from "./appTabsPlugin";
|
||||
import {contributeComponent} from './components/ContributedComponents';
|
||||
|
||||
export function activate({bus, services}) {
|
||||
|
||||
services.dom = {
|
||||
export function activate(ctx) {
|
||||
|
||||
ctx.services.dom = {
|
||||
viewerContainer: document.getElementById('viewer-container'),
|
||||
contributeComponent
|
||||
};
|
||||
|
||||
bus.subscribe(APP_TABS_TOKENS.TABS, ({activeTab}) => {
|
||||
|
||||
ctx.appTabsService.tabs$.attach(({activeTab}) => {
|
||||
if (activeTab === 0) {
|
||||
services.viewer.sceneSetup.updateViewportSize();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import * as ExtensionsPlugin from '../craft/extensionsPlugin';
|
|||
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 StoragePlugin from '../storage/storagePlugin';
|
||||
import * as ProjectPlugin from '../projectPlugin';
|
||||
import * as ProjectManagerPlugin from '../projectManager/projectManagerPlugin';
|
||||
import * as SketcherPlugin from '../sketch/sketcherPlugin';
|
||||
|
|
|
|||
80
web/app/cad/repository/browserLocalStorage.ts
Normal file
80
web/app/cad/repository/browserLocalStorage.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import {Storage, StorageUpdateEvent} from "./storage";
|
||||
import {stream} from "lstream";
|
||||
|
||||
|
||||
export class BrowserLocalStorage implements Storage {
|
||||
|
||||
readonly prefix: string;
|
||||
|
||||
private readonly updates$ = stream();
|
||||
private readonly notify: (key) => void;
|
||||
|
||||
constructor(prefix) {
|
||||
this.prefix = prefix;
|
||||
this.notify = (path) => {
|
||||
this.updates$.next({
|
||||
path,
|
||||
timestamp: Date.now
|
||||
});
|
||||
};
|
||||
window.addEventListener('storage', evt => this.notify(evt.key.substring(prefix.length)), false);
|
||||
}
|
||||
|
||||
get(path: string): Promise<string> {
|
||||
try {
|
||||
return Promise.resolve(localStorage.getItem(this.prefix + path));
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
set(path: string, content: string): Promise<void> {
|
||||
try {
|
||||
localStorage.setItem(this.prefix + path, content);
|
||||
this.notify(path);
|
||||
return Promise.resolve();
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
return Promise.resolve(localStorage.hasOwnProperty(this.prefix + path));
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
list(path: string): Promise<string[]> {
|
||||
const namespace = this.prefix + path;
|
||||
try {
|
||||
let keys = [];
|
||||
for(let i = localStorage.length - 1; i >= 0 ; i--) {
|
||||
const key = localStorage.key(i);
|
||||
if (key.startsWith(namespace)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(keys);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
remove(path: string): Promise<void> {
|
||||
try {
|
||||
localStorage.removeItem(this.prefix + path);
|
||||
this.notify(path);
|
||||
return Promise.resolve();
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
attach(callback: (value: StorageUpdateEvent) => any): () => void {
|
||||
return this.updates$.attach(callback);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
20
web/app/cad/repository/storage.ts
Normal file
20
web/app/cad/repository/storage.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import {Observable} from "lstream";
|
||||
|
||||
export interface StorageUpdateEvent {
|
||||
path: string;
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export interface Storage extends Observable<StorageUpdateEvent> {
|
||||
|
||||
set(path: string, content: string): Promise<void>;
|
||||
|
||||
get(path: string): Promise<string>;
|
||||
|
||||
remove(path: string): Promise<void>;
|
||||
|
||||
list(path: string): Promise<string[]>;
|
||||
|
||||
exists(path: string): Promise<boolean>;
|
||||
|
||||
}
|
||||
90
web/app/cad/repository/storageDispatcher.ts
Normal file
90
web/app/cad/repository/storageDispatcher.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
|
||||
|
||||
import {Storage, StorageUpdateEvent} from "./storage";
|
||||
import {stream} from "lstream";
|
||||
|
||||
|
||||
export class StorageDispatcher implements Storage {
|
||||
|
||||
readonly storageMap: {string: Storage};
|
||||
readonly defaultStorage: Storage;
|
||||
readonly updates$ = stream();
|
||||
readonly throttledInterface$ = this.updates$.throttle(100);
|
||||
|
||||
constructor(storageMap, defaultStorage) {
|
||||
this.storageMap = storageMap;
|
||||
this.defaultStorage = defaultStorage;
|
||||
Object.entries(this.storageMap).forEach(([storageId, storage]) => {
|
||||
|
||||
storage.attach(e => {
|
||||
this.updates$.next({...e, path: storageId + ':' + e.path});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
classifyStorage(path: string): [Storage, string] {
|
||||
|
||||
const splitter = path.indexOf(':');
|
||||
if (splitter === -1) {
|
||||
return [this.defaultStorage, path];
|
||||
}
|
||||
const storageId = path.substring(0, splitter);
|
||||
const subPath = path.substring(splitter + 1);
|
||||
const storage = this.storageMap[storageId];
|
||||
if (!storage) {
|
||||
throw new Error('unknown storage reference: ' + storageId);
|
||||
}
|
||||
return [storage, subPath];
|
||||
}
|
||||
|
||||
get(path: string): Promise<string> {
|
||||
try {
|
||||
const [storage, subPath] = this.classifyStorage(path);
|
||||
return storage.get(subPath);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
set(path: string, content: string): Promise<void> {
|
||||
try {
|
||||
const [storage, subPath] = this.classifyStorage(path);
|
||||
storage.set(subPath, content);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
const [storage, subPath] = this.classifyStorage(path);
|
||||
return storage.exists(subPath);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
list(path: string): Promise<string[]> {
|
||||
try {
|
||||
const [storage, subPath] = this.classifyStorage(path);
|
||||
return storage.list(subPath);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
remove(path: string): Promise<void> {
|
||||
try {
|
||||
const [storage, subPath] = this.classifyStorage(path);
|
||||
storage.remove(subPath);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
attach(callback: (value: StorageUpdateEvent) => any): () => void {
|
||||
return this.throttledInterface$.attach(callback);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ export function activate(ctx) {
|
|||
services.viewer.requestRender();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
services.storage.addListener(onSketchUpdate);
|
||||
|
||||
function getAllSketches() {
|
||||
|
|
@ -154,7 +154,7 @@ export function activate(ctx) {
|
|||
function sketchFace2D(face) {
|
||||
updateSketchBoundaries(face);
|
||||
let sketchURL = services.project.getSketchURL(face.id);
|
||||
services.appTabs.show(face.id, 'Sketch ' + face.id, 'sketcher.html#' + sketchURL);
|
||||
ctx.appTabsService.show(face.id, 'Sketch ' + face.id, 'sketcher.html#' + sketchURL);
|
||||
}
|
||||
|
||||
function reassignSketch(fromId, toId) {
|
||||
|
|
@ -190,3 +190,4 @@ export function activate(ctx) {
|
|||
reassignSketchMode: initReassignSketchMode(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {stream} from '../../../modules/lstream';
|
||||
import {stream} from 'lstream';
|
||||
import {ApplicationContext} from "context";
|
||||
|
||||
const updates$ = stream();
|
||||
|
||||
|
|
@ -8,7 +9,9 @@ export function defineStreams(ctx) {
|
|||
}
|
||||
}
|
||||
|
||||
export function activate({services, streams}) {
|
||||
export function activate(ctx: ApplicationContext) {
|
||||
|
||||
const {services, streams} = ctx;
|
||||
|
||||
function set(key, value) {
|
||||
console.log("Saving: " + key);
|
||||
|
|
@ -54,7 +57,35 @@ export function activate({services, streams}) {
|
|||
|
||||
const addListener = listener => streams.storage.update.attach(listener);
|
||||
|
||||
services.storage = {
|
||||
ctx.storageService = {
|
||||
set, get, remove, addListener, getAllKeysFromNamespace, exists
|
||||
};
|
||||
|
||||
services.storage = ctx.storageService;
|
||||
|
||||
}
|
||||
|
||||
|
||||
export interface StorageService {
|
||||
|
||||
set(path: string, content: string): void;
|
||||
|
||||
get(path: string): string;
|
||||
|
||||
remove(path: string): void;
|
||||
|
||||
getAllKeysFromNamespace(path: string): string[];
|
||||
|
||||
exists(path: string): boolean;
|
||||
|
||||
addListener(callback: (StorageUpdateEvent) => void);
|
||||
|
||||
}
|
||||
|
||||
declare module 'context' {
|
||||
interface ApplicationContext {
|
||||
|
||||
storageService: StorageService;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +101,7 @@ export class IO {
|
|||
if (sketch.boundary) {
|
||||
this.createBoundaryObjects(sketch.boundary);
|
||||
}
|
||||
this.viewer.createGroundObjects();
|
||||
|
||||
if (sketch.version !== 3) {
|
||||
return;
|
||||
|
|
@ -196,7 +197,6 @@ export class IO {
|
|||
}
|
||||
|
||||
} finally {
|
||||
this.viewer.createGroundObjects();
|
||||
this.viewer.parametricManager.finishTransaction();
|
||||
this.viewer.parametricManager.notify();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ export class Viewer {
|
|||
|
||||
this.toolManager = new ToolManager(this, new PanTool(this));
|
||||
this.parametricManager = new ParametricManager(this);
|
||||
this.createGroundObjects();
|
||||
|
||||
this.translate = {x: 0.0, y: 0.0};
|
||||
this.scale = 1.0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue