mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 08:25:19 +01:00
improve wizard react integration
This commit is contained in:
parent
4d8990c0d9
commit
02efa9a3dc
29 changed files with 595 additions and 519 deletions
|
|
@ -14,8 +14,8 @@ export class StreamBase {
|
||||||
return new PairwiseStream(this, first);
|
return new PairwiseStream(this, first);
|
||||||
}
|
}
|
||||||
|
|
||||||
scan(initAccumulator) {
|
scan(initAccumulator, scanFunc) {
|
||||||
return new ScanStream(this, initAccumulator);
|
return new ScanStream(this, initAccumulator, scanFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
remember(initialValue, usingStream) {
|
remember(initialValue, usingStream) {
|
||||||
|
|
|
||||||
8
modules/lstream/index.d.ts
vendored
8
modules/lstream/index.d.ts
vendored
|
|
@ -9,11 +9,11 @@ interface Stream<T> extends Observable<T> {
|
||||||
|
|
||||||
filter(predicate: (T) => boolean): Stream<T>;
|
filter(predicate: (T) => boolean): Stream<T>;
|
||||||
|
|
||||||
pairwise(first: T): Stream<[T, T]>;
|
pairwise(first?: T): Stream<[T, T]>;
|
||||||
|
|
||||||
scan(initAccumulator: any): Stream<any>;
|
scan<T>(seed: T, scanFn: (accum: T, current: T) => T): Stream<T>;
|
||||||
|
|
||||||
remember(initialValue: T, usingStream: any): StateStream<T>
|
remember(initialValue: T, usingStream?: any): StateStream<T>
|
||||||
|
|
||||||
distinct(): Stream<T>;
|
distinct(): Stream<T>;
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ export function stream<T>(): Emitter<T>;
|
||||||
|
|
||||||
export function eventStream<T>(): Emitter<T>;
|
export function eventStream<T>(): Emitter<T>;
|
||||||
|
|
||||||
export function combine(...streams: Stream<any>[]): Stream<any[]>;
|
export function combine<T>(...streams: Stream<any>[]): Stream<T>;
|
||||||
|
|
||||||
export function merge(...streams: Stream<any>[]): Stream<any>;
|
export function merge(...streams: Stream<any>[]): Stream<any>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,17 @@ import {StreamBase} from './base';
|
||||||
|
|
||||||
export class ScanStream extends StreamBase {
|
export class ScanStream extends StreamBase {
|
||||||
|
|
||||||
constructor(stream, initAccumulator) {
|
constructor(stream, seed, scanFunc) {
|
||||||
super();
|
super();
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.acc = initAccumulator;
|
this.value = seed;
|
||||||
|
this.scanFunc = scanFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
attach(observer) {
|
attach(observer) {
|
||||||
return this.stream.attach(v => this.acc = observer(this.acc, v));
|
return this.stream.attach(v => {
|
||||||
|
this.value = this.scanFunc(this.value, v);
|
||||||
|
observer(this.value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ export function GenericWizard({topicId, title, left, className, children, onCanc
|
||||||
left?: number,
|
left?: number,
|
||||||
onCancel: () => any,
|
onCancel: () => any,
|
||||||
onOK: () => any,
|
onOK: () => any,
|
||||||
infoText: string
|
onKeyDown: (e) => any,
|
||||||
|
infoText: any
|
||||||
} & WindowProps ) {
|
} & WindowProps ) {
|
||||||
|
|
||||||
return <Window initWidth={250}
|
return <Window initWidth={250}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,22 @@ import React from 'react';
|
||||||
export default class ComboBoxControl extends React.Component {
|
export default class ComboBoxControl extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let {onChange, value, children} = this.props;
|
let {onChange, value, includeNonExistent, children} = this.props;
|
||||||
|
let nonExistent = null;
|
||||||
|
if (includeNonExistent) {
|
||||||
|
let needsInclusion = false;
|
||||||
|
React.Children.forEach(children, opt => {
|
||||||
|
if (opt.value === value) {
|
||||||
|
needsInclusion = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (needsInclusion) {
|
||||||
|
nonExistent = <ComboBoxOption value={value}>{value ? value+'' : '<empty>'}</ComboBoxOption>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return <select value={value} onChange={e => onChange(e.target.value)}>
|
return <select value={value} onChange={e => onChange(e.target.value)}>
|
||||||
|
{nonExistent}
|
||||||
{children}
|
{children}
|
||||||
</select>
|
</select>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import context from 'context';
|
|
||||||
|
|
||||||
export default function errorBoundary(message, fix, resetOn) {
|
|
||||||
return function(Comp) {
|
|
||||||
class ErrorBoundary extends React.Component {
|
|
||||||
|
|
||||||
state = {
|
|
||||||
hasError: false,
|
|
||||||
fixAttempt: false
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidCatch() {
|
|
||||||
this.setState({hasError: true});
|
|
||||||
if (!this.state.fixAttempt) {
|
|
||||||
if (fix) {
|
|
||||||
fix(this.props);
|
|
||||||
this.setState({hasError: false, fixAttempt: true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (resetOn) {
|
|
||||||
let stream = resetOn(context.streams);
|
|
||||||
if (stream) {
|
|
||||||
this.attcahing = true;
|
|
||||||
this.detacher = stream.attach(this.reset);
|
|
||||||
this.attcahing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reset = () => {
|
|
||||||
if (this.attcahing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({hasError: false, fixAttempt: false});
|
|
||||||
if (this.detacher) {
|
|
||||||
this.detacher();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.hasError) {
|
|
||||||
return message || null;
|
|
||||||
}
|
|
||||||
return <Comp {...this.props} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ErrorBoundary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
modules/ui/errorBoundary.tsx
Normal file
40
modules/ui/errorBoundary.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React from 'react';
|
||||||
|
import context from 'context';
|
||||||
|
import {Stream} from "lstream";
|
||||||
|
import {useStream} from "ui/effects";
|
||||||
|
|
||||||
|
type ErrorBoundaryProps = {
|
||||||
|
message: any;
|
||||||
|
children: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function HealingErrorBoundary({resetOn, ...props}: ErrorBoundaryProps & {
|
||||||
|
resetOn: (ctx) => Stream<any>,
|
||||||
|
}) {
|
||||||
|
|
||||||
|
const key = useStream(ctx => resetOn(ctx).scan(0, (acc, curr) => acc + curr));
|
||||||
|
|
||||||
|
return <ErrorBoundary key={key} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, {
|
||||||
|
hasError: boolean
|
||||||
|
}> {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
hasError: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidCatch(error:Error, errorInfo:React.ErrorInfo) {
|
||||||
|
console.error(error);
|
||||||
|
this.setState({hasError: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return this.props.message || null;
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
||||||
"classnames": "2.2.5",
|
"classnames": "2.2.5",
|
||||||
"clipper-lib": "6.2.1",
|
"clipper-lib": "6.2.1",
|
||||||
"earcut": "2.1.1",
|
"earcut": "2.1.1",
|
||||||
|
"immer": "^9.0.12",
|
||||||
"less": "^3.11.1",
|
"less": "^3.11.1",
|
||||||
"libtess": "1.2.2",
|
"libtess": "1.2.2",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
|
@ -7512,6 +7513,15 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immer": {
|
||||||
|
"version": "9.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz",
|
||||||
|
"integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
|
||||||
|
|
@ -21428,6 +21438,11 @@
|
||||||
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
|
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"immer": {
|
||||||
|
"version": "9.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz",
|
||||||
|
"integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA=="
|
||||||
|
},
|
||||||
"import-fresh": {
|
"import-fresh": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@
|
||||||
"classnames": "2.2.5",
|
"classnames": "2.2.5",
|
||||||
"clipper-lib": "6.2.1",
|
"clipper-lib": "6.2.1",
|
||||||
"earcut": "2.1.1",
|
"earcut": "2.1.1",
|
||||||
|
"immer": "^9.0.12",
|
||||||
"less": "^3.11.1",
|
"less": "^3.11.1",
|
||||||
"libtess": "1.2.2",
|
"libtess": "1.2.2",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ export function activate(ctx: ApplicationContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function solveAssembly(): void {
|
function solveAssembly(): void {
|
||||||
if (ctx.craftService.isEditingHistory()) {
|
if (ctx.craftService.isEditingHistory) {
|
||||||
console.log('skipping assembly resolve request in the history mode');
|
console.log('skipping assembly resolve request in the history mode');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {addModification, stepOverriding} from './craftHistoryUtils';
|
import {addModification, finishHistoryEditing, stepOverriding} from './craftHistoryUtils';
|
||||||
import {Emitter, state, StateStream, stream} from 'lstream';
|
import {Emitter, state, StateStream, stream} from 'lstream';
|
||||||
import materializeParams from './schema/materializeParams';
|
import materializeParams from './schema/materializeParams';
|
||||||
import CadError from '../../utils/errors';
|
import CadError from '../../utils/errors';
|
||||||
|
|
@ -103,15 +103,16 @@ export function activate(ctx: CoreContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEditingHistory() {
|
|
||||||
const mods = this.modifications$.value;
|
|
||||||
return mods && mods.pointer !== mods.history.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.craftService = {
|
ctx.craftService = {
|
||||||
modify, modifyInHistoryAndStep, reset, rebuild, runRequest, runPipeline,
|
|
||||||
|
get isEditingHistory() {
|
||||||
|
const mods = this.modifications$.value;
|
||||||
|
return mods && mods.pointer !== mods.history.length - 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
modify, modifyInHistoryAndStep, reset, rebuild, runRequest, runPipeline,
|
||||||
historyTravel: historyTravel(modifications$),
|
historyTravel: historyTravel(modifications$),
|
||||||
modifications$, models$, update$, isEditingHistory, pipelineFailure$
|
modifications$, models$, update$, pipelineFailure$
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -244,9 +245,16 @@ export interface OperationResult {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CraftHistory {
|
export interface CraftHistory {
|
||||||
history: OperationRequest[];
|
history: OperationRequest[];
|
||||||
pointer: number;
|
pointer: number;
|
||||||
|
hints?: CraftHints;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CraftHints {
|
||||||
|
|
||||||
|
noWizardFocus?: boolean;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CraftService {
|
interface CraftService {
|
||||||
|
|
@ -256,9 +264,9 @@ interface CraftService {
|
||||||
update$: Emitter<void>;
|
update$: Emitter<void>;
|
||||||
pipelineFailure$: StateStream<any>
|
pipelineFailure$: StateStream<any>
|
||||||
|
|
||||||
modify(request: OperationRequest, onAccepted: () => void, onError: () => Error);
|
modify(request: OperationRequest, onAccepted: () => void, onError: (error) => void);
|
||||||
|
|
||||||
modifyInHistoryAndStep(request: OperationRequest, onAccepted: () => void, onError: () => Error);
|
modifyInHistoryAndStep(request: OperationRequest, onAccepted: () => void, onError: (error) => void);
|
||||||
|
|
||||||
reset(modifications: OperationRequest[]);
|
reset(modifications: OperationRequest[]);
|
||||||
|
|
||||||
|
|
@ -270,13 +278,13 @@ interface CraftService {
|
||||||
|
|
||||||
runPipeline(history: OperationRequest[], beginIndex: number, endIndex: number): Promise<void>;
|
runPipeline(history: OperationRequest[], beginIndex: number, endIndex: number): Promise<void>;
|
||||||
|
|
||||||
isEditingHistory(): boolean;
|
isEditingHistory: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HistoryTravel {
|
interface HistoryTravel {
|
||||||
setPointer(pointer, hints:any);
|
setPointer(pointer, hints:any);
|
||||||
begin(hints: any);
|
begin(hints?: any);
|
||||||
end(hints: any);
|
end(hints?: any);
|
||||||
forward(hints: any);
|
forward(hints: any);
|
||||||
backward(hints: any);
|
backward(hints: any);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ const pushedModels = new Set();
|
||||||
export const OCI: OCCCommandInterface = new Proxy({}, {
|
export const OCI: OCCCommandInterface = new Proxy({}, {
|
||||||
get: function (target, prop: string, receiver) {
|
get: function (target, prop: string, receiver) {
|
||||||
return prop in target ? target[prop] : function() {
|
return prop in target ? target[prop] : function() {
|
||||||
|
if (typeof prop !== 'string') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
prop = prop.replace(/^_/, '');
|
prop = prop.replace(/^_/, '');
|
||||||
const args = Array.from(arguments).map(arg => {
|
const args = Array.from(arguments).map(arg => {
|
||||||
const type = typeof arg;
|
const type = typeof arg;
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ export interface Operation<R> extends OperationDescriptor<R>{
|
||||||
icon: string|IconType;
|
icon: string|IconType;
|
||||||
};
|
};
|
||||||
schemaIndex: SchemaIndex;
|
schemaIndex: SchemaIndex;
|
||||||
form: () => React.ReactNode;
|
form: React.FunctionComponent;
|
||||||
schema: OperationSchema;
|
schema: OperationSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +107,8 @@ export interface OperationDescriptor<R> {
|
||||||
run: (request: R, opContext: CoreContext) => OperationResult | Promise<OperationResult>;
|
run: (request: R, opContext: CoreContext) => OperationResult | Promise<OperationResult>;
|
||||||
paramsInfo: (params: R) => string,
|
paramsInfo: (params: R) => string,
|
||||||
previewGeomProvider?: (params: R) => OperationGeometryProvider,
|
previewGeomProvider?: (params: R) => OperationGeometryProvider,
|
||||||
form: FormDefinition | (() => React.ReactNode),
|
previewer?: any,
|
||||||
|
form: FormDefinition | React.FunctionComponent,
|
||||||
schema?: OperationSchema,
|
schema?: OperationSchema,
|
||||||
onParamsUpdate?: (params, name, value) => void,
|
onParamsUpdate?: (params, name, value) => void,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import {TypeRegistry, Types} from "cad/craft/schema/types";
|
import {Types} from "cad/craft/schema/types";
|
||||||
import {OperationSchema, SchemaField} from "cad/craft/schema/schema";
|
import {isValueNotProvided, OperationSchema, SchemaField} from "cad/craft/schema/schema";
|
||||||
import {ApplicationContext} from "context";
|
import {CoreContext} from "context";
|
||||||
|
|
||||||
export default function initializeBySchema(schema: OperationSchema, context: ApplicationContext) {
|
export default function initializeBySchema(schema: OperationSchema, context: CoreContext) {
|
||||||
let fields = Object.keys(schema);
|
let fields = Object.keys(schema);
|
||||||
let obj = {};
|
let obj = {};
|
||||||
for (let field of fields) {
|
for (let field of fields) {
|
||||||
|
|
@ -42,3 +42,33 @@ export default function initializeBySchema(schema: OperationSchema, context: App
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function fillUpMissingFields(params: any, schema: OperationSchema, context: CoreContext) {
|
||||||
|
let fields = Object.keys(schema);
|
||||||
|
for (let field of fields) {
|
||||||
|
const md = schema[field] as SchemaField;
|
||||||
|
|
||||||
|
if (md.optional) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = params[field];
|
||||||
|
|
||||||
|
const isPrimitive =
|
||||||
|
md.type !== Types.array
|
||||||
|
&& md.type !== Types.object
|
||||||
|
&& md.type !== Types.entity;
|
||||||
|
|
||||||
|
if (isPrimitive && isValueNotProvided(val)) {
|
||||||
|
params[field] = md.defaultValue;
|
||||||
|
} else if (md.type === Types.object) {
|
||||||
|
if (!val) {
|
||||||
|
val = {};
|
||||||
|
params[field] = val;
|
||||||
|
}
|
||||||
|
fillUpMissingFields(val, md.schema, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import {TypeRegistry} from "cad/craft/schema/types";
|
import {TypeRegistry} from "cad/craft/schema/types";
|
||||||
import {CoreContext} from "context";
|
import {CoreContext} from "context";
|
||||||
import {
|
import {
|
||||||
|
isValueNotProvided,
|
||||||
OperationParams,
|
OperationParams,
|
||||||
OperationParamsError,
|
OperationParamsError,
|
||||||
OperationParamsErrorReporter,
|
OperationParamsErrorReporter,
|
||||||
|
|
@ -33,14 +34,13 @@ function materializeParamsImpl(ctx: CoreContext,
|
||||||
result: any,
|
result: any,
|
||||||
parentReportError: OperationParamsErrorReporter) {
|
parentReportError: OperationParamsErrorReporter) {
|
||||||
|
|
||||||
const notProvided = value => value === undefined || value === null || value === '';
|
|
||||||
|
|
||||||
for (let field of Object.keys(schema)) {
|
for (let field of Object.keys(schema)) {
|
||||||
const reportError = parentReportError.dot(field);
|
const reportError = parentReportError.dot(field);
|
||||||
const md = schema[field];
|
const md = schema[field];
|
||||||
let value = params[field];
|
let value = params[field];
|
||||||
|
|
||||||
if (notProvided(value)) {
|
if (isValueNotProvided(value)) {
|
||||||
if (!md.optional) {
|
if (!md.optional) {
|
||||||
reportError('required');
|
reportError('required');
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +52,7 @@ function materializeParamsImpl(ctx: CoreContext,
|
||||||
value = md.resolve(
|
value = md.resolve(
|
||||||
ctx, value, md as any, reportError
|
ctx, value, md as any, reportError
|
||||||
)
|
)
|
||||||
if (notProvided(value) && !md.optional) {
|
if (isValueNotProvided(value) && !md.optional) {
|
||||||
reportError('required');
|
reportError('required');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,4 +93,6 @@ export function unwrapMetadata(fieldMd: SchemaField) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return fieldMd;
|
return fieldMd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isValueNotProvided = value => value === undefined || value === null || value === '';
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {aboveElement} from 'ui/positionUtils';
|
||||||
.map(([modifications, operationRegistry, insertOperationReq]) => ({
|
.map(([modifications, operationRegistry, insertOperationReq]) => ({
|
||||||
...modifications,
|
...modifications,
|
||||||
operationRegistry,
|
operationRegistry,
|
||||||
inProgressOperation: insertOperationReq.type,
|
inProgressOperation: !!insertOperationReq,
|
||||||
getOperation: type => operationRegistry[type]||EMPTY_OBJECT
|
getOperation: type => operationRegistry[type]||EMPTY_OBJECT
|
||||||
})))
|
})))
|
||||||
@mapContext(({streams, services}) => ({
|
@mapContext(({streams, services}) => ({
|
||||||
|
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import Stack from 'ui/components/Stack';
|
|
||||||
import Button from 'ui/components/controls/Button';
|
|
||||||
import ButtonGroup from 'ui/components/controls/ButtonGroup';
|
|
||||||
|
|
||||||
import ls from './Wizard.less';
|
|
||||||
import CadError from '../../../../utils/errors';
|
|
||||||
import {FormContext, FormContextData} from './form/Form';
|
|
||||||
import connect from 'ui/connect';
|
|
||||||
import {combine} from 'lstream';
|
|
||||||
import {GenericWizard} from "ui/components/GenericWizard";
|
|
||||||
import * as PropTypes from "prop-types";
|
|
||||||
import {useStream} from "ui/effects";
|
|
||||||
|
|
||||||
@connect((streams, props) => combine(props.context.workingRequest$, props.context.state$)
|
|
||||||
.map(([workingRequest, state]) => ({
|
|
||||||
...workingRequest,
|
|
||||||
activeParam: state.activeParam,
|
|
||||||
error: state.error
|
|
||||||
})))
|
|
||||||
export default class Wizard extends React.Component {
|
|
||||||
|
|
||||||
state = {
|
|
||||||
hasInternalError: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
updateParam = (name, value) => {
|
|
||||||
this.props.context.updateParam(name, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidCatch() {
|
|
||||||
this.setState({hasInternalError: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.hasInternalError) {
|
|
||||||
return <span>operation error</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let {left, type, params, state, context} = this.props;
|
|
||||||
let operation = context.operation;
|
|
||||||
|
|
||||||
let title = (operation.label || type).toUpperCase();
|
|
||||||
|
|
||||||
let Form = operation.form;
|
|
||||||
|
|
||||||
const error = this.props.error;
|
|
||||||
return <GenericWizard
|
|
||||||
left={left}
|
|
||||||
title={title}
|
|
||||||
onClose={this.cancel}
|
|
||||||
onKeyDown={this.onKeyDown}
|
|
||||||
setFocus={this.focusFirstInput}
|
|
||||||
className='Wizard'
|
|
||||||
data-operation-id={operation.id}
|
|
||||||
topicId={operation.id}
|
|
||||||
onCancel={this.cancel}
|
|
||||||
onOK={this.onOK}
|
|
||||||
infoText={<>
|
|
||||||
{error && <ErrorPrinter error={error}/>}
|
|
||||||
<PipelineError />
|
|
||||||
</>}
|
|
||||||
>
|
|
||||||
<FormContext.Provider value={new FormContextData(this.props.context, [])}>
|
|
||||||
<Form/>
|
|
||||||
</FormContext.Provider>
|
|
||||||
|
|
||||||
</GenericWizard>;
|
|
||||||
}
|
|
||||||
|
|
||||||
onKeyDown = e => {
|
|
||||||
switch (e.keyCode) {
|
|
||||||
case 27 :
|
|
||||||
this.cancel();
|
|
||||||
break;
|
|
||||||
case 13 :
|
|
||||||
this.onOK();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
focusFirstInput = el => {
|
|
||||||
if (this.props.noFocus) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let toFocus = el.querySelector('input, select');
|
|
||||||
if (!toFocus) {
|
|
||||||
toFocus = el;
|
|
||||||
}
|
|
||||||
toFocus.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
cancel = () => {
|
|
||||||
this.props.onCancel();
|
|
||||||
};
|
|
||||||
|
|
||||||
onOK = () => {
|
|
||||||
this.props.onOK();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function PipelineError() {
|
|
||||||
const pipelineFailure = useStream(ctx => ctx.craftService.pipelineFailure$);
|
|
||||||
if (!pipelineFailure) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return <ErrorPrinter error={pipelineFailure}/>
|
|
||||||
}
|
|
||||||
|
|
||||||
function ErrorPrinter({error}) {
|
|
||||||
return <div className={ls.errorMessage}>
|
|
||||||
{CadError.ALGORITHM_ERROR_KINDS.includes(error.kind) && <span>
|
|
||||||
performing operation with current parameters leads to an invalid object
|
|
||||||
(self-intersecting / zero-thickness / complete degeneration or unsupported cases)
|
|
||||||
</span>}
|
|
||||||
{error.code && <div className={ls.errorCode}>{error.code}</div>}
|
|
||||||
{error.userMessage && <div className={ls.userErrorMessage}>{error.userMessage}</div>}
|
|
||||||
{!error.userMessage && <div>internal error processing operation, check the log</div>}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
122
web/app/cad/craft/wizard/components/Wizard.tsx
Normal file
122
web/app/cad/craft/wizard/components/Wizard.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
import React, {useContext} from 'react';
|
||||||
|
|
||||||
|
import ls from './Wizard.less';
|
||||||
|
import CadError from '../../../../utils/errors';
|
||||||
|
import {FormParamsContext, FormPathContext, FormStateContext} from './form/Form';
|
||||||
|
import {GenericWizard} from "ui/components/GenericWizard";
|
||||||
|
import {useStream} from "ui/effects";
|
||||||
|
import {AppContext} from "cad/dom/components/AppContext";
|
||||||
|
|
||||||
|
interface WizardProps {
|
||||||
|
noFocus: boolean;
|
||||||
|
|
||||||
|
left?: number;
|
||||||
|
|
||||||
|
onCancel(): void;
|
||||||
|
|
||||||
|
onOK(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Wizard(props: WizardProps) {
|
||||||
|
|
||||||
|
const ctx = useContext(AppContext);
|
||||||
|
const state = useStream(ctx => ctx.wizardService.state$);
|
||||||
|
const workingRequest = useStream(ctx => ctx.wizardService.workingRequest$);
|
||||||
|
|
||||||
|
if (!workingRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const operation = ctx.operationService.get(workingRequest.type);
|
||||||
|
|
||||||
|
if (!operation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = state.error;
|
||||||
|
|
||||||
|
const onKeyDown = e => {
|
||||||
|
switch (e.keyCode) {
|
||||||
|
case 27 :
|
||||||
|
cancel();
|
||||||
|
break;
|
||||||
|
case 13 :
|
||||||
|
onOK();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const focusFirstInput = el => {
|
||||||
|
if (props.noFocus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let toFocus = el.querySelector('input, select');
|
||||||
|
if (!toFocus) {
|
||||||
|
toFocus = el;
|
||||||
|
}
|
||||||
|
toFocus.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
props.onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOK = () => {
|
||||||
|
props.onOK();
|
||||||
|
};
|
||||||
|
|
||||||
|
let {left} = props;
|
||||||
|
let wizardService = ctx.wizardService;
|
||||||
|
|
||||||
|
let title = (operation.label || operation.id).toUpperCase();
|
||||||
|
|
||||||
|
let Form = operation.form;
|
||||||
|
|
||||||
|
return <GenericWizard
|
||||||
|
left={left}
|
||||||
|
title={title}
|
||||||
|
onClose={cancel}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
setFocus={focusFirstInput}
|
||||||
|
className='Wizard'
|
||||||
|
data-operation-id={operation.id}
|
||||||
|
topicId={operation.id}
|
||||||
|
onCancel={cancel}
|
||||||
|
onOK={onOK}
|
||||||
|
infoText={<>
|
||||||
|
{error && <ErrorPrinter error={error}/>}
|
||||||
|
<PipelineError />
|
||||||
|
</>}
|
||||||
|
>
|
||||||
|
|
||||||
|
<FormParamsContext.Provider value={workingRequest.params}>
|
||||||
|
<FormPathContext.Provider value={[]}>
|
||||||
|
<FormStateContext.Provider value={state}>
|
||||||
|
<Form/>
|
||||||
|
</FormStateContext.Provider>
|
||||||
|
</FormPathContext.Provider>
|
||||||
|
</FormParamsContext.Provider>
|
||||||
|
|
||||||
|
</GenericWizard>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function PipelineError() {
|
||||||
|
const pipelineFailure = useStream(ctx => ctx.craftService.pipelineFailure$);
|
||||||
|
if (!pipelineFailure) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <ErrorPrinter error={pipelineFailure}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
function ErrorPrinter({error}) {
|
||||||
|
return <div className={ls.errorMessage}>
|
||||||
|
{CadError.ALGORITHM_ERROR_KINDS.includes(error.kind) && <span>
|
||||||
|
performing operation with current parameters leads to an invalid object
|
||||||
|
(self-intersecting / zero-thickness / complete degeneration or unsupported cases)
|
||||||
|
</span>}
|
||||||
|
{error.code && <div className={ls.errorCode}>{error.code}</div>}
|
||||||
|
{error.userMessage && <div className={ls.userErrorMessage}>{error.userMessage}</div>}
|
||||||
|
{!error.userMessage && <div>internal error processing operation, check the log</div>}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import Wizard from './Wizard';
|
|
||||||
import connect from 'ui/connect';
|
|
||||||
import decoratorChain from 'ui/decoratorChain';
|
|
||||||
import mapContext from 'ui/mapContext';
|
|
||||||
import {finishHistoryEditing} from '../../craftHistoryUtils';
|
|
||||||
|
|
||||||
function WizardManager({wizardContext, type, cancel, cancelHistoryEdit, applyWorkingRequest}) {
|
|
||||||
if (!wizardContext) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return <Wizard key={wizardContext.ID}
|
|
||||||
context={wizardContext}
|
|
||||||
noFocus={wizardContext.noWizardFocus}
|
|
||||||
onCancel={wizardContext.changingHistory ? cancelHistoryEdit : cancel}
|
|
||||||
onOK={applyWorkingRequest} />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default decoratorChain(
|
|
||||||
connect(streams => streams.wizard.wizardContext.map(wizardContext => ({wizardContext}))),
|
|
||||||
mapContext(ctx => ({
|
|
||||||
cancel: ctx.services.wizard.cancel,
|
|
||||||
cancelHistoryEdit: () => ctx.streams.craft.modifications.update(modifications => finishHistoryEditing(modifications)),
|
|
||||||
applyWorkingRequest: ctx.services.wizard.applyWorkingRequest
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
(WizardManager);
|
|
||||||
22
web/app/cad/craft/wizard/components/WizardManager.tsx
Normal file
22
web/app/cad/craft/wizard/components/WizardManager.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import React, {useContext} from 'react';
|
||||||
|
import Wizard from './Wizard';
|
||||||
|
import {useStream} from "ui/effects";
|
||||||
|
import {AppContext} from "cad/dom/components/AppContext";
|
||||||
|
import {ErrorBoundary} from "ui/errorBoundary";
|
||||||
|
|
||||||
|
export default function WizardManager() {
|
||||||
|
|
||||||
|
const ctx = useContext(AppContext);
|
||||||
|
const workingRequest = useStream(ctx => ctx.wizardService.workingRequest$);
|
||||||
|
|
||||||
|
if (!workingRequest) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ErrorBoundary key={workingRequest.requestKey}
|
||||||
|
message={<span>operation error</span>}>
|
||||||
|
<Wizard noFocus={workingRequest.hints?.noWizardFocus}
|
||||||
|
onCancel={ctx.craftService.isEditingHistory ? ctx.craftService.historyTravel.end : ctx.wizardService.cancel}
|
||||||
|
onOK={ctx.wizardService.applyWorkingRequest} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
}
|
||||||
|
|
@ -1,43 +1,17 @@
|
||||||
import React from 'react';
|
import React, {useContext} from 'react';
|
||||||
import Label from 'ui/components/controls/Label';
|
import Label from 'ui/components/controls/Label';
|
||||||
import Field from 'ui/components/controls/Field';
|
import Field from 'ui/components/controls/Field';
|
||||||
import Stack from 'ui/components/Stack';
|
import Stack from 'ui/components/Stack';
|
||||||
import {camelCaseSplitToStr} from 'gems/camelCaseSplit';
|
import {camelCaseSplitToStr} from 'gems/camelCaseSplit';
|
||||||
import {FlattenPath, ParamsPath, ParamsPathSegment, WizardContext} from "cad/craft/wizard/wizardTypes";
|
import {ParamsPath, ParamsPathSegment, WizardState} from "cad/craft/wizard/wizardTypes";
|
||||||
import {flattenPath, OperationParamValue} from "cad/craft/schema/schema";
|
import {OperationParams, OperationParamValue} from "cad/craft/schema/schema";
|
||||||
|
import {AppContext} from "cad/dom/components/AppContext";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
export const FormContext: React.Context<FormContextData> = React.createContext(null);
|
export const FormStateContext: React.Context<WizardState> = React.createContext(null);
|
||||||
|
export const FormParamsContext: React.Context<OperationParams> = React.createContext(null);
|
||||||
|
export const FormPathContext: React.Context<ParamsPath> = React.createContext([]);
|
||||||
|
|
||||||
export class FormContextData {
|
|
||||||
|
|
||||||
wizardContext: WizardContext;
|
|
||||||
prefix: ParamsPath;
|
|
||||||
|
|
||||||
constructor(wizardContext: WizardContext, prefix: ParamsPath) {
|
|
||||||
this.wizardContext = wizardContext;
|
|
||||||
this.prefix = prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateParam(segment: ParamsPathSegment, value: OperationParamValue): void {
|
|
||||||
this.wizardContext.updateParam([...this.prefix, segment], value);
|
|
||||||
}
|
|
||||||
|
|
||||||
readParam(segment: ParamsPathSegment): OperationParamValue {
|
|
||||||
return this.wizardContext.readParam([...this.prefix, segment]);
|
|
||||||
}
|
|
||||||
|
|
||||||
dot(segment: ParamsPathSegment): FormContextData {
|
|
||||||
return new FormContextData(this.wizardContext, [...this.prefix, segment]);
|
|
||||||
}
|
|
||||||
|
|
||||||
setActiveParam = (path: FlattenPath) => {
|
|
||||||
this.wizardContext.updateState(state => state.activeParam = path);
|
|
||||||
}
|
|
||||||
|
|
||||||
get activeParam(): FlattenPath {
|
|
||||||
return this.wizardContext.state$.value.activeParam;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Group({children}) {
|
export function Group({children}) {
|
||||||
return <Stack>
|
return <Stack>
|
||||||
|
|
@ -61,36 +35,42 @@ interface FormFieldProps {
|
||||||
children?: any
|
children?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export function attachToForm(Control) {
|
export function attachToForm(Control): any {
|
||||||
|
|
||||||
return function FormField({name, ...props}: FormFieldProps) {
|
return function FormField({name, ...props}: FormFieldProps) {
|
||||||
return <FormContext.Consumer>
|
|
||||||
{
|
const ctx = useContext(AppContext);
|
||||||
(ctx: FormContextData) => {
|
const formPath = useContext(FormPathContext);
|
||||||
const fullPath = flattenPath([...ctx.prefix, name]);
|
const formState = useContext(FormStateContext);
|
||||||
const onChange = val => ctx.updateParam(name, val);
|
const params = useContext(FormParamsContext);
|
||||||
const setActive = val => ctx.setActiveParam(val ? fullPath : undefined);
|
|
||||||
return <React.Fragment>
|
const fullPath = [...formPath, name];
|
||||||
<Control value={ctx.readParam(name)}
|
const fullPathFlatten = fullPath.join('.');
|
||||||
onChange={onChange}
|
const onChange = value => ctx.wizardService.updateParam(fullPath, value);
|
||||||
name={name} {...props}
|
const setActive = (isActive) => ctx.wizardService.updateState(state => {
|
||||||
setActive={setActive}
|
state.activeParam = isActive ? fullPathFlatten : null;
|
||||||
active={ctx.activeParam === fullPath} />
|
});
|
||||||
</React.Fragment>;
|
|
||||||
}
|
const value = _.get(params, fullPath);
|
||||||
}
|
|
||||||
</FormContext.Consumer>;
|
return <React.Fragment>
|
||||||
|
<Control value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
name={name} {...props}
|
||||||
|
setActive={setActive}
|
||||||
|
active={formState.activeParam === fullPathFlatten} />
|
||||||
|
</React.Fragment>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SubForm(props: {name: ParamsPathSegment, children: any}) {
|
export function SubForm(props: {name: ParamsPathSegment, children: any}) {
|
||||||
|
|
||||||
return <FormContext.Consumer>
|
const formState = useContext(FormStateContext);
|
||||||
{
|
const formPath = useContext(FormPathContext);
|
||||||
(ctx: FormContextData) => {
|
|
||||||
return <FormContext.Provider value={ctx.dot(props.name)}>
|
return <FormParamsContext.Provider value={formState[props.name]}>
|
||||||
{props.children}
|
<FormPathContext.Provider value={[...formPath, props.name]}>
|
||||||
</FormContext.Provider>
|
{props.children}
|
||||||
}
|
</FormPathContext.Provider>
|
||||||
}
|
</FormParamsContext.Provider>;
|
||||||
</FormContext.Consumer>
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,163 +1,181 @@
|
||||||
import {state} from 'lstream';
|
import {combine, state, StateStream} from 'lstream';
|
||||||
import initializeBySchema from '../schema/initializeBySchema';
|
import initializeBySchema, {fillUpMissingFields} from '../schema/initializeBySchema';
|
||||||
import {clone, EMPTY_OBJECT} from 'gems/objects';
|
import {clone} from 'gems/objects';
|
||||||
import materializeParams from '../schema/materializeParams';
|
import materializeParams from '../schema/materializeParams';
|
||||||
import {createFunctionList} from 'gems/func';
|
import {createFunctionList} from 'gems/func';
|
||||||
import {OperationRequest} from "cad/craft/craftPlugin";
|
import {CraftHints, CraftHistory, OperationRequest} from "cad/craft/craftPlugin";
|
||||||
import {ParamsPath, WizardContext, WizardState} from "cad/craft/wizard/wizardTypes";
|
import {NewOperationCall, ParamsPath, WizardService, WizardState} from "cad/craft/wizard/wizardTypes";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import {OperationParamValue} from "cad/craft/schema/schema";
|
import {OperationParamValue} from "cad/craft/schema/schema";
|
||||||
|
import {ApplicationContext} from "context";
|
||||||
|
import {Operation} from "cad/craft/operationPlugin";
|
||||||
|
import produce from "immer"
|
||||||
|
|
||||||
export function activate(ctx) {
|
type WorkingRequest = OperationRequest & {
|
||||||
|
hints?: CraftHints,
|
||||||
|
requestKey: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function activate(ctx: ApplicationContext) {
|
||||||
|
|
||||||
let {streams, services} = ctx;
|
let {streams, services} = ctx;
|
||||||
|
|
||||||
streams.wizard = {};
|
const insertOperation$ = state<NewOperationCall>(null);
|
||||||
|
|
||||||
streams.wizard.insertOperation = state(EMPTY_OBJECT);
|
|
||||||
|
|
||||||
streams.wizard.effectiveOperation = state(EMPTY_OBJECT);
|
let REQUEST_COUNTER = 1;
|
||||||
|
|
||||||
streams.wizard.insertOperation.attach(insertOperationReq => {
|
const workingRequest$: StateStream<WorkingRequest> = combine<[NewOperationCall, CraftHistory]>(
|
||||||
if (insertOperationReq.type) {
|
insertOperation$,
|
||||||
let type = insertOperationReq.type;
|
ctx.craftService.modifications$
|
||||||
let operation = ctx.services.operation.get(type);
|
).map<NewOperationCall|OperationRequest>(([insertOperationReq, mods]) => {
|
||||||
streams.wizard.effectiveOperation.value = {
|
|
||||||
type: operation.id,
|
|
||||||
initialOverrides: insertOperationReq.initialOverrides,
|
|
||||||
changingHistory: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function gotoEditHistoryModeIfNeeded({pointer, history, hints}) {
|
|
||||||
if (pointer !== history.length - 1) {
|
|
||||||
let {type, params} = history[pointer + 1];
|
|
||||||
streams.wizard.effectiveOperation.value = {
|
|
||||||
type,
|
|
||||||
params,
|
|
||||||
noWizardFocus: hints && hints.noWizardFocus,
|
|
||||||
changingHistory: true
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
streams.wizard.effectiveOperation.value = EMPTY_OBJECT;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
let operation;
|
||||||
|
let params;
|
||||||
streams.craft.modifications.attach(mod => {
|
|
||||||
if (streams.wizard.insertOperation.value.type) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gotoEditHistoryModeIfNeeded(mod);
|
|
||||||
});
|
|
||||||
|
|
||||||
streams.wizard.wizardContext = streams.wizard.effectiveOperation.map((opRequest: OperationRequest) => {
|
if (insertOperationReq !== null) {
|
||||||
let wizCtx: WizardContext = null;
|
operation = ctx.operationService.get(insertOperationReq.type);
|
||||||
if (opRequest.type) {
|
params = initializeBySchema(operation.schema, ctx);
|
||||||
|
if (insertOperationReq.initialOverrides) {
|
||||||
let operation = ctx.services.operation.get(opRequest.type);
|
applyOverrides(params, insertOperationReq.initialOverrides);
|
||||||
|
|
||||||
let params;
|
|
||||||
let {changingHistory, noWizardFocus} = opRequest;
|
|
||||||
if (changingHistory) {
|
|
||||||
params = clone(opRequest.params)
|
|
||||||
} else {
|
|
||||||
params = initializeBySchema(operation.schema, ctx);
|
|
||||||
if (opRequest.initialOverrides) {
|
|
||||||
applyOverrides(params, opRequest.initialOverrides);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let workingRequest$ = state({
|
return {
|
||||||
type: opRequest.type,
|
type: operation.id,
|
||||||
params
|
params,
|
||||||
});
|
requestKey: REQUEST_COUNTER++
|
||||||
|
}
|
||||||
let materializedWorkingRequest$ = workingRequest$.map(req => {
|
} else {
|
||||||
let params = {};
|
const {pointer, history, hints} = mods
|
||||||
let errors = [];
|
if (pointer !== history.length - 1) {
|
||||||
materializeParams(ctx, req.params, operation.schema, params, errors);
|
let {type, params} = history[pointer + 1];
|
||||||
if (errors.length !== 0) {
|
|
||||||
return INVALID_REQUEST;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
type: req.type,
|
type,
|
||||||
params
|
params: clone(params),
|
||||||
|
hints,
|
||||||
|
requestKey: REQUEST_COUNTER++
|
||||||
};
|
};
|
||||||
}).remember(INVALID_REQUEST).filter(r => r !== INVALID_REQUEST).throttle(500);
|
} else {
|
||||||
const state$ = state<WizardState>({
|
return null;
|
||||||
activeParam: null
|
}
|
||||||
});
|
|
||||||
const updateParams = mutator => workingRequest$.mutate(data => mutator(data.params));
|
|
||||||
const updateState = mutator => state$.mutate(state => mutator(state));
|
|
||||||
const updateParam = (path: ParamsPath, value: OperationParamValue) => {
|
|
||||||
updateParams(params => {
|
|
||||||
// if (operation.onParamsUpdate) {
|
|
||||||
// operation.onParamsUpdate(params, name, value, params[name]);
|
|
||||||
// }
|
|
||||||
if (!Array.isArray(path)) {
|
|
||||||
path = [path]
|
|
||||||
}
|
|
||||||
_.set(params, path, value);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const readParam = (path: ParamsPath) => {
|
|
||||||
return _.get(params, path);
|
|
||||||
};
|
|
||||||
|
|
||||||
const disposerList = createFunctionList();
|
|
||||||
wizCtx = {
|
|
||||||
workingRequest$, materializedWorkingRequest$, state$,
|
|
||||||
updateParams, updateParam, readParam, updateState,
|
|
||||||
operation, changingHistory, noWizardFocus,
|
|
||||||
addDisposer: disposerList.add,
|
|
||||||
dispose: disposerList.call,
|
|
||||||
ID: ++REQUEST_COUNTER,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return wizCtx;
|
|
||||||
}).remember(null);
|
}).remember(null);
|
||||||
|
|
||||||
streams.wizard.wizardContext.pairwise().attach(([oldContext, newContext]) => {
|
const materializedWorkingRequest$ = workingRequest$.map(req => {
|
||||||
if (oldContext) {
|
if (req == null) {
|
||||||
oldContext.dispose();
|
return null;
|
||||||
}
|
}
|
||||||
});
|
let params = {};
|
||||||
|
let errors = [];
|
||||||
services.wizard = {
|
let operation = ctx.services.operation.get(req.type);
|
||||||
|
|
||||||
open: (type, initialOverrides) => {
|
materializeParams(ctx, req.params, operation.schema, params, errors);
|
||||||
streams.wizard.insertOperation.value = {
|
if (errors.length !== 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: req.type,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
}).remember(null).filter(r => r !== null).throttle(500);
|
||||||
|
|
||||||
|
const state$ = state<WizardState>({});
|
||||||
|
let disposerList = createFunctionList();
|
||||||
|
|
||||||
|
// reset effect
|
||||||
|
workingRequest$.pairwise().attach(([old, curr]) => {
|
||||||
|
if (old !== null && old.requestKey !== curr?.requestKey) {
|
||||||
|
console.log("=========> DISPOSE")
|
||||||
|
disposerList.call();
|
||||||
|
disposerList = createFunctionList();
|
||||||
|
state$.next({});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateParams = mutator => workingRequest$.update((req: WorkingRequest) => produce(req, draft => mutator(draft.params)));
|
||||||
|
const updateState = mutator => state$.update((state: WizardState) => produce(state, mutator));
|
||||||
|
const updateParam = (path: ParamsPath, value: OperationParamValue) => {
|
||||||
|
updateParams(params => {
|
||||||
|
// if (operation.onParamsUpdate) {
|
||||||
|
// operation.onParamsUpdate(params, name, value, params[name]);
|
||||||
|
// }
|
||||||
|
if (!Array.isArray(path)) {
|
||||||
|
path = [path]
|
||||||
|
}
|
||||||
|
_.set(params, path, value);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const readParam = (path: ParamsPath) => {
|
||||||
|
return _.get(workingRequest$.value.params, path);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getWorkingRequest = () => workingRequest$.value;
|
||||||
|
|
||||||
|
//legacy
|
||||||
|
streams.wizard = {
|
||||||
|
insertOperation: insertOperation$,
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
insertOperation$.next(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const wizardService: WizardService = {
|
||||||
|
|
||||||
|
open: (type: string, initialOverrides: NewOperationCall) => {
|
||||||
|
streams.wizard.insertOperation.next({
|
||||||
type,
|
type,
|
||||||
initialOverrides
|
initialOverrides
|
||||||
};
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel: () => {
|
cancel,
|
||||||
streams.wizard.insertOperation.value = EMPTY_OBJECT;
|
|
||||||
gotoEditHistoryModeIfNeeded(streams.craft.modifications.value);
|
|
||||||
},
|
|
||||||
|
|
||||||
applyWorkingRequest: () => {
|
applyWorkingRequest: () => {
|
||||||
let {type, params} = streams.wizard.wizardContext.value.workingRequest$.value;
|
let {type, params} = getWorkingRequest();
|
||||||
let request = clone({type, params});
|
let request = clone({type, params});
|
||||||
const setError = error => streams.wizard.wizardContext.mutate(ctx => ctx.state$.mutate(state => state.error = error));
|
const setError = error => state$.mutate(state => state.error = error);
|
||||||
if (streams.wizard.insertOperation.value.type) {
|
if (insertOperation$.value) {
|
||||||
ctx.services.craft.modify(request, () => streams.wizard.insertOperation.value = EMPTY_OBJECT, setError );
|
ctx.craftService.modify(request, cancel, setError);
|
||||||
} else {
|
} else {
|
||||||
ctx.services.craft.modifyInHistoryAndStep(request, () => streams.wizard.effectiveOperation.value = EMPTY_OBJECT, setError);
|
ctx.craftService.modifyInHistoryAndStep(request, () => {}, setError);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
isInProgress: () => streams.wizard.wizardContext.value !== null
|
isInProgress: () => getWorkingRequest() !== null,
|
||||||
|
|
||||||
|
get workingRequest() {
|
||||||
|
return getWorkingRequest();
|
||||||
|
},
|
||||||
|
|
||||||
|
get materializedWorkingRequest() {
|
||||||
|
return materializedWorkingRequest$.value;
|
||||||
|
},
|
||||||
|
|
||||||
|
get operation(): Operation<any> {
|
||||||
|
const req = getWorkingRequest();
|
||||||
|
if (!req) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ctx.operationService.get(req.type);
|
||||||
|
},
|
||||||
|
|
||||||
|
workingRequest$, materializedWorkingRequest$, state$,
|
||||||
|
updateParams, updateParam, readParam, updateState,
|
||||||
|
addDisposer: disposerList.add
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ctx.wizardService = services.wizard = wizardService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module 'context' {
|
||||||
|
interface ApplicationContext {
|
||||||
|
wizardService: WizardService
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function applyOverrides(params, initialOverrides) {
|
function applyOverrides(params, initialOverrides) {
|
||||||
Object.assign(params, initialOverrides);
|
Object.assign(params, initialOverrides);
|
||||||
}
|
}
|
||||||
|
|
||||||
const INVALID_REQUEST = {};
|
|
||||||
let REQUEST_COUNTER = 0;
|
|
||||||
|
|
@ -1,37 +1,36 @@
|
||||||
import {FACE, SHELL} from '../../model/entities';
|
import {FACE, SHELL} from 'cad/model/entities';
|
||||||
import {memoize} from "lodash/function";
|
import {memoize} from "lodash/function";
|
||||||
import {Types} from "cad/craft/schema/types";
|
|
||||||
import {OperationRequest} from "cad/craft/craftPlugin";
|
import {OperationRequest} from "cad/craft/craftPlugin";
|
||||||
import {FlattenPath, ParamsPath, WizardContext} from "cad/craft/wizard/wizardTypes";
|
import {FlattenPath, ParamsPath, WizardService} from "cad/craft/wizard/wizardTypes";
|
||||||
import {OperationParamValue, SchemaField} from "cad/craft/schema/schema";
|
import {OperationParamValue} from "cad/craft/schema/schema";
|
||||||
import {EntityReference, SchemaIndexField} from "cad/craft/operationPlugin";
|
import {EntityReference} from "cad/craft/operationPlugin";
|
||||||
|
import {ApplicationContext} from "context";
|
||||||
|
|
||||||
export function activate(ctx) {
|
export function activate(ctx: ApplicationContext) {
|
||||||
ctx.streams.wizard.wizardContext.attach((wizCtx: WizardContext) => {
|
const wizardService = ctx.wizardService;
|
||||||
|
wizardService.workingRequest$.attach((opRequest: OperationRequest) => {
|
||||||
ctx.services.marker.clear();
|
ctx.services.marker.clear();
|
||||||
if (wizCtx) {
|
if (opRequest) {
|
||||||
const wizardPickHandler = createPickHandlerFromSchema(wizCtx);
|
const wizardPickHandler = createPickHandlerFromSchema(wizardService);
|
||||||
ctx.services.pickControl.setPickHandler(wizardPickHandler);
|
ctx.services.pickControl.setPickHandler(wizardPickHandler);
|
||||||
wizCtx.workingRequest$.attach(({type, params}: OperationRequest) => {
|
const marker = ctx.services.marker;
|
||||||
const marker = ctx.services.marker;
|
marker.startSession();
|
||||||
marker.startSession();
|
let {schemaIndex} = wizardService.operation;
|
||||||
let {schemaIndex} = wizCtx.operation;
|
schemaIndex.entities.forEach(entityRef => {
|
||||||
schemaIndex.entities.forEach(entityRef => {
|
//TODO: move to uiDefinition
|
||||||
//TODO: move to uiDefinition
|
let color = entityRef.metadata.markColor;
|
||||||
let color = entityRef.metadata.markColor;
|
|
||||||
|
|
||||||
let val = wizCtx.readParam(entityRef.field.path);
|
let val = wizardService.readParam(entityRef.field.path);
|
||||||
|
|
||||||
if (Array.isArray(val)) {
|
if (Array.isArray(val)) {
|
||||||
val.forEach(id => marker.mark(id, color));
|
val.forEach(id => marker.mark(id, color));
|
||||||
} else {
|
} else {
|
||||||
if (val) {
|
if (val) {
|
||||||
marker.mark(val, color);
|
marker.mark(val, color);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
marker.commit();
|
|
||||||
});
|
});
|
||||||
|
marker.commit();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ctx.services.pickControl.setPickHandler(null);
|
ctx.services.pickControl.setPickHandler(null);
|
||||||
|
|
@ -52,20 +51,20 @@ const arrayValue = (id, arr) => {
|
||||||
|
|
||||||
const getEntityParams = memoize(schema => Object.keys(schema).filter(key => schema[key].type === 'entity'));
|
const getEntityParams = memoize(schema => Object.keys(schema).filter(key => schema[key].type === 'entity'));
|
||||||
|
|
||||||
function createPickHandlerFromSchema(wizCtx: WizardContext) {
|
function createPickHandlerFromSchema(wizardService: WizardService) {
|
||||||
|
|
||||||
function update(param: ParamsPath, value: OperationParamValue, paramToMakeActive: FlattenPath) {
|
function update(param: ParamsPath, value: OperationParamValue, paramToMakeActive: FlattenPath) {
|
||||||
wizCtx.updateParam(param, value);
|
wizardService.updateParam(param, value);
|
||||||
wizCtx.updateState(state => {
|
wizardService.updateState(state => {
|
||||||
state.activeParam = paramToMakeActive;
|
state.activeParam = paramToMakeActive;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return model => {
|
return model => {
|
||||||
const modelType = model.TYPE;
|
const modelType = model.TYPE;
|
||||||
|
|
||||||
let {schemaIndex} = wizCtx.operation;
|
let {schemaIndex} = wizardService.operation;
|
||||||
let activeEntityRef = () => {
|
let activeEntityRef = () => {
|
||||||
const state = wizCtx.state$.value;
|
const state = wizardService.state$.value;
|
||||||
return schemaIndex.entitiesByFlattenedPaths[state.activeParam];
|
return schemaIndex.entitiesByFlattenedPaths[state.activeParam];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +82,7 @@ function createPickHandlerFromSchema(wizCtx: WizardContext) {
|
||||||
const param = entityRef.field;
|
const param = entityRef.field;
|
||||||
const valueGetter = entityRef.isArray ? arrayValue : singleValue;
|
const valueGetter = entityRef.isArray ? arrayValue : singleValue;
|
||||||
let paramToMakeActive = getNextActiveParam(entityRef);
|
let paramToMakeActive = getNextActiveParam(entityRef);
|
||||||
const currentValue = wizCtx.readParam(param.path);
|
const currentValue = wizardService.readParam(param.path);
|
||||||
update(param.path, valueGetter(id, currentValue), paramToMakeActive.field.flattenedPath);
|
update(param.path, valueGetter(id, currentValue), paramToMakeActive.field.flattenedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +110,7 @@ function createPickHandlerFromSchema(wizCtx: WizardContext) {
|
||||||
|
|
||||||
function deselectIfNeeded(id) {
|
function deselectIfNeeded(id) {
|
||||||
for (let entityRef of schemaIndex.entities) {
|
for (let entityRef of schemaIndex.entities) {
|
||||||
let val = wizCtx.readParam(entityRef.field.path);
|
let val = wizardService.readParam(entityRef.field.path);
|
||||||
if (val === id) {
|
if (val === id) {
|
||||||
update(entityRef.field.path, undefined, entityRef.field.flattenedPath);
|
update(entityRef.field.path, undefined, entityRef.field.flattenedPath);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import {StateStream} from "lstream";
|
import {StateStream} from "lstream";
|
||||||
import {OperationRequest} from "cad/craft/craftPlugin";
|
import {CraftHints, OperationRequest} from "cad/craft/craftPlugin";
|
||||||
import {MaterializedOperationParams, OperationParamValue, OperationParams} from "cad/craft/schema/schema";
|
import {MaterializedOperationParams, OperationParamValue, OperationParams} from "cad/craft/schema/schema";
|
||||||
import {Operation} from "cad/craft/operationPlugin";
|
import {Operation} from "cad/craft/operationPlugin";
|
||||||
|
|
||||||
|
|
@ -10,17 +10,30 @@ export type ParamsPath = ParamsPathSegment[];
|
||||||
export type FlattenPath = string;
|
export type FlattenPath = string;
|
||||||
|
|
||||||
export type WizardState = {
|
export type WizardState = {
|
||||||
activeParam: FlattenPath
|
activeParam?: FlattenPath
|
||||||
|
error?: any
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface WizardContext {
|
export interface WizardService {
|
||||||
|
|
||||||
workingRequest$: StateStream<OperationRequest>;
|
workingRequest$: StateStream<OperationRequest&{hints?: CraftHints}>;
|
||||||
|
|
||||||
materializedWorkingRequest$: StateStream<MaterializedOperationParams>;
|
materializedWorkingRequest$: StateStream<MaterializedOperationParams>;
|
||||||
|
|
||||||
state$: StateStream<WizardState>;
|
state$: StateStream<WizardState>;
|
||||||
|
|
||||||
|
open(type: string, initialOverrides: NewOperationCall);
|
||||||
|
|
||||||
|
cancel();
|
||||||
|
|
||||||
|
applyWorkingRequest();
|
||||||
|
|
||||||
|
isInProgress(): boolean;
|
||||||
|
|
||||||
|
workingRequest: OperationRequest;
|
||||||
|
|
||||||
|
materializedWorkingRequest: any;
|
||||||
|
|
||||||
updateParams: (mutator: (params: OperationParams) => void) => void;
|
updateParams: (mutator: (params: OperationParams) => void) => void;
|
||||||
|
|
||||||
updateParam: (path: ParamsPath, value: OperationParamValue) => void;
|
updateParam: (path: ParamsPath, value: OperationParamValue) => void;
|
||||||
|
|
@ -31,13 +44,11 @@ export interface WizardContext {
|
||||||
|
|
||||||
operation: Operation<any>;
|
operation: Operation<any>;
|
||||||
|
|
||||||
changingHistory: boolean;
|
|
||||||
|
|
||||||
noWizardFocus: boolean;
|
|
||||||
|
|
||||||
addDisposer: (disposer: () => any|void) => void;
|
addDisposer: (disposer: () => any|void) => void;
|
||||||
|
|
||||||
dispose: () => void;
|
}
|
||||||
|
|
||||||
ID: number;
|
export interface NewOperationCall {
|
||||||
|
type: string;
|
||||||
|
initialOverrides: OperationParams;
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import {ComboBoxField, NumberField} from "cad/craft/wizard/components/form/Fields";
|
import {ComboBoxField} from "cad/craft/wizard/components/form/Fields";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {flattenPath, OperationSchema} from "cad/craft/schema/schema";
|
|
||||||
import {FieldBasicProps, fieldToSchemaGeneric} from "cad/mdf/ui/field";
|
import {FieldBasicProps, fieldToSchemaGeneric} from "cad/mdf/ui/field";
|
||||||
import {Types} from "cad/craft/schema/types";
|
import {Types} from "cad/craft/schema/types";
|
||||||
import {ComboBoxOption} from "ui/components/controls/ComboBoxControl";
|
import {ComboBoxOption} from "ui/components/controls/ComboBoxControl";
|
||||||
import {FormContext, FormContextData} from "cad/craft/wizard/components/form/Form";
|
|
||||||
|
|
||||||
export interface ChoiceWidgetProps extends FieldBasicProps {
|
export interface ChoiceWidgetProps extends FieldBasicProps {
|
||||||
|
|
||||||
|
|
@ -19,21 +17,10 @@ export interface ChoiceWidgetProps extends FieldBasicProps {
|
||||||
export function ChoiceWidget(props: ChoiceWidgetProps) {
|
export function ChoiceWidget(props: ChoiceWidgetProps) {
|
||||||
if (!props.style || props.style === 'dropdown') {
|
if (!props.style || props.style === 'dropdown') {
|
||||||
|
|
||||||
return <FormContext.Consumer>
|
return <ComboBoxField name={props.name} defaultValue={props.defaultValue} label={props.label} includeNonExistent>
|
||||||
{
|
{props.values.map(value => <ComboBoxOption value={value} key={value}>{value}</ComboBoxOption>)}
|
||||||
(ctx: FormContextData) => {
|
</ComboBoxField>;
|
||||||
const value = ctx.readParam(props.name);
|
|
||||||
let nonExistent = null;
|
|
||||||
if (!props.values.includes(value as any)) {
|
|
||||||
nonExistent = <ComboBoxOption value={value}>{value ? value+'' : '<empty>'}</ComboBoxOption>
|
|
||||||
}
|
|
||||||
return <ComboBoxField name={props.name} defaultValue={props.defaultValue} label={props.label} >
|
|
||||||
{nonExistent}
|
|
||||||
{props.values.map(value => <ComboBoxOption value={value} key={value}>{value}</ComboBoxOption>)}
|
|
||||||
</ComboBoxField>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</FormContext.Consumer>
|
|
||||||
} else {
|
} else {
|
||||||
throw 'implement me';
|
throw 'implement me';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import {createPreviewer} from './scenePreviewer';
|
|
||||||
|
|
||||||
export function activate(ctx) {
|
|
||||||
let {streams, services} = ctx;
|
|
||||||
|
|
||||||
streams.wizard.wizardContext.attach(wizCtx => {
|
|
||||||
if (!wizCtx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let {operation, materializedWorkingRequest$} = wizCtx;
|
|
||||||
if (operation.previewGeomProvider || operation.previewer) {
|
|
||||||
let previewer = null;
|
|
||||||
materializedWorkingRequest$.attach(({type, params}) => {
|
|
||||||
if (previewer === null) {
|
|
||||||
try {
|
|
||||||
if (operation.previewGeomProvider) {
|
|
||||||
previewer = createPreviewer(operation.previewGeomProvider, services, params);
|
|
||||||
} else if (operation.previewer) {
|
|
||||||
previewer = operation.previewer(ctx, params, wizCtx.updateParams);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
wizCtx.addDisposer(() => {
|
|
||||||
previewer.dispose();
|
|
||||||
ctx.services.viewer.requestRender();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
previewer.update(params);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.services.viewer.requestRender();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
42
web/app/cad/preview/previewPlugin.ts
Normal file
42
web/app/cad/preview/previewPlugin.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import {createPreviewer} from './scenePreviewer';
|
||||||
|
import {ApplicationContext} from "context";
|
||||||
|
|
||||||
|
export function activate(ctx: ApplicationContext) {
|
||||||
|
let previewer = null;
|
||||||
|
ctx.wizardService.materializedWorkingRequest$.attach(materializedWorkingRequest => {
|
||||||
|
if (!materializedWorkingRequest) {
|
||||||
|
previewer = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {type, params} = materializedWorkingRequest;
|
||||||
|
const operation = ctx.wizardService.operation;
|
||||||
|
if (operation.previewGeomProvider || operation.previewer) {
|
||||||
|
if (previewer === null) {
|
||||||
|
let newPreviewer;
|
||||||
|
try {
|
||||||
|
if (operation.previewGeomProvider) {
|
||||||
|
newPreviewer = createPreviewer(operation.previewGeomProvider, ctx.services, params);
|
||||||
|
} else if (operation.previewer) {
|
||||||
|
newPreviewer = operation.previewer(ctx, params, ctx.wizardService.updateParams);
|
||||||
|
}
|
||||||
|
previewer = newPreviewer;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.wizardService.addDisposer(() => {
|
||||||
|
newPreviewer.dispose();
|
||||||
|
previewer = null;
|
||||||
|
ctx.services.viewer.requestRender();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
previewer.update(params);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.services.viewer.requestRender();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import {SketchFormat_V3} from "../sketcher/io";
|
||||||
import {OperationRequest} from "./craft/craftPlugin";
|
import {OperationRequest} from "./craft/craftPlugin";
|
||||||
import {ProjectModel} from "./projectManager/projectManagerPlugin";
|
import {ProjectModel} from "./projectManager/projectManagerPlugin";
|
||||||
import {DebugMode$} from "debugger/Debugger";
|
import {DebugMode$} from "debugger/Debugger";
|
||||||
|
import {fillUpMissingFields} from "cad/craft/schema/initializeBySchema";
|
||||||
|
|
||||||
export const STORAGE_GLOBAL_PREFIX = 'TCAD';
|
export const STORAGE_GLOBAL_PREFIX = 'TCAD';
|
||||||
export const PROJECTS_PREFIX = `${STORAGE_GLOBAL_PREFIX}.projects.`;
|
export const PROJECTS_PREFIX = `${STORAGE_GLOBAL_PREFIX}.projects.`;
|
||||||
|
|
@ -54,6 +55,7 @@ export function initProjectService(ctx: CoreContext, id: string, hints: any) {
|
||||||
let dataStr = ctx.storageService.get(ctx.projectService.projectStorageKey());
|
let dataStr = ctx.storageService.get(ctx.projectService.projectStorageKey());
|
||||||
if (dataStr) {
|
if (dataStr) {
|
||||||
let data = JSON.parse(dataStr);
|
let data = JSON.parse(dataStr);
|
||||||
|
upgradeIfNeeded(data);
|
||||||
loadData(data);
|
loadData(data);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -61,6 +63,16 @@ export function initProjectService(ctx: CoreContext, id: string, hints: any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function upgradeIfNeeded(data: ProjectModel) {
|
||||||
|
if (data.history) {
|
||||||
|
data.history.forEach(req => {
|
||||||
|
const operation = ctx.operationService.get(req.type);
|
||||||
|
if (operation) {
|
||||||
|
fillUpMissingFields(req.params, operation.schema, ctx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function loadData(data: ProjectModel) {
|
function loadData(data: ProjectModel) {
|
||||||
if (data.expressions) {
|
if (data.expressions) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue