refactor storage plugin - type safety

This commit is contained in:
Val Erastov (xibyte) 2020-05-16 22:22:02 -07:00
parent 5493cd0edd
commit 41ca9a51e8
26 changed files with 456 additions and 359 deletions

View file

@ -1,8 +0,0 @@
import Bus from '../bus';
export default {
services: {},
streams: {},
//@deprecated
bus: new Bus()
};

13
modules/context/index.ts Normal file
View file

@ -0,0 +1,13 @@
export interface ApplicationContext {
services: any,
streams: any,
}
export default {
services: {},
streams: {}
} as ApplicationContext;

View file

@ -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,

View file

@ -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[]>;

View file

@ -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(() => {

View file

@ -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
};

View file

@ -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;
}

View file

@ -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)
})
});

View file

@ -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);
}
}

View file

@ -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')
};

View 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;
}
}

View file

@ -1,3 +0,0 @@
import React from "react";
export const AppContext = React.createContext({});

View file

@ -0,0 +1,4 @@
import React from "react";
import {ApplicationContext} from "context";
export const AppContext: React.Context<ApplicationContext> = React.createContext(null);

View file

@ -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%'}}/>
}

View file

@ -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
};
}

View 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>;
}

View file

@ -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>
}

View file

@ -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();
}

View file

@ -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';

View 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);
}
}

View 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>;
}

View 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);
}
}

View file

@ -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)
}
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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;