mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 16:33:15 +01:00
255 lines
6.2 KiB
TypeScript
255 lines
6.2 KiB
TypeScript
import React from 'react';
|
|
import {state} from 'lstream';
|
|
import {IconType} from "react-icons";
|
|
import {ActionAppearance} from "../actions/actionSystemBundle";
|
|
import {ApplicationContext} from "cad/context";
|
|
import {OperationResult} from "./craftBundle";
|
|
import {OperationSchema, SchemaField, schemaIterator, unwrapMetadata} from "cad/craft/schema/schema";
|
|
import {FormDefinition} from "cad/mdf/ui/uiDefinition";
|
|
import {Types} from "cad/craft/schema/types";
|
|
import {EntityTypeSchema} from "cad/craft/schema/types/entityType";
|
|
import {FlattenPath, ParamsPath} from "cad/craft/wizard/wizardTypes";
|
|
import {IconDeclaration} from "cad/icons/IconDeclaration";
|
|
import {loadDeclarativeForm} from "cad/mdf/declarativeFormLoader";
|
|
import {operationIconToActionIcon} from "cad/craft/operationHelper";
|
|
import {GenerateWorkbenchOperationDocumentationLink} from "doc/documentationHelper";
|
|
|
|
export function activate(ctx: ApplicationContext) {
|
|
|
|
const registry$ = state({});
|
|
|
|
ctx.streams.operation = {
|
|
registry:registry$
|
|
};
|
|
|
|
function addOperation(descriptor: OperationDescriptor<any>, actions) {
|
|
let {id, label, info, icon, actionParams, form, schema} = descriptor;
|
|
|
|
if (!schema) {
|
|
const {schema: derivedSchema, form: loadedForm} = loadDeclarativeForm(form as FormDefinition);
|
|
schema = derivedSchema;
|
|
form = loadedForm;
|
|
}
|
|
|
|
if (!schema.featureId) {
|
|
(schema as any).featureId = {
|
|
type: Types.number,
|
|
optional: false,
|
|
}
|
|
}
|
|
|
|
if (!label) {
|
|
label = id;
|
|
}
|
|
|
|
const appearance: ActionAppearance = {
|
|
label,
|
|
info
|
|
};
|
|
|
|
operationIconToActionIcon(icon, appearance);
|
|
|
|
const opAction = {
|
|
id: id,
|
|
appearance,
|
|
invoke: () => ctx.services.wizard.open(id),
|
|
...actionParams
|
|
};
|
|
|
|
const schemaIndex = createSchemaIndex(schema);
|
|
|
|
let documentationLink = descriptor.documentationLink;
|
|
if (!documentationLink && descriptor.path) {
|
|
documentationLink = GenerateWorkbenchOperationDocumentationLink(descriptor.path);
|
|
}
|
|
|
|
const operation = {
|
|
appearance,
|
|
schemaIndex,
|
|
defaultActiveField: schemaIndex.fields[0]?.flattenedPath, // to be overridden by descriptor
|
|
...descriptor,
|
|
schema, form,
|
|
documentationLink
|
|
};
|
|
|
|
registry$.mutate(registry => registry[id] = operation);
|
|
actions.push(opAction);
|
|
|
|
if (descriptor.masking) {
|
|
descriptor.masking.forEach(masking => {
|
|
|
|
|
|
const appearance = {
|
|
...opAction.appearance,
|
|
label: masking.label,
|
|
info: masking.info,
|
|
};
|
|
|
|
operationIconToActionIcon(masking.icon, appearance);
|
|
|
|
actions.push({
|
|
...opAction,
|
|
id: masking.id,
|
|
appearance,
|
|
invoke: () => ctx.services.wizard.open(id, masking.maskingParams),
|
|
});
|
|
|
|
});
|
|
}
|
|
}
|
|
|
|
function registerOperations(operations) {
|
|
const actions = [];
|
|
for (const op of operations) {
|
|
addOperation(op, actions);
|
|
}
|
|
ctx.actionService.registerActions(actions);
|
|
}
|
|
|
|
function get<T extends FeatureIdParam>(id: string): Operation<T> {
|
|
const op = registry$.value[id];
|
|
if (!op) {
|
|
throw `operation ${id} is not registered`;
|
|
}
|
|
return op;
|
|
}
|
|
|
|
ctx.operationService = {
|
|
registerOperations,
|
|
get
|
|
};
|
|
|
|
ctx.services.operation = ctx.operationService;
|
|
}
|
|
|
|
interface FeatureIdParam {
|
|
featureId: string;
|
|
}
|
|
|
|
export interface Operation<R extends FeatureIdParam> extends OperationDescriptor<R>{
|
|
appearance: {
|
|
id: string;
|
|
label: string;
|
|
info: string;
|
|
icon32: string;
|
|
icon96: string;
|
|
icon: string|IconType;
|
|
};
|
|
schemaIndex: SchemaIndex;
|
|
form: React.FunctionComponent;
|
|
schema: OperationSchema;
|
|
}
|
|
|
|
type OpIcon = IconDeclaration | IconType | string | ((props: any) => JSX.Element);
|
|
|
|
type MaterializedParams = any;
|
|
|
|
export interface OperationDescriptor<R extends FeatureIdParam> {
|
|
id: string;
|
|
label: string;
|
|
info: string;
|
|
icon: OpIcon;
|
|
actionParams?: any;
|
|
run: (params: MaterializedParams, ctx: ApplicationContext, rawParams: R) => OperationResult | Promise<OperationResult>;
|
|
paramsInfo: (params: R) => string,
|
|
previewGeomProvider?: (params: R) => OperationGeometryProvider,
|
|
previewer?: any,
|
|
form: FormDefinition | React.FunctionComponent,
|
|
defaultActiveField?: string,
|
|
schema?: OperationSchema,
|
|
onParamsUpdate?: (params, name, value) => void,
|
|
documentationLink?: string,
|
|
path?: string,
|
|
masking?: {
|
|
id: string,
|
|
label: string;
|
|
info: string,
|
|
icon: OpIcon;
|
|
maskingParams: any;
|
|
}[],
|
|
dynamicLabel?: (params: R) => string,
|
|
dynamicIcon?: (params: R) => OpIcon,
|
|
}
|
|
|
|
export interface OperationService {
|
|
registerOperations(descriptior: OperationDescriptor<any>[]);
|
|
get<T extends FeatureIdParam>(operationId: string): Operation<T>;
|
|
}
|
|
|
|
export type Index<T> = {
|
|
[beanPath: string]: T
|
|
};
|
|
|
|
|
|
export interface SchemaIndexField {
|
|
path: ParamsPath,
|
|
flattenedPath: FlattenPath,
|
|
metadata: SchemaField
|
|
}
|
|
|
|
export interface EntityReference {
|
|
field: SchemaIndexField;
|
|
metadata: EntityTypeSchema;
|
|
isArray: boolean;
|
|
}
|
|
|
|
export interface SchemaIndex {
|
|
fields: SchemaIndexField[],
|
|
entities: EntityReference[],
|
|
fieldsByFlattenedPaths: Index<SchemaIndexField>;
|
|
entitiesByFlattenedPaths: Index<EntityReference>;
|
|
}
|
|
|
|
export interface OperationGeometryProvider {
|
|
|
|
}
|
|
|
|
function createSchemaIndex(schema: OperationSchema): SchemaIndex {
|
|
|
|
const index = {
|
|
fields: [],
|
|
fieldsByFlattenedPaths: {},
|
|
entities: [],
|
|
entitiesByFlattenedPaths: {}
|
|
} as SchemaIndex;
|
|
|
|
schemaIterator(schema, (path, flattenedPath, metadata) => {
|
|
const indexField = {
|
|
path: [...path],
|
|
flattenedPath,
|
|
metadata
|
|
};
|
|
index.fields.push(indexField);
|
|
index.fieldsByFlattenedPaths[flattenedPath] = indexField;
|
|
|
|
});
|
|
|
|
index.fields.forEach(f => {
|
|
|
|
const unwrappedMd = unwrapMetadata(f.metadata);
|
|
|
|
if (unwrappedMd.type !== Types.entity) {
|
|
return;
|
|
}
|
|
const entity: EntityReference= {
|
|
field: f,
|
|
isArray: f.metadata.type === Types.array,
|
|
metadata: unwrappedMd
|
|
};
|
|
|
|
index.entities.push(entity);
|
|
index.entitiesByFlattenedPaths[f.flattenedPath] = entity;
|
|
});
|
|
|
|
return index;
|
|
}
|
|
|
|
|
|
export interface OperationBundleContext {
|
|
|
|
operationService: OperationService;
|
|
}
|
|
|
|
export const BundleName = "@Operation";
|
|
|
|
|