diff --git a/.husky/pre-commit b/.husky/pre-commit index 76031ec..5325f24 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,6 +1,9 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npx lint-staged +# dist/ is tracked and shipped ("files": ["/dist", "/src"]), so a src change +# committed without it leaves consumers on the old code - and a brand-new module +# is worse than stale, since its dist file is absent entirely and the emitted +# code imports something that does not exist. npm run transpile git add dist/ diff --git a/dist/WorkflowDesignerContainer.d.ts b/dist/WorkflowDesignerContainer.d.ts index 07fae2b..afe2c27 100644 --- a/dist/WorkflowDesignerContainer.d.ts +++ b/dist/WorkflowDesignerContainer.d.ts @@ -48,6 +48,12 @@ type WorkflowDesignerContainerBaseProps = { getDefaultComputeConfig: (cluster?: unknown) => Record; generateEntityId: () => string; openDocumentationDialog?: (searchText: string) => void; + /** Fires when unsaved-changes state flips; lets the shell mark Save / guard navigation. */ + onDirtyChange?: (isDirty: boolean) => void; + /** See {@link SubworkflowProps.useUnitInspector}. */ + useUnitInspector?: boolean; + /** See {@link WorkflowDefaultLayoutProps.useHostTheme}. */ + useHostTheme?: boolean; }; export type WorkflowDesignerContainerProps = WorkflowDesignerContainerBaseProps; export default function WorkflowDesignerContainer(containerProps: WorkflowDesignerContainerProps): React.JSX.Element; diff --git a/dist/WorkflowDesignerContainer.d.ts.map b/dist/WorkflowDesignerContainer.d.ts.map index 9c5f3b7..064629c 100644 --- a/dist/WorkflowDesignerContainer.d.ts.map +++ b/dist/WorkflowDesignerContainer.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"WorkflowDesignerContainer.d.ts","sourceRoot":"","sources":["../src/WorkflowDesignerContainer.tsx"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"WorkflowDesignerContainer.d.ts","sourceRoot":"","sources":["../src/WorkflowDesignerContainer.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/WorkflowDesignerContainer.js b/dist/WorkflowDesignerContainer.js index d6e19ef..dc643a0 100644 --- a/dist/WorkflowDesignerContainer.js +++ b/dist/WorkflowDesignerContainer.js @@ -1,15 +1,16 @@ -import { jsx as _jsx } from "react/jsx-runtime"; +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { Utils } from "@mat3ra/utils"; import { Subworkflow, Workflow } from "@mat3ra/wode"; import Box from "@mui/material/Box"; import CircularProgress from "@mui/material/CircularProgress"; -import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; +import { UndoSnackbar } from "./components/common/UndoSnackbar"; import { Workflow as WoveWorkflowDesigner } from "./components/workflows/Workflow"; import { WorkflowComponentsContext } from "./WorkflowComponentsContext"; /** Stable empty-array sentinel so the metaProperties useLayoutEffect dep never spuriously fires. */ const EMPTY_META_PROPERTIES = []; export default function WorkflowDesignerContainer(containerProps) { - const { initialWorkflow, defaultMaterial, metaProperties = EMPTY_META_PROPERTIES, editable, showHistory, workflowHistory, isStandalone, adjustable, showHeader, showMetadata, extraActions = [], accountUsers, accountUsersIsLoading, profile, publicAccount, clusters, dialogs, templates, isLoading, saveWorkflow, isDescriptionEditable, EntityHeaderComponent, EntityNameComponent, MetadataComponent, HistoryComponent, SubworkflowFormTitleComponent, PseudoFormComponent, DataGridComponent, BrillouinZoneImageComponent, getDefaultComputeConfig, generateEntityId, } = containerProps; + const { initialWorkflow, defaultMaterial, metaProperties = EMPTY_META_PROPERTIES, editable, showHistory, workflowHistory, isStandalone, adjustable, showHeader, showMetadata, extraActions = [], accountUsers, accountUsersIsLoading, profile, publicAccount, clusters, dialogs, templates, isLoading, saveWorkflow, isDescriptionEditable, EntityHeaderComponent, EntityNameComponent, MetadataComponent, HistoryComponent, SubworkflowFormTitleComponent, PseudoFormComponent, DataGridComponent, BrillouinZoneImageComponent, getDefaultComputeConfig, generateEntityId, onDirtyChange, useUnitInspector, useHostTheme, } = containerProps; const workflowComponents = useMemo(() => ({ EntityHeaderComponent, EntityNameComponent, @@ -43,9 +44,34 @@ export default function WorkflowDesignerContainer(containerProps) { }; }); const [renderGeneration, setRenderGeneration] = useState(0); + const [removeUndoState, setRemoveUndoState] = useState(null); + const [isDirty, setIsDirty] = useState(false); /** Latest workflow for save; avoids stale reads when `onSave` used `setState(prev => …)`. */ const workflowRef = useRef(state.workflow); workflowRef.current = state.workflow; + /** + * Unsaved-changes baseline. Captured after the first `workflow.render()` pass (not from + * `initialWorkflow`) because rendering injects context into units, which would otherwise + * read as an immediate phantom edit. + */ + const dirtyBaselineRef = useRef(null); + const onDirtyChangeRef = useRef(onDirtyChange); + onDirtyChangeRef.current = onDirtyChange; + useEffect(() => { + const currentJson = JSON.stringify(state.workflow.toJSON()); + if (dirtyBaselineRef.current === null) { + dirtyBaselineRef.current = currentJson; + return; + } + const nextDirty = currentJson !== dirtyBaselineRef.current; + setIsDirty((prev) => { + var _a; + if (prev !== nextDirty) { + (_a = onDirtyChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onDirtyChangeRef, nextDirty); + } + return nextDirty; + }); + }, [state.workflow, renderGeneration]); /** * Sole entry point for `workflow.render()` in the workflow designer UI tree. */ @@ -72,6 +98,15 @@ export default function WorkflowDesignerContainer(containerProps) { setState((prev) => ({ ...prev, workflow: nextWorkflow })); }, []); const onSave = useCallback((omitRedirect) => { + // Reset the unsaved-changes baseline optimistically; save errors surface via alerts. + dirtyBaselineRef.current = JSON.stringify(workflowRef.current.toJSON()); + setIsDirty((prev) => { + var _a; + if (prev) { + (_a = onDirtyChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onDirtyChangeRef, false); + } + return false; + }); saveWorkflow({ workflow: workflowRef.current, omitRedirect }).catch(() => { /* errors reported inside saveWorkflow */ }); @@ -125,14 +160,27 @@ export default function WorkflowDesignerContainer(containerProps) { renderWorkflow(); }, [renderWorkflow, generateEntityId]); const onUnitRemove = useCallback((flowchartId) => { + var _a; if (flowchartId == null) { return; } + const { current } = workflowRef; + const removedUnit = current.unitInstances.find((u) => u.flowchartId === flowchartId); + const snapshot = current.toJSON(); setState((prev) => { const workflow = prev.workflow.clone(); workflow.removeUnit(flowchartId); return { ...prev, workflow }; }); + setRemoveUndoState({ + message: `Removed "${(_a = removedUnit === null || removedUnit === void 0 ? void 0 : removedUnit.name) !== null && _a !== void 0 ? _a : "unit"}"`, + onUndo: () => { + const restored = new Workflow(snapshot); + workflowRef.current = restored; + setState((prev) => ({ ...prev, workflow: restored })); + renderWorkflow(); + }, + }); renderWorkflow(); }, [renderWorkflow]); const onUnitUpdate = useCallback((unit) => { @@ -176,5 +224,5 @@ export default function WorkflowDesignerContainer(containerProps) { py: 4, }, children: _jsx(CircularProgress, {}) })); } - return (_jsx(WorkflowComponentsContext.Provider, { value: workflowComponents, children: _jsx(WoveWorkflowDesigner, { workflow: workflow, jobHasParent: Boolean((state.job || {}).parentJob), isSetPublicVisible: state.isSetPublicVisible || false, isLoading: isLoading, materials: materials, showHeader: showHeader, showMetadata: showMetadata, editable: editable, showHistory: showHistory, workflowHistory: workflowHistory, isStandalone: isStandalone, adjustable: adjustable, metaProperties: metaProperties, extraActions: extraActions, accountUsers: accountUsers, accountUsersIsLoading: accountUsersIsLoading, profile: profile, publicAccount: publicAccount, clusters: clusters, dialogs: dialogs, templates: templates, onUpdate: onUpdate, onSave: onSave, onNameUpdate: onNameUpdate, onUpdateTags: onUpdateTags, onUnitAdd: onUnitAdd, onUnitAddSubworkflowFromConfig: onUnitAddSubworkflowFromConfig, onUnitRemove: onUnitRemove, onUnitUpdate: onUnitUpdate, onSubworkflowUnitUpdate: onSubworkflowUnitUpdate, onRender: renderWorkflow, workflowRenderGeneration: renderGeneration, isDescriptionEditable: isDescriptionEditable }) })); + return (_jsxs(WorkflowComponentsContext.Provider, { value: workflowComponents, children: [_jsx(UndoSnackbar, { state: removeUndoState, onClose: () => setRemoveUndoState(null) }), _jsx(WoveWorkflowDesigner, { useUnitInspector: useUnitInspector, useHostTheme: useHostTheme, workflow: workflow, isDirty: isDirty, jobHasParent: Boolean((state.job || {}).parentJob), isSetPublicVisible: state.isSetPublicVisible || false, isLoading: isLoading, materials: materials, showHeader: showHeader, showMetadata: showMetadata, editable: editable, showHistory: showHistory, workflowHistory: workflowHistory, isStandalone: isStandalone, adjustable: adjustable, metaProperties: metaProperties, extraActions: extraActions, accountUsers: accountUsers, accountUsersIsLoading: accountUsersIsLoading, profile: profile, publicAccount: publicAccount, clusters: clusters, dialogs: dialogs, templates: templates, onUpdate: onUpdate, onSave: onSave, onNameUpdate: onNameUpdate, onUpdateTags: onUpdateTags, onUnitAdd: onUnitAdd, onUnitAddSubworkflowFromConfig: onUnitAddSubworkflowFromConfig, onUnitRemove: onUnitRemove, onUnitUpdate: onUnitUpdate, onSubworkflowUnitUpdate: onSubworkflowUnitUpdate, onRender: renderWorkflow, workflowRenderGeneration: renderGeneration, isDescriptionEditable: isDescriptionEditable })] })); } diff --git a/dist/components/common/BrillouinZone.d.ts b/dist/components/common/BrillouinZone.d.ts new file mode 100644 index 0000000..c0ce69e --- /dev/null +++ b/dist/components/common/BrillouinZone.d.ts @@ -0,0 +1,25 @@ +import React from "react"; +import { type Vector3 } from "./brillouinZoneGeometry"; +export interface BrillouinZoneProps { + /** + * Reciprocal vectors of the material's own lattice + * (`new ReciprocalLattice(material.lattice).reciprocalVectors`). Preferred: exact for this + * material. wove's component contract does not carry them, so the call site supplies them. + */ + reciprocalVectors?: [Vector3, Vector3, Vector3]; + /** Bravais lattice type, e.g. `FCC` — used only when {@link reciprocalVectors} is absent. */ + latticeType?: string; + /** Web-app asset path wove derives from the lattice; used only as a last fallback. */ + imgSrc?: string; + description?: string; +} +/** + * Draws the first Brillouin zone for a lattice type instead of fetching a per-lattice PNG. + * + * Hosts that ship their own artwork keep passing `BrillouinZoneImageComponent`; this is the + * default for everyone else, where `imgSrc` points at an asset that does not exist (see + * {@link computeBrillouinZoneFaces}). Falls back to the image for lattice types it cannot model. + */ +export declare function BrillouinZone({ reciprocalVectors, latticeType, imgSrc, description, }: BrillouinZoneProps): React.JSX.Element; +export default BrillouinZone; +//# sourceMappingURL=BrillouinZone.d.ts.map \ No newline at end of file diff --git a/dist/components/common/BrillouinZone.d.ts.map b/dist/components/common/BrillouinZone.d.ts.map new file mode 100644 index 0000000..448d054 --- /dev/null +++ b/dist/components/common/BrillouinZone.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"BrillouinZone.d.ts","sourceRoot":"","sources":["../../../src/components/common/BrillouinZone.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,EAEH,KAAK,OAAO,EAGf,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,kBAAkB;IAC/B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAChD,6FAA6F;IAC7F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAsED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAC1B,iBAAiB,EACjB,WAAW,EACX,MAAM,EACN,WAAW,GACd,EAAE,kBAAkB,qBA2DpB;AAED,eAAe,aAAa,CAAC"} \ No newline at end of file diff --git a/dist/components/common/BrillouinZone.js b/dist/components/common/BrillouinZone.js new file mode 100644 index 0000000..fc87551 --- /dev/null +++ b/dist/components/common/BrillouinZone.js @@ -0,0 +1,71 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import Box from "@mui/material/Box"; +import { useTheme } from "@mui/material/styles"; +import Typography from "@mui/material/Typography"; +import { useMemo } from "react"; +import { computeBrillouinZoneFaces, computeBrillouinZoneFacesFromReciprocalVectors, } from "./brillouinZoneGeometry"; +/** Fixed three-quarter view; the zone is a static illustration, not an interactive scene. */ +const VIEW_YAW = Math.PI / 5; +const VIEW_PITCH = Math.PI / 7; +const SIZE = 220; +const PADDING = 12; +function project([x, y, z]) { + const cosYaw = Math.cos(VIEW_YAW); + const sinYaw = Math.sin(VIEW_YAW); + const rotatedX = x * cosYaw + z * sinYaw; + const rotatedZ = -x * sinYaw + z * cosYaw; + const cosPitch = Math.cos(VIEW_PITCH); + const sinPitch = Math.sin(VIEW_PITCH); + const rotatedY = y * cosPitch - rotatedZ * sinPitch; + // SVG's y axis grows downward, hence the negation. + return { x: rotatedX, y: -rotatedY, depth: y * sinPitch + rotatedZ * cosPitch }; +} +function projectFaces(faces) { + const projectedByFace = faces.map((face) => face.vertices.map(project)); + const all = projectedByFace.flat(); + const minX = Math.min(...all.map((p) => p.x)); + const maxX = Math.max(...all.map((p) => p.x)); + const minY = Math.min(...all.map((p) => p.y)); + const maxY = Math.max(...all.map((p) => p.y)); + const span = Math.max(maxX - minX, maxY - minY) || 1; + const scaleFactor = (SIZE - 2 * PADDING) / span; + const offsetX = PADDING + (SIZE - 2 * PADDING - (maxX - minX) * scaleFactor) / 2; + const offsetY = PADDING + (SIZE - 2 * PADDING - (maxY - minY) * scaleFactor) / 2; + return (projectedByFace + .map((projected, index) => { + const points = projected + .map((p) => `${(offsetX + (p.x - minX) * scaleFactor).toFixed(2)},${(offsetY + + (p.y - minY) * scaleFactor).toFixed(2)}`) + .join(" "); + const depth = projected.reduce((sum, p) => sum + p.depth, 0) / (projected.length || 1); + // Lambert-ish shading from a light above and to the viewer's left. + const [nx, ny, nz] = faces[index].normal; + const shade = Math.max(0, nx * -0.3 + ny * 0.55 + nz * 0.78); + return { points, depth, shade }; + }) + // Painter's algorithm: the zone is convex, so far-to-near ordering hides back faces. + .sort((left, right) => left.depth - right.depth)); +} +/** + * Draws the first Brillouin zone for a lattice type instead of fetching a per-lattice PNG. + * + * Hosts that ship their own artwork keep passing `BrillouinZoneImageComponent`; this is the + * default for everyone else, where `imgSrc` points at an asset that does not exist (see + * {@link computeBrillouinZoneFaces}). Falls back to the image for lattice types it cannot model. + */ +export function BrillouinZone({ reciprocalVectors, latticeType, imgSrc, description, }) { + const theme = useTheme(); + const faces = useMemo(() => reciprocalVectors + ? computeBrillouinZoneFacesFromReciprocalVectors(reciprocalVectors) + : computeBrillouinZoneFaces(latticeType !== null && latticeType !== void 0 ? latticeType : ""), [reciprocalVectors, latticeType]); + const projected = useMemo(() => (faces ? projectFaces(faces) : null), [faces]); + if (!projected) { + if (!imgSrc) + return null; + return (_jsx(Box, { className: "brillouin-zone brillouin-zone--image", children: _jsx("img", { src: imgSrc, alt: description || "Brillouin zone", style: { maxWidth: "100%" } }) })); + } + const faceColor = theme.palette.primary.main; + const edgeColor = theme.palette.mode === "dark" ? "#0d1117" : "#ffffff"; + return (_jsxs(Box, { className: "brillouin-zone", "data-tid": "brillouin-zone", sx: { my: 1 }, children: [_jsx("svg", { width: SIZE, height: SIZE, viewBox: `0 0 ${SIZE} ${SIZE}`, role: "img", "aria-label": `First Brillouin zone of a ${latticeType} lattice`, children: projected.map((face) => (_jsx("polygon", { points: face.points, fill: faceColor, fillOpacity: 0.25 + 0.6 * face.shade, stroke: edgeColor, strokeWidth: 1, strokeLinejoin: "round" }, face.points))) }), _jsxs(Typography, { variant: "caption", color: "text.secondary", component: "div", children: ["First Brillouin zone \u2014 ", latticeType, " lattice"] }), description ? (_jsx(Typography, { variant: "caption", color: "text.secondary", component: "div", children: description })) : null] })); +} +export default BrillouinZone; diff --git a/dist/components/common/UndoSnackbar.d.ts b/dist/components/common/UndoSnackbar.d.ts new file mode 100644 index 0000000..69cf600 --- /dev/null +++ b/dist/components/common/UndoSnackbar.d.ts @@ -0,0 +1,15 @@ +import React from "react"; +export type UndoSnackbarState = { + message: string; + onUndo: () => void; +} | null; +/** + * Post-removal "Undo" affordance: the removal is applied immediately and can be reverted + * within {@link AUTO_HIDE_MS}. Rendered by both the workflow-level container (subworkflow + * removals) and {@link Subworkflow} (unit removals). + */ +export declare function UndoSnackbar({ state, onClose, }: { + state: UndoSnackbarState; + onClose: () => void; +}): React.JSX.Element; +//# sourceMappingURL=UndoSnackbar.d.ts.map \ No newline at end of file diff --git a/dist/components/common/UndoSnackbar.d.ts.map b/dist/components/common/UndoSnackbar.d.ts.map new file mode 100644 index 0000000..2ead80c --- /dev/null +++ b/dist/components/common/UndoSnackbar.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"UndoSnackbar.d.ts","sourceRoot":"","sources":["../../../src/components/common/UndoSnackbar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,MAAM,iBAAiB,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,IAAI,CAAC;CACtB,GAAG,IAAI,CAAC;AAIT;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,OAAO,GACV,EAAE;IACC,KAAK,EAAE,iBAAiB,CAAC;IACzB,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB,qBA2BA"} \ No newline at end of file diff --git a/dist/components/common/UndoSnackbar.js b/dist/components/common/UndoSnackbar.js new file mode 100644 index 0000000..c613a2f --- /dev/null +++ b/dist/components/common/UndoSnackbar.js @@ -0,0 +1,20 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +import Button from "@mui/material/Button"; +import Snackbar from "@mui/material/Snackbar"; +const AUTO_HIDE_MS = 10000; +/** + * Post-removal "Undo" affordance: the removal is applied immediately and can be reverted + * within {@link AUTO_HIDE_MS}. Rendered by both the workflow-level container (subworkflow + * removals) and {@link Subworkflow} (unit removals). + */ +export function UndoSnackbar({ state, onClose, }) { + var _a; + return (_jsx(Snackbar, { open: Boolean(state), autoHideDuration: AUTO_HIDE_MS, onClose: (_event, reason) => { + if (reason === "clickaway") + return; + onClose(); + }, message: (_a = state === null || state === void 0 ? void 0 : state.message) !== null && _a !== void 0 ? _a : "", anchorOrigin: { vertical: "bottom", horizontal: "center" }, "data-tid": "undo-snackbar", action: _jsx(Button, { color: "secondary", size: "small", "data-tid": "undo-remove", onClick: () => { + state === null || state === void 0 ? void 0 : state.onUndo(); + onClose(); + }, children: "Undo" }) })); +} diff --git a/dist/components/common/brillouinZoneForProvider.d.ts b/dist/components/common/brillouinZoneForProvider.d.ts new file mode 100644 index 0000000..30be447 --- /dev/null +++ b/dist/components/common/brillouinZoneForProvider.d.ts @@ -0,0 +1,11 @@ +import React from "react"; +import { type BrillouinZoneProps } from "./BrillouinZone"; +/** + * Binds {@link BrillouinZone} to a context provider's own material, so the zone is computed from + * that material's reciprocal lattice rather than from representative ratios for its lattice type. + * + * wove passes only `latticeType`/`imgSrc` to the injected component, but the call site has the + * provider — and therefore the material — in hand. + */ +export declare function brillouinZoneComponentForProvider(provider: unknown): React.ComponentType; +//# sourceMappingURL=brillouinZoneForProvider.d.ts.map \ No newline at end of file diff --git a/dist/components/common/brillouinZoneForProvider.d.ts.map b/dist/components/common/brillouinZoneForProvider.d.ts.map new file mode 100644 index 0000000..18a6fcf --- /dev/null +++ b/dist/components/common/brillouinZoneForProvider.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"brillouinZoneForProvider.d.ts","sourceRoot":"","sources":["../../../src/components/common/brillouinZoneForProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AA8BzE;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAC7C,QAAQ,EAAE,OAAO,GAClB,KAAK,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAgBzC"} \ No newline at end of file diff --git a/dist/components/common/brillouinZoneForProvider.js b/dist/components/common/brillouinZoneForProvider.js new file mode 100644 index 0000000..3ea523c --- /dev/null +++ b/dist/components/common/brillouinZoneForProvider.js @@ -0,0 +1,47 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +import { ReciprocalLattice } from "@mat3ra/made"; +import { BrillouinZone } from "./BrillouinZone"; +function reciprocalVectorsFromProvider(provider) { + var _a; + const lattice = (_a = provider === null || provider === void 0 ? void 0 : provider.material) === null || _a === void 0 ? void 0 : _a.lattice; + if (!lattice) { + return undefined; + } + try { + const vectors = new ReciprocalLattice(lattice).reciprocalVectors; + return (vectors === null || vectors === void 0 ? void 0 : vectors.length) === 3 ? vectors : undefined; + } + catch (_b) { + // Malformed lattice: fall back to the lattice-type approximation inside the component. + return undefined; + } +} +/** + * Component identity must be stable across renders or React remounts the SVG on every keystroke + * in the k-path form; the provider instance is the natural cache key. + */ +const componentByProvider = new WeakMap(); +/** + * Binds {@link BrillouinZone} to a context provider's own material, so the zone is computed from + * that material's reciprocal lattice rather than from representative ratios for its lattice type. + * + * wove passes only `latticeType`/`imgSrc` to the injected component, but the call site has the + * provider — and therefore the material — in hand. + */ +export function brillouinZoneComponentForProvider(provider) { + if (!provider || typeof provider !== "object") { + return BrillouinZone; + } + const cached = componentByProvider.get(provider); + if (cached) { + return cached; + } + const reciprocalVectors = reciprocalVectorsFromProvider(provider); + const component = reciprocalVectors + ? // eslint-disable-next-line react/jsx-props-no-spreading + (props) => _jsx(BrillouinZone, { ...props, reciprocalVectors: reciprocalVectors }) + : BrillouinZone; + component.displayName = "BrillouinZoneForProvider"; + componentByProvider.set(provider, component); + return component; +} diff --git a/dist/components/common/brillouinZoneGeometry.d.ts b/dist/components/common/brillouinZoneGeometry.d.ts new file mode 100644 index 0000000..7d7e88a --- /dev/null +++ b/dist/components/common/brillouinZoneGeometry.d.ts @@ -0,0 +1,37 @@ +/** + * First Brillouin zone geometry — the Wigner-Seitz cell of the reciprocal lattice. + * + * `@mat3ra/wove` renders the zone by pointing an `` at + * `/images/brillouin_zone/.png`, an asset that ships with the web app only: every + * other consumer of the designer (standalone demo, Storybook, embedders) gets a broken image, + * and the absolute path cannot resolve under a non-root deployment base. The lattice type is + * already known at that point, so the zone can be derived instead of fetched. + */ +export type Vector3 = [number, number, number]; +export interface BrillouinZoneFace { + /** Polygon vertices in reciprocal space, ordered counter-clockwise about {@link normal}. */ + vertices: Vector3[]; + /** Outward unit normal — the reciprocal lattice vector whose bisector plane cuts this face. */ + normal: Vector3; +} +/** + * Builds the first Brillouin zone from the reciprocal lattice vectors: the set of points + * closer to the origin than to any other reciprocal lattice point, i.e. the intersection of + * the half-spaces `x·G ≤ |G|²/2`. Vertices are the plane triple-intersections that satisfy + * every other half-space; faces group the vertices lying on each plane. + * + * Prefer this over {@link computeBrillouinZoneFaces}: it is exact for the material at hand, + * where the lattice *type* alone leaves the cell shape underdetermined. Pass + * `new ReciprocalLattice(material.lattice).reciprocalVectors` from `@mat3ra/made`. + */ +export declare function computeBrillouinZoneFacesFromReciprocalVectors(vectors: [Vector3, Vector3, Vector3]): BrillouinZoneFace[] | null; +/** + * Zone for a Bravais lattice *type*, for callers that have no material to hand. + * + * The type alone does not fix the cell: non-cubic systems have c/a and angle freedom, so + * {@link PRIMITIVE_VECTORS_BY_LATTICE_TYPE} stands in representative ratios — the same + * compromise a single canonical image per type makes. Exact for the cubic systems. + */ +export declare function computeBrillouinZoneFaces(latticeType: string): BrillouinZoneFace[] | null; +export declare const SUPPORTED_LATTICE_TYPES: string[]; +//# sourceMappingURL=brillouinZoneGeometry.d.ts.map \ No newline at end of file diff --git a/dist/components/common/brillouinZoneGeometry.d.ts.map b/dist/components/common/brillouinZoneGeometry.d.ts.map new file mode 100644 index 0000000..8aedc88 --- /dev/null +++ b/dist/components/common/brillouinZoneGeometry.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"brillouinZoneGeometry.d.ts","sourceRoot":"","sources":["../../../src/components/common/brillouinZoneGeometry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAC9B,4FAA4F;IAC5F,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,+FAA+F;IAC/F,MAAM,EAAE,OAAO,CAAC;CACnB;AAsJD;;;;;;;;;GASG;AACH,wBAAgB,8CAA8C,CAC1D,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,GACrC,iBAAiB,EAAE,GAAG,IAAI,CAqF5B;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,EAAE,GAAG,IAAI,CAUzF;AAED,eAAO,MAAM,uBAAuB,UAAiD,CAAC"} \ No newline at end of file diff --git a/dist/components/common/brillouinZoneGeometry.js b/dist/components/common/brillouinZoneGeometry.js new file mode 100644 index 0000000..949f762 --- /dev/null +++ b/dist/components/common/brillouinZoneGeometry.js @@ -0,0 +1,233 @@ +/** + * First Brillouin zone geometry — the Wigner-Seitz cell of the reciprocal lattice. + * + * `@mat3ra/wove` renders the zone by pointing an `` at + * `/images/brillouin_zone/.png`, an asset that ships with the web app only: every + * other consumer of the designer (standalone demo, Storybook, embedders) gets a broken image, + * and the absolute path cannot resolve under a non-root deployment base. The lattice type is + * already known at that point, so the zone can be derived instead of fetched. + */ +const SQRT3_OVER_2 = Math.sqrt(3) / 2; +/** + * Primitive vectors per Bravais lattice type, in units of `a`. Non-cubic types have shape + * degrees of freedom (c/a, angles) that the lattice *type* alone does not fix; those use + * representative ratios, matching what a single canonical image per type also depicts. + */ +const PRIMITIVE_VECTORS_BY_LATTICE_TYPE = { + CUB: [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + ], + FCC: [ + [0, 0.5, 0.5], + [0.5, 0, 0.5], + [0.5, 0.5, 0], + ], + BCC: [ + [-0.5, 0.5, 0.5], + [0.5, -0.5, 0.5], + [0.5, 0.5, -0.5], + ], + TET: [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1.4], + ], + BCT: [ + [-0.5, 0.5, 0.7], + [0.5, -0.5, 0.7], + [0.5, 0.5, -0.7], + ], + ORC: [ + [1, 0, 0], + [0, 1.3, 0], + [0, 0, 1.7], + ], + ORCF: [ + [0, 0.65, 0.85], + [0.5, 0, 0.85], + [0.5, 0.65, 0], + ], + ORCI: [ + [-0.5, 0.65, 0.85], + [0.5, -0.65, 0.85], + [0.5, 0.65, -0.85], + ], + ORCC: [ + [0.5, -0.65, 0], + [0.5, 0.65, 0], + [0, 0, 1.7], + ], + HEX: [ + [0.5, -SQRT3_OVER_2, 0], + [0.5, SQRT3_OVER_2, 0], + [0, 0, 1.6], + ], + RHL: [ + [0.9, -0.5, 0.3], + [0.9, 0.5, 0.3], + [0.2, 0, 1.05], + ], + MCL: [ + [1, 0, 0], + [0, 1.2, 0], + [0, 0.55, 1.4], + ], + MCLC: [ + [0.5, 0.6, 0], + [-0.5, 0.6, 0], + [0, 0.55, 1.4], + ], + TRI: [ + [1, 0, 0], + [0.25, 1.15, 0], + [0.3, 0.35, 1.3], + ], +}; +function cross(a, b) { + return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; +} +function dot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} +function subtract(a, b) { + return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; +} +function scale(a, factor) { + return [a[0] * factor, a[1] * factor, a[2] * factor]; +} +function length(a) { + return Math.sqrt(dot(a, a)); +} +function normalize(a) { + const magnitude = length(a); + return magnitude === 0 ? [0, 0, 0] : scale(a, 1 / magnitude); +} +/** + * `2π` is omitted: it scales every reciprocal vector equally, and the zone is drawn normalized. + */ +function reciprocalVectors(a1, a2, a3) { + const volume = dot(a1, cross(a2, a3)); + if (Math.abs(volume) < 1e-12) { + return null; + } + return [ + scale(cross(a2, a3), 1 / volume), + scale(cross(a3, a1), 1 / volume), + scale(cross(a1, a2), 1 / volume), + ]; +} +/** Solves `M x = rhs` for 3×3 `M` by Cramer's rule; null when `M` is singular. */ +function solve3x3(rows, rhs) { + const determinant = dot(rows[0], cross(rows[1], rows[2])); + if (Math.abs(determinant) < 1e-9) { + return null; + } + /** Determinant of `rows` with column `index` replaced by `rhs`. */ + const replacedDeterminant = (index) => { + const replaced = rows.map((row, rowIndex) => { + const next = [...row]; + next[index] = rhs[rowIndex]; + return next; + }); + return dot(replaced[0], cross(replaced[1], replaced[2])); + }; + return [ + replacedDeterminant(0) / determinant, + replacedDeterminant(1) / determinant, + replacedDeterminant(2) / determinant, + ]; +} +const TOLERANCE = 1e-7; +/** + * Builds the first Brillouin zone from the reciprocal lattice vectors: the set of points + * closer to the origin than to any other reciprocal lattice point, i.e. the intersection of + * the half-spaces `x·G ≤ |G|²/2`. Vertices are the plane triple-intersections that satisfy + * every other half-space; faces group the vertices lying on each plane. + * + * Prefer this over {@link computeBrillouinZoneFaces}: it is exact for the material at hand, + * where the lattice *type* alone leaves the cell shape underdetermined. Pass + * `new ReciprocalLattice(material.lattice).reciprocalVectors` from `@mat3ra/made`. + */ +export function computeBrillouinZoneFacesFromReciprocalVectors(vectors) { + const [b1, b2, b3] = vectors; + if ([b1, b2, b3].some((vector) => !vector || vector.length !== 3 || vector.some(Number.isNaN))) { + return null; + } + const reciprocalLatticePoints = []; + for (let h = -2; h <= 2; h += 1) { + for (let k = -2; k <= 2; k += 1) { + for (let l = -2; l <= 2; l += 1) { + if (h !== 0 || k !== 0 || l !== 0) { + reciprocalLatticePoints.push([ + h * b1[0] + k * b2[0] + l * b3[0], + h * b1[1] + k * b2[1] + l * b3[1], + h * b1[2] + k * b2[2] + l * b3[2], + ]); + } + } + } + } + // Only the nearest shells can bound the cell; trimming keeps the triple loop small. + const planes = reciprocalLatticePoints + .sort((left, right) => length(left) - length(right)) + .slice(0, 40) + .map((g) => ({ normal: g, offset: dot(g, g) / 2 })); + const isInsideCell = (point) => planes.every((plane) => dot(point, plane.normal) <= plane.offset + TOLERANCE); + const vertices = []; + for (let i = 0; i < planes.length; i += 1) { + for (let j = i + 1; j < planes.length; j += 1) { + for (let k = j + 1; k < planes.length; k += 1) { + const point = solve3x3([planes[i].normal, planes[j].normal, planes[k].normal], [planes[i].offset, planes[j].offset, planes[k].offset]); + const isCellVertex = Boolean(point) && isInsideCell(point); + const isDuplicate = isCellVertex && + vertices.some((existing) => length(subtract(existing, point)) < 1e-6); + if (isCellVertex && !isDuplicate) { + vertices.push(point); + } + } + } + } + if (vertices.length < 4) { + return null; + } + const faces = []; + planes.forEach((plane) => { + const onPlane = vertices.filter((vertex) => Math.abs(dot(vertex, plane.normal) - plane.offset) < 1e-6); + if (onPlane.length < 3) + return; + // Order the polygon by angle around the face normal, in an in-plane basis. + const normal = normalize(plane.normal); + const centroid = scale(onPlane.reduce((sum, v) => [sum[0] + v[0], sum[1] + v[1], sum[2] + v[2]], [0, 0, 0]), 1 / onPlane.length); + const reference = normalize(subtract(onPlane[0], centroid)); + const bitangent = cross(normal, reference); + const ordered = [...onPlane].sort((left, right) => { + const leftOffset = subtract(left, centroid); + const rightOffset = subtract(right, centroid); + return (Math.atan2(dot(leftOffset, bitangent), dot(leftOffset, reference)) - + Math.atan2(dot(rightOffset, bitangent), dot(rightOffset, reference))); + }); + faces.push({ vertices: ordered, normal }); + }); + return faces.length >= 4 ? faces : null; +} +/** + * Zone for a Bravais lattice *type*, for callers that have no material to hand. + * + * The type alone does not fix the cell: non-cubic systems have c/a and angle freedom, so + * {@link PRIMITIVE_VECTORS_BY_LATTICE_TYPE} stands in representative ratios — the same + * compromise a single canonical image per type makes. Exact for the cubic systems. + */ +export function computeBrillouinZoneFaces(latticeType) { + const key = String(latticeType || "") + .toUpperCase() + .split(/[_\-\s]/)[0]; + const primitiveVectors = PRIMITIVE_VECTORS_BY_LATTICE_TYPE[key]; + if (!primitiveVectors) { + return null; + } + const reciprocal = reciprocalVectors(...primitiveVectors); + return reciprocal ? computeBrillouinZoneFacesFromReciprocalVectors(reciprocal) : null; +} +export const SUPPORTED_LATTICE_TYPES = Object.keys(PRIMITIVE_VECTORS_BY_LATTICE_TYPE); diff --git a/dist/components/subworkflows/ImportantSettings.d.ts b/dist/components/subworkflows/ImportantSettings.d.ts index c5a76a3..8042d70 100644 --- a/dist/components/subworkflows/ImportantSettings.d.ts +++ b/dist/components/subworkflows/ImportantSettings.d.ts @@ -1,4 +1,4 @@ -import { type Subworkflow } from "@mat3ra/wode"; +import { type ExecutionUnit, type Subworkflow } from "@mat3ra/wode"; import React from "react"; interface ImportantSettingsProps { subworkflow: Subworkflow; @@ -7,6 +7,12 @@ interface ImportantSettingsProps { id?: string; onContextChanged: () => void; } +interface ImportantSettingsForUnitProps { + unit: ExecutionUnit; + unitIndex: number; + onContextChanged: () => void; +} +export declare function ImportantSettingsForUnit({ unit, unitIndex, onContextChanged, }: ImportantSettingsForUnitProps): React.JSX.Element; export declare function ImportantSettings({ subworkflow, role, className, id, onContextChanged, }: ImportantSettingsProps): React.JSX.Element; export {}; //# sourceMappingURL=ImportantSettings.d.ts.map \ No newline at end of file diff --git a/dist/components/subworkflows/ImportantSettings.d.ts.map b/dist/components/subworkflows/ImportantSettings.d.ts.map index 7f9aeaa..30d6124 100644 --- a/dist/components/subworkflows/ImportantSettings.d.ts.map +++ b/dist/components/subworkflows/ImportantSettings.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"ImportantSettings.d.ts","sourceRoot":"","sources":["../../../src/components/subworkflows/ImportantSettings.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA+C,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAK7F,OAAO,KAAK,MAAM,OAAO,CAAC;AAc1B,UAAU,sBAAsB;IAC5B,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAChC;AA6LD,wBAAgB,iBAAiB,CAAC,EAC9B,WAAW,EACX,IAAI,EACJ,SAAS,EACT,EAAE,EACF,gBAAgB,GACnB,EAAE,sBAAsB,qBAmBxB"} \ No newline at end of file +{"version":3,"file":"ImportantSettings.d.ts","sourceRoot":"","sources":["../../../src/components/subworkflows/ImportantSettings.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAK7F,OAAO,KAAK,MAAM,OAAO,CAAC;AAe1B,UAAU,sBAAsB;IAC5B,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAChC;AAqBD,UAAU,6BAA6B;IACnC,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,wBAAgB,wBAAwB,CAAC,EACrC,IAAI,EACJ,SAAS,EACT,gBAAgB,GACnB,EAAE,6BAA6B,qBAoE/B;AAqGD,wBAAgB,iBAAiB,CAAC,EAC9B,WAAW,EACX,IAAI,EACJ,SAAS,EACT,EAAE,EACF,gBAAgB,GACnB,EAAE,sBAAsB,qBAmBxB"} \ No newline at end of file diff --git a/dist/components/subworkflows/ImportantSettings.js b/dist/components/subworkflows/ImportantSettings.js index 08f5b7a..b92372e 100644 --- a/dist/components/subworkflows/ImportantSettings.js +++ b/dist/components/subworkflows/ImportantSettings.js @@ -7,6 +7,7 @@ import Typography from "@mui/material/Typography"; import ajv from "@rjsf/validator-ajv8"; import React from "react"; import { useWorkflowComponents } from "../../WorkflowComponentsContext"; +import { brillouinZoneComponentForProvider } from "../common/brillouinZoneForProvider"; import { mergeUiSchemaWithDefaultFieldStyles } from "./importantSettingsFormUtils"; /** * Use schema `type`, not `instanceof`. Job/workflow units are built via Meteor-compiled @@ -26,17 +27,19 @@ function getProviderTitle(provider) { switch (provider.name) { case "boundaryConditions": return "Boundary Conditions"; + case "cutoffs": + return "Planewave Cutoffs"; default: return provider.name; } } -function ImportantSettingsForUnit({ unit, unitIndex, onContextChanged, }) { +export function ImportantSettingsForUnit({ unit, unitIndex, onContextChanged, }) { const [formRevision, setFormRevision] = React.useState(0); const { SubworkflowFormTitleComponent, BrillouinZoneImageComponent } = useWorkflowComponents(); return (_jsxs(Box, { my: 2, className: "important-settings-for-unit ImportantSettingsForUnit", id: unit.flowchartId, "data-tid": unit.name, children: [_jsx(SubworkflowFormTitleComponent, { title: `Unit ${unitIndex}: ${unit.name}` }), _jsx(Box, { ml: 3, children: getUnitImportantSettingsProviders(unit).map((provider, index) => { const title = getProviderTitle(provider); const data = provider.getData(); - return (_jsxs(Box, { className: "ImportantSettingsForUnit-Box", my: 2, "data-form-revision": formRevision, "data-tid": title, children: [_jsx(Typography, { variant: "h6", children: title }), _jsx(ExtraImportantSettingsByContextProvider, { provider: provider, BrillouinZoneImageComponent: BrillouinZoneImageComponent }), _jsx(RJSForm, { schema: provider.jsonSchema, validator: ajv, uiSchema: provider.uiSchema, formData: data, experimental_defaultFormStateBehavior: { + return (_jsxs(Box, { className: "ImportantSettingsForUnit-Box", my: 2, "data-form-revision": formRevision, "data-tid": title, children: [_jsx(Typography, { variant: "h6", children: title }), _jsx(ExtraImportantSettingsByContextProvider, { provider: provider, BrillouinZoneImageComponent: BrillouinZoneImageComponent !== null && BrillouinZoneImageComponent !== void 0 ? BrillouinZoneImageComponent : brillouinZoneComponentForProvider(provider) }), _jsx(RJSForm, { schema: provider.jsonSchema, validator: ajv, uiSchema: provider.uiSchema, formData: data, experimental_defaultFormStateBehavior: { mergeDefaultsIntoFormData: "useDefaultIfFormDataUndefined", }, onChange: ({ formData }) => { const rootSchema = provider.jsonSchema; @@ -85,7 +88,7 @@ function ImportantSettingsForSubworkflow({ subworkflow, onContextChanged, }) { return (_jsxs(Box, { className: "ImportantSettingsForSubworkflow", my: 2, id: subworkflow.id, children: [_jsx(SubworkflowFormTitleComponent, { title: "Settings global to this Subworkflow" }), _jsx(Box, { ml: 3, mt: 2, children: groups.map((group) => { const [{ provider: firstProvider }] = group; const data = firstProvider.getData(); - return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", children: getProviderTitle(firstProvider) }), _jsx(RJSForm, { validator: ajv, schema: firstProvider.jsonSchema, uiSchema: mergeUiSchemaWithDefaultFieldStyles(firstProvider.uiSchema), formData: data, + return (_jsxs(Box, { children: [_jsx(Typography, { variant: "h6", children: getProviderTitle(firstProvider) }), _jsx(RJSForm, { validator: ajv, schema: firstProvider.jsonSchema, uiSchema: mergeUiSchemaWithDefaultFieldStyles(firstProvider.uiSchema, firstProvider.name), formData: data, // fields={firstProvider.fields} // widgets={{ CheckboxWidget: Checkbox }} onChange: ({ formData }) => { diff --git a/dist/components/subworkflows/Subworkflow.d.ts b/dist/components/subworkflows/Subworkflow.d.ts index d646754..88a0ef8 100644 --- a/dist/components/subworkflows/Subworkflow.d.ts +++ b/dist/components/subworkflows/Subworkflow.d.ts @@ -27,20 +27,36 @@ export type SubworkflowProps = { jobProperties?: WorkflowDesignerProperty[]; activeTabIndex: number; onActiveTabIndexChange: (tabIndex: number) => void; + /** + * Drops this subworkflow's own Compute tab. Set it when the host already + * shows a compute surface of its own — the job designer does, and two places + * called "Compute" on one screen leave the reader guessing which one the job + * will actually run with. + */ + hideComputeSubTab?: boolean; + /** + * Clicking a unit in the flowchart opens its settings in a side drawer, + * instead of the reader leaving for the Settings tab and finding that unit + * among all the others. + * + * Opt-in per host: the tabs stay exactly as they are, so a host with tests or + * documentation against them is unaffected until it flips this. + */ + useUnitInspector?: boolean; }; export declare const TAB_NAVIGATION_CONFIG: { readonly overview: { - readonly itemName: "Overview"; + readonly itemName: "Units"; readonly className: ""; readonly href: "sw-overview"; }; readonly importantSettings: { - readonly itemName: "Important settings"; + readonly itemName: "Settings"; readonly className: ""; readonly href: "sw-important-settings"; }; readonly detailedView: { - readonly itemName: "Detailed view"; + readonly itemName: "Outputs"; readonly className: ""; readonly href: "sw-detailed-view"; }; @@ -50,5 +66,5 @@ export declare const TAB_NAVIGATION_CONFIG: { readonly href: "sw-compute"; }; }; -export declare function Subworkflow({ subworkflow, onUpdate, isStandalone, editable, adjustable, metaProperties, onOutputUpdateRequest, isMethodDataLoading, accountUsers, accountUsersIsLoading, currentUser, clusters, materials, materialsIndex, onMaterialSwitch, profile, publicAccount, createMetaProperty, pseudoUploadReduxDialog, unitTypeReduxDialog, className, jobProperties, activeTabIndex, onActiveTabIndexChange, }: SubworkflowProps): React.JSX.Element; +export declare function Subworkflow({ subworkflow, onUpdate, isStandalone, editable, adjustable, metaProperties, onOutputUpdateRequest, isMethodDataLoading, accountUsers, accountUsersIsLoading, currentUser, clusters, materials, materialsIndex, onMaterialSwitch, profile, publicAccount, createMetaProperty, pseudoUploadReduxDialog, unitTypeReduxDialog, className, jobProperties, activeTabIndex, onActiveTabIndexChange, useUnitInspector, hideComputeSubTab, }: SubworkflowProps): React.JSX.Element; //# sourceMappingURL=Subworkflow.d.ts.map \ No newline at end of file diff --git a/dist/components/subworkflows/Subworkflow.d.ts.map b/dist/components/subworkflows/Subworkflow.d.ts.map index b9b4262..9df9d9e 100644 --- a/dist/components/subworkflows/Subworkflow.d.ts.map +++ b/dist/components/subworkflows/Subworkflow.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"Subworkflow.d.ts","sourceRoot":"","sources":["../../../src/components/subworkflows/Subworkflow.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAqB,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAIvF,OAAO,EAEH,KAAK,eAAe,EACpB,WAAW,IAAI,eAAe,EAEjC,MAAM,cAAc,CAAC;AAOtB,OAAO,KAAyC,MAAM,OAAO,CAAC;AAE9D,OAAO,KAAK,EACR,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,EACxB,wCAAwC,EACxC,2BAA2B,EAC3B,4BAA4B,EAC5B,kCAAkC,EAClC,uBAAuB,EACvB,wBAAwB,EAExB,oBAAoB,EACvB,MAAM,qBAAqB,CAAC;AAU7B,MAAM,MAAM,gBAAgB,GAAG;IAC3B,WAAW,EAAE,eAAe,CAAC;IAC7B,QAAQ,EAAE,CAAC,WAAW,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACnD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,4BAA4B,EAAE,CAAC;IAChD,qBAAqB,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACrD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,QAAQ,CAAC,EAAE,uBAAuB,EAAE,CAAC;IACrC,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAChD,OAAO,EAAE,uBAAuB,CAAC;IACjC,aAAa,EAAE,uBAAuB,CAAC;IACvC,kBAAkB,EAAE,CAChB,QAAQ,EAAE,wCAAwC,KACjD,OAAO,CAAC,kCAAkC,GAAG,SAAS,CAAC,CAAC;IAC7D,uBAAuB,EAAE,2BAA2B,CAAC;IACrD,mBAAmB,EAAE,2BAA2B,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;IAC3C,cAAc,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD,CAAC;AAEF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;CAqBxB,CAAC;AAEX,wBAAgB,WAAW,CAAC,EACxB,WAAW,EACX,QAAQ,EACR,YAAoB,EACpB,QAAe,EACf,UAAkB,EAClB,cAAmB,EACnB,qBAAqB,EACrB,mBAA2B,EAC3B,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,QAAa,EACb,SAAc,EACd,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,aAAa,EACb,kBAAkB,EAClB,uBAAuB,EACvB,mBAAmB,EACnB,SAAS,EACT,aAAa,EACb,cAAc,EACd,sBAAsB,GACzB,EAAE,gBAAgB,qBAoUlB"} \ No newline at end of file +{"version":3,"file":"Subworkflow.d.ts","sourceRoot":"","sources":["../../../src/components/subworkflows/Subworkflow.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAqB,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAIvF,OAAO,EAEH,KAAK,eAAe,EACpB,WAAW,IAAI,eAAe,EAEjC,MAAM,cAAc,CAAC;AAOtB,OAAO,KAAyC,MAAM,OAAO,CAAC;AAE9D,OAAO,KAAK,EACR,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,EACxB,wCAAwC,EACxC,2BAA2B,EAC3B,4BAA4B,EAC5B,kCAAkC,EAClC,uBAAuB,EACvB,wBAAwB,EAExB,oBAAoB,EACvB,MAAM,qBAAqB,CAAC;AAY7B,MAAM,MAAM,gBAAgB,GAAG;IAC3B,WAAW,EAAE,eAAe,CAAC;IAC7B,QAAQ,EAAE,CAAC,WAAW,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACnD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,4BAA4B,EAAE,CAAC;IAChD,qBAAqB,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACrD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,QAAQ,CAAC,EAAE,uBAAuB,EAAE,CAAC;IACrC,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAChD,OAAO,EAAE,uBAAuB,CAAC;IACjC,aAAa,EAAE,uBAAuB,CAAC;IACvC,kBAAkB,EAAE,CAChB,QAAQ,EAAE,wCAAwC,KACjD,OAAO,CAAC,kCAAkC,GAAG,SAAS,CAAC,CAAC;IAC7D,uBAAuB,EAAE,2BAA2B,CAAC;IACrD,mBAAmB,EAAE,2BAA2B,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;IAC3C,cAAc,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;CAqBxB,CAAC;AAMX,wBAAgB,WAAW,CAAC,EACxB,WAAW,EACX,QAAQ,EACR,YAAoB,EACpB,QAAe,EACf,UAAkB,EAClB,cAAmB,EACnB,qBAAqB,EACrB,mBAA2B,EAC3B,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,QAAa,EACb,SAAc,EACd,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,aAAa,EACb,kBAAkB,EAClB,uBAAuB,EACvB,mBAAmB,EACnB,SAAS,EACT,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,gBAAwB,EACxB,iBAAyB,GAC5B,EAAE,gBAAgB,qBAyWlB"} \ No newline at end of file diff --git a/dist/components/subworkflows/Subworkflow.js b/dist/components/subworkflows/Subworkflow.js index af2e26e..ecd6eb3 100644 --- a/dist/components/subworkflows/Subworkflow.js +++ b/dist/components/subworkflows/Subworkflow.js @@ -15,7 +15,9 @@ import Grid from "@mui/material/Grid"; import Stack from "@mui/material/Stack"; import { useCallback, useMemo, useState } from "react"; import { useWorkflowComponents } from "../../WorkflowComponentsContext"; +import { UndoSnackbar } from "../common/UndoSnackbar"; import UnitModal from "../units/UnitModal"; +import UnitInspectorDrawer from "./UnitInspectorDrawer"; import { ImportantSettings } from "./ImportantSettings"; import { SubworkflowExecutionUnitDetailsRow } from "./SubworkflowExecutionUnitDetailsRow"; import { SubworkflowMethodPanel } from "./SubworkflowMethodPanel"; @@ -23,17 +25,17 @@ import WorkflowCompute from "./WorkflowCompute"; const AccordionComponent = Accordion; export const TAB_NAVIGATION_CONFIG = { overview: { - itemName: "Overview", + itemName: "Units", className: "", href: "sw-overview", }, importantSettings: { - itemName: "Important settings", + itemName: "Settings", className: "", href: "sw-important-settings", }, detailedView: { - itemName: "Detailed view", + itemName: "Outputs", className: "", href: "sw-detailed-view", }, @@ -43,10 +45,14 @@ export const TAB_NAVIGATION_CONFIG = { href: "sw-compute", }, }; -export function Subworkflow({ subworkflow, onUpdate, isStandalone = false, editable = true, adjustable = false, metaProperties = [], onOutputUpdateRequest, isMethodDataLoading = false, accountUsers, accountUsersIsLoading, currentUser, clusters = [], materials = [], materialsIndex, onMaterialSwitch, profile, publicAccount, createMetaProperty, pseudoUploadReduxDialog, unitTypeReduxDialog, className, jobProperties, activeTabIndex, onActiveTabIndexChange, }) { - var _a, _b, _c; +const COMPUTE_TAB_NAME = TAB_NAVIGATION_CONFIG.compute.itemName; +/** Compute is the last tab, which is what lets it be dropped without renumbering the rest. */ +const COMPUTE_TAB_INDEX = Object.keys(TAB_NAVIGATION_CONFIG).indexOf("compute"); +export function Subworkflow({ subworkflow, onUpdate, isStandalone = false, editable = true, adjustable = false, metaProperties = [], onOutputUpdateRequest, isMethodDataLoading = false, accountUsers, accountUsersIsLoading, currentUser, clusters = [], materials = [], materialsIndex, onMaterialSwitch, profile, publicAccount, createMetaProperty, pseudoUploadReduxDialog, unitTypeReduxDialog, className, jobProperties, activeTabIndex, onActiveTabIndexChange, useUnitInspector = false, hideComputeSubTab = false, }) { + var _a, _b, _c, _d; const { getDefaultComputeConfig } = useWorkflowComponents(); const [unitIndex, setUnitIndex] = useState(0); + const [removeUndoState, setRemoveUndoState] = useState(null); const applyToSubworkflow = useCallback((fn) => { fn(subworkflow); onUpdate(subworkflow.toJSON()); @@ -98,10 +104,17 @@ export function Subworkflow({ subworkflow, onUpdate, isStandalone = false, edita }); }, [applyToSubworkflow]); const onUnitRemove = useCallback((flowchartId) => { + var _a; + const removedUnit = subworkflow.getUnit(flowchartId); + const snapshot = subworkflow.toJSON(); applyToSubworkflow((sw) => { sw.removeUnit(flowchartId); }); - }, [applyToSubworkflow]); + setRemoveUndoState({ + message: `Removed unit "${(_a = removedUnit === null || removedUnit === void 0 ? void 0 : removedUnit.name) !== null && _a !== void 0 ? _a : flowchartId}"`, + onUndo: () => onUpdate(snapshot), + }); + }, [applyToSubworkflow, subworkflow, onUpdate]); const onUnitClone = useCallback((unit, index) => { const { flowchartId: _omitFlowchartId, next: _omitNext, head: _omitHead, ...config } = unit; applyToSubworkflow((sw) => { @@ -148,29 +161,42 @@ export function Subworkflow({ subworkflow, onUpdate, isStandalone = false, edita unit.togglePostProcessor(safeMakeObject(postProcessor), enabled); }); }, [applyToSubworkflow]); + const [isInspectorOpen, setIsInspectorOpen] = useState(false); const onUnitSelect = useCallback((unit) => { const index = subworkflow.units.findIndex((u) => u.flowchartId === unit.flowchartId); if (index > -1) { setUnitIndex((prev) => (index !== prev ? index : prev)); + if (useUnitInspector) + setIsInspectorOpen(true); } - }, [subworkflow.units]); + }, [subworkflow.units, useUnitInspector]); + // The selected index survives units being added or removed; the drawer must + // not go on showing settings for a unit that is no longer there. + const inspectedUnit = isInspectorOpen && useUnitInspector ? (_a = subworkflow.unitsInstances[unitIndex]) !== null && _a !== void 0 ? _a : null : null; const setTabIndex = useCallback((index) => { onActiveTabIndexChange(index); }, [onActiveTabIndexChange]); const categorizedModelList = new ModelStandata().getAll(); const filteredModels = new ApplicationModelStandata().findByApplicationParameters({ modelList: categorizedModelList, - name: (_a = subworkflow.application) === null || _a === void 0 ? void 0 : _a.name, - version: (_b = subworkflow.application) === null || _b === void 0 ? void 0 : _b.version, - build: (_c = subworkflow.application) === null || _c === void 0 ? void 0 : _c.build, + name: (_b = subworkflow.application) === null || _b === void 0 ? void 0 : _b.name, + version: (_c = subworkflow.application) === null || _c === void 0 ? void 0 : _c.version, + build: (_d = subworkflow.application) === null || _d === void 0 ? void 0 : _d.build, }); - const tabs = useMemo(() => Object.values(TAB_NAVIGATION_CONFIG).map((tab, index) => ({ + const tabs = useMemo(() => Object.values(TAB_NAVIGATION_CONFIG) + .map((tab, index) => ({ ...tab, href: undefined, onClick: (event) => { event.preventDefault(); setTabIndex(index); }, - })), [setTabIndex]); - return (_jsxs(Stack, { "data-tid": "subworkflow", height: "100%", className: className, children: [_jsx(TabsMenu, { tabs: tabs, activeTabIndex: activeTabIndex, sx: { fontSize: 12, height: "100%" } }), _jsxs(TabContext, { value: `${activeTabIndex}`, children: [_jsx(TabPanel, { value: "0", id: TAB_NAVIGATION_CONFIG.overview.href, sx: { height: "100%" }, children: _jsxs(Stack, { spacing: 3, height: "100%", children: [_jsx(AccordionComponent, { header: "Details", id: "subworkflow-accordion", sx: { pt: 0 }, children: _jsxs(Stack, { spacing: 2, children: [_jsx(Properties, { subworkflow: subworkflow, onUpdate: onUpdate, editable: editable || adjustable }), _jsx(ApplicationAve, { application: subworkflow.application, onApplicationUpdate: onApplicationUpdate, editable: editable }), subworkflow.modelInstance.isUnknown ? null : (_jsx(Model, { id: "model", model: subworkflow.modelInstance, models: filteredModels, application: subworkflow.application, onUpdate: onModelUpdate, editable: editable })), _jsx(SubworkflowMethodPanel, { subworkflow: subworkflow, editable: editable, adjustable: adjustable, isMethodDataLoading: isMethodDataLoading, isStandalone: isStandalone, materials: materials, profile: profile, onUpdate: onChildSubworkflowInstanceUpdate, pseudoUploadReduxDialog: pseudoUploadReduxDialog, metaProperties: metaProperties, createMetaProperty: createMetaProperty })] }) }), _jsx(UnitsFlowchartContainer, { units: subworkflow.unitsInstances, onUnitAdd: onUnitAdd, isStandalone: isStandalone, editable: editable, adjustable: adjustable, onUnitClone: onUnitClone, onUnitRemove: onUnitRemove, onUnitUpdate: onUnitUpdate, materials: materials, materialsIndex: materialsIndex, onMaterialSwitch: onMaterialSwitch, subworkflow: subworkflow, onOutputUpdateRequest: onOutputUpdateRequest, publicAccount: publicAccount, unitIndex: unitIndex, onUnitSelect: onUnitSelect, unitTypeReduxDialog: unitTypeReduxDialog, jobProperties: jobProperties, UnitModalComponent: UnitModal })] }) }), _jsx(TabPanel, { value: "1", id: TAB_NAVIGATION_CONFIG.importantSettings.href, "data-tab-name": TAB_NAVIGATION_CONFIG.importantSettings.itemName, children: _jsx(ImportantSettings, { id: TAB_NAVIGATION_CONFIG.importantSettings.href, subworkflow: subworkflow, onContextChanged: onImportantSettingsContextChanged }) }), _jsx(TabPanel, { value: "2", children: _jsx(Grid, { container: true, spacing: 2, children: subworkflow.unitsInstances.map((unit, index) => (_jsx(SubworkflowExecutionUnitDetailsRow, { unit: unit, index: index, editable: editable, onUnitResultsChanged: onUnitResultsChanged, onUnitIsDraftChanged: onUnitIsDraftChanged, onUnitMonitorChanged: onUnitMonitorChanged, onUnitPostProcessorChanged: onUnitPostProcessorChanged }, unit.flowchartId))) }) }), _jsx(TabPanel, { value: "3", children: _jsx(WorkflowCompute, { compute: subworkflow.compute, onUpdate: onComputeUpdate, onToggle: onComputeToggle, showAdvancedOptions: new Application(subworkflow.application).hasAdvancedComputeOptions, accountUsers: accountUsers, accountUsersIsLoading: accountUsersIsLoading, currentUser: currentUser !== null && currentUser !== void 0 ? currentUser : profile.user.entity, clusters: clusters }) })] })] })); + })) + // Compute is the last entry, so dropping it leaves the remaining + // tabs on the indices their panels are keyed to. + .filter((tab) => !(hideComputeSubTab && tab.itemName === COMPUTE_TAB_NAME)), [setTabIndex, hideComputeSubTab]); + // A subworkflow whose Compute tab was open when the host hid it would + // otherwise be left showing an empty panel. + const visibleTabIndex = hideComputeSubTab && activeTabIndex === COMPUTE_TAB_INDEX ? 0 : activeTabIndex; + return (_jsxs(Stack, { "data-tid": "subworkflow", height: "100%", className: className, children: [_jsx(UndoSnackbar, { state: removeUndoState, onClose: () => setRemoveUndoState(null) }), useUnitInspector ? (_jsx(UnitInspectorDrawer, { unit: inspectedUnit, unitIndex: unitIndex, onClose: () => setIsInspectorOpen(false), onContextChanged: onImportantSettingsContextChanged })) : null, _jsx(TabsMenu, { tabs: tabs, activeTabIndex: visibleTabIndex, sx: { fontSize: 12, height: "100%" } }), _jsxs(TabContext, { value: `${visibleTabIndex}`, children: [_jsx(TabPanel, { value: "0", id: TAB_NAVIGATION_CONFIG.overview.href, sx: { height: "100%" }, children: _jsxs(Stack, { spacing: 3, height: "100%", children: [_jsx(UnitsFlowchartContainer, { units: subworkflow.unitsInstances, onUnitAdd: onUnitAdd, isStandalone: isStandalone, editable: editable, adjustable: adjustable, onUnitClone: onUnitClone, onUnitRemove: onUnitRemove, onUnitUpdate: onUnitUpdate, materials: materials, materialsIndex: materialsIndex, onMaterialSwitch: onMaterialSwitch, subworkflow: subworkflow, onOutputUpdateRequest: onOutputUpdateRequest, publicAccount: publicAccount, unitIndex: unitIndex, onUnitSelect: onUnitSelect, unitTypeReduxDialog: unitTypeReduxDialog, jobProperties: jobProperties, UnitModalComponent: UnitModal }), _jsx(AccordionComponent, { header: "Details", id: "subworkflow-accordion", sx: { pt: 0 }, children: _jsxs(Stack, { spacing: 2, children: [_jsx(Properties, { subworkflow: subworkflow, onUpdate: onUpdate, editable: editable || adjustable }), _jsx(ApplicationAve, { application: subworkflow.application, onApplicationUpdate: onApplicationUpdate, editable: editable }), subworkflow.modelInstance.isUnknown ? null : (_jsx(Model, { id: "model", model: subworkflow.modelInstance, models: filteredModels, application: subworkflow.application, onUpdate: onModelUpdate, editable: editable })), _jsx(SubworkflowMethodPanel, { subworkflow: subworkflow, editable: editable, adjustable: adjustable, isMethodDataLoading: isMethodDataLoading, isStandalone: isStandalone, materials: materials, profile: profile, onUpdate: onChildSubworkflowInstanceUpdate, pseudoUploadReduxDialog: pseudoUploadReduxDialog, metaProperties: metaProperties, createMetaProperty: createMetaProperty })] }) })] }) }), _jsx(TabPanel, { value: "1", id: TAB_NAVIGATION_CONFIG.importantSettings.href, "data-tab-name": TAB_NAVIGATION_CONFIG.importantSettings.itemName, children: _jsx(ImportantSettings, { id: TAB_NAVIGATION_CONFIG.importantSettings.href, subworkflow: subworkflow, onContextChanged: onImportantSettingsContextChanged }) }), _jsx(TabPanel, { value: "2", children: _jsx(Grid, { container: true, spacing: 2, children: subworkflow.unitsInstances.map((unit, index) => (_jsx(SubworkflowExecutionUnitDetailsRow, { unit: unit, index: index, editable: editable, onUnitResultsChanged: onUnitResultsChanged, onUnitIsDraftChanged: onUnitIsDraftChanged, onUnitMonitorChanged: onUnitMonitorChanged, onUnitPostProcessorChanged: onUnitPostProcessorChanged }, unit.flowchartId))) }) }), hideComputeSubTab ? null : (_jsx(TabPanel, { value: `${COMPUTE_TAB_INDEX}`, children: _jsx(WorkflowCompute, { compute: subworkflow.compute, onUpdate: onComputeUpdate, onToggle: onComputeToggle, showAdvancedOptions: new Application(subworkflow.application).hasAdvancedComputeOptions, accountUsers: accountUsers, accountUsersIsLoading: accountUsersIsLoading, currentUser: currentUser !== null && currentUser !== void 0 ? currentUser : profile.user.entity, clusters: clusters }) }))] })] })); } diff --git a/dist/components/subworkflows/UnitInspectorDrawer.d.ts b/dist/components/subworkflows/UnitInspectorDrawer.d.ts new file mode 100644 index 0000000..55704cf --- /dev/null +++ b/dist/components/subworkflows/UnitInspectorDrawer.d.ts @@ -0,0 +1,26 @@ +import { type AnySubworkflowUnit } from "@mat3ra/wode"; +import React from "react"; +export interface UnitInspectorDrawerProps { + /** The unit to inspect. `null` closes the drawer. */ + unit: AnySubworkflowUnit | null; + unitIndex: number; + onClose: () => void; + onContextChanged: () => void; + id?: string; +} +/** + * One unit's settings, beside the flowchart that selects it. + * + * Adjusting a unit used to mean leaving the flowchart for the Settings tab, + * finding that unit among all the others, changing it, and coming back to see + * what it did — a bounce between three tabs for one edit, with the diagram that + * gives the change its meaning off screen the whole time. Here the unit stays + * selected in the flowchart while its settings are open next to it. + * + * A plain MUI `Drawer` rather than cove's `ResizableDrawer`: that one is anchored + * to the bottom and resizes on height only, and generalising it to two axes is a + * change to a shared component with its own consumers. The width handle here is + * a few lines and does not put them at risk. + */ +export default function UnitInspectorDrawer({ unit, unitIndex, onClose, onContextChanged, id, }: UnitInspectorDrawerProps): React.JSX.Element; +//# sourceMappingURL=UnitInspectorDrawer.d.ts.map \ No newline at end of file diff --git a/dist/components/subworkflows/UnitInspectorDrawer.d.ts.map b/dist/components/subworkflows/UnitInspectorDrawer.d.ts.map new file mode 100644 index 0000000..3c669b2 --- /dev/null +++ b/dist/components/subworkflows/UnitInspectorDrawer.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"UnitInspectorDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/subworkflows/UnitInspectorDrawer.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,cAAc,CAAC;AAM3E,OAAO,KAAK,MAAM,OAAO,CAAC;AAS1B,MAAM,WAAW,wBAAwB;IACrC,qDAAqD;IACrD,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAC;CACf;AAQD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,EACxC,IAAI,EACJ,SAAS,EACT,OAAO,EACP,gBAAgB,EAChB,EAA4B,GAC/B,EAAE,wBAAwB,qBA2F1B"} \ No newline at end of file diff --git a/dist/components/subworkflows/UnitInspectorDrawer.js b/dist/components/subworkflows/UnitInspectorDrawer.js new file mode 100644 index 0000000..952e051 --- /dev/null +++ b/dist/components/subworkflows/UnitInspectorDrawer.js @@ -0,0 +1,72 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import IconByName from "@mat3ra/cove/dist/mui/components/icon/IconByName"; +import Box from "@mui/material/Box"; +import Drawer from "@mui/material/Drawer"; +import IconButton from "@mui/material/IconButton"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import React from "react"; +import { ImportantSettingsForUnit } from "./ImportantSettings"; +/** Width bounds for the drag handle. Narrower than this and the forms stop fitting. */ +const MIN_WIDTH = 320; +const MAX_WIDTH = 900; +const DEFAULT_WIDTH = 460; +function isExecutionUnit(unit) { + // Schema `type`, not `instanceof`: units may come from a second compiled copy + // of `@mat3ra/wode` (see the note in ImportantSettings). + return unit.type === "execution"; +} +/** + * One unit's settings, beside the flowchart that selects it. + * + * Adjusting a unit used to mean leaving the flowchart for the Settings tab, + * finding that unit among all the others, changing it, and coming back to see + * what it did — a bounce between three tabs for one edit, with the diagram that + * gives the change its meaning off screen the whole time. Here the unit stays + * selected in the flowchart while its settings are open next to it. + * + * A plain MUI `Drawer` rather than cove's `ResizableDrawer`: that one is anchored + * to the bottom and resizes on height only, and generalising it to two axes is a + * change to a shared component with its own consumers. The width handle here is + * a few lines and does not put them at risk. + */ +export default function UnitInspectorDrawer({ unit, unitIndex, onClose, onContextChanged, id = "unit-inspector-drawer", }) { + var _a; + const [width, setWidth] = React.useState(DEFAULT_WIDTH); + const isResizing = React.useRef(false); + React.useEffect(() => { + const resize = (event) => { + if (!isResizing.current) + return; + // The drawer is anchored right, so its width is the distance from the + // pointer to the right edge of the window. + const next = window.innerWidth - event.clientX; + setWidth(Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, next))); + }; + const stop = () => { + isResizing.current = false; + }; + document.addEventListener("mousemove", resize); + document.addEventListener("mouseup", stop); + return () => { + document.removeEventListener("mousemove", resize); + document.removeEventListener("mouseup", stop); + }; + }, []); + return (_jsxs(Drawer, { id: id, anchor: "right", open: Boolean(unit), onClose: onClose, + // Persistent would leave the flowchart squeezed; temporary keeps the + // diagram at full width and lets Escape and a backdrop click close it. + variant: "temporary", PaperProps: { sx: { width, maxWidth: "100vw" } }, children: [_jsx(Box, { role: "separator", "aria-orientation": "vertical", "aria-label": "Resize the inspector", onMouseDown: () => { + isResizing.current = true; + }, sx: { + position: "absolute", + left: 0, + top: 0, + bottom: 0, + width: 6, + cursor: "col-resize", + "&:hover": { bgcolor: "action.hover" }, + } }), _jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, sx: { p: 2, pl: 3, borderBottom: "1px solid", borderColor: "divider" }, children: [_jsxs(Box, { sx: { flexGrow: 1, minWidth: 0 }, children: [_jsx(Typography, { variant: "subtitle2", noWrap: true, children: (_a = unit === null || unit === void 0 ? void 0 : unit.name) !== null && _a !== void 0 ? _a : "Unit" }), _jsxs(Typography, { variant: "caption", color: "text.secondary", children: ["Unit ", unitIndex + 1, (unit === null || unit === void 0 ? void 0 : unit.type) ? ` · ${unit.type}` : ""] })] }), _jsx(IconButton, { size: "small", onClick: onClose, "aria-label": "Close the inspector", children: _jsx(IconByName, { name: "actions.close", fontSize: "small" }) })] }), _jsx(Box, { sx: { overflowY: "auto", px: 3, pb: 3 }, children: unit && isExecutionUnit(unit) ? (_jsx(ImportantSettingsForUnit, { unit: unit, unitIndex: unitIndex, onContextChanged: onContextChanged })) : (_jsx(Typography, { variant: "body2", color: "text.secondary", sx: { pt: 2 }, children: unit + ? `A ${unit.type} unit has no important settings to adjust.` + : "Select a unit in the flowchart." })) })] })); +} diff --git a/dist/components/subworkflows/WorkflowCompute.d.ts.map b/dist/components/subworkflows/WorkflowCompute.d.ts.map index e9ce489..6e775fd 100644 --- a/dist/components/subworkflows/WorkflowCompute.d.ts.map +++ b/dist/components/subworkflows/WorkflowCompute.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"WorkflowCompute.d.ts","sourceRoot":"","sources":["../../../src/components/subworkflows/WorkflowCompute.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EACR,uBAAuB,EACvB,wBAAwB,EACxB,oBAAoB,EACvB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,oBAAoB,GAAG;IAC/B,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACnC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,WAAW,EAAE,wBAAwB,CAAC;IACtC,QAAQ,CAAC,EAAE,uBAAuB,EAAE,CAAC;CACxC,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACpC,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,mBAAmB,EACnB,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,QAAa,GAChB,EAAE,oBAAoB,qBAsCtB"} \ No newline at end of file +{"version":3,"file":"WorkflowCompute.d.ts","sourceRoot":"","sources":["../../../src/components/subworkflows/WorkflowCompute.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EACR,uBAAuB,EACvB,wBAAwB,EACxB,oBAAoB,EACvB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,oBAAoB,GAAG;IAC/B,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACnC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,WAAW,EAAE,wBAAwB,CAAC;IACtC,QAAQ,CAAC,EAAE,uBAAuB,EAAE,CAAC;CACxC,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACpC,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,mBAAmB,EACnB,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,QAAa,GAChB,EAAE,oBAAoB,qBA2CtB"} \ No newline at end of file diff --git a/dist/components/subworkflows/WorkflowCompute.js b/dist/components/subworkflows/WorkflowCompute.js index ca1a5e2..0702c27 100644 --- a/dist/components/subworkflows/WorkflowCompute.js +++ b/dist/components/subworkflows/WorkflowCompute.js @@ -2,10 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /* eslint-disable jsx-a11y/label-has-associated-control */ import { ComputeForm } from "@mat3ra/ive"; import Box from "@mui/material/Box"; -import Checkbox from "@mui/material/Checkbox"; -import FormControlLabel from "@mui/material/FormControlLabel"; +import Stack from "@mui/material/Stack"; +import Switch from "@mui/material/Switch"; +import Typography from "@mui/material/Typography"; export default function WorkflowCompute({ compute, onToggle, onUpdate, showAdvancedOptions, accountUsers, accountUsersIsLoading, currentUser, clusters = [], }) { - return (_jsxs(Box, { children: [_jsx(Box, { children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { "data-tid": "toggle-compute", onChange: (e) => onToggle(e.target.checked), checked: Boolean(compute) }), label: " Run inside a separate job" }) }), Boolean(compute) && (_jsx(ComputeForm, { id: "compute-form-embedded", compute: compute, user: currentUser, onUpdate: onUpdate, clusters: clusters, showHeader: false, showAdvancedOptions: showAdvancedOptions, accountUsers: accountUsers, isAccountUsersLoading: accountUsersIsLoading, gridParams: { + return (_jsxs(Box, { children: [_jsxs(Stack, { direction: "row", spacing: 1.5, alignItems: "flex-start", sx: { mb: 1 }, children: [_jsx(Switch, { "data-tid": "toggle-compute", checked: Boolean(compute), onChange: (e) => onToggle(e.target.checked), inputProps: { "aria-label": "Override compute for this subworkflow" } }), _jsxs(Box, { children: [_jsx(Typography, { variant: "subtitle1", fontWeight: 600, color: "text.primary", children: "Override compute for this subworkflow" }), _jsx(Typography, { variant: "caption", color: "text.secondary", component: "div", children: "Off: jobs use the compute settings chosen at job creation. On: this subworkflow always runs as a separate job with the resources below." })] })] }), Boolean(compute) && (_jsx(ComputeForm, { id: "compute-form-embedded", compute: compute, user: currentUser, onUpdate: onUpdate, clusters: clusters, showHeader: false, showAdvancedOptions: showAdvancedOptions, accountUsers: accountUsers, isAccountUsersLoading: accountUsersIsLoading, gridParams: { left: { xs: 12, }, diff --git a/dist/components/subworkflows/importantSettingsFormUtils.d.ts b/dist/components/subworkflows/importantSettingsFormUtils.d.ts index aff2929..52e36aa 100644 --- a/dist/components/subworkflows/importantSettingsFormUtils.d.ts +++ b/dist/components/subworkflows/importantSettingsFormUtils.d.ts @@ -10,7 +10,9 @@ export type ImportantSettingsFormProvider = T & { }; /** * Shallow-merges default layout into each top-level entry of `uiSchema`, matching - * `JSONSchemaFormMixin#uiSchemaStyled` without mutating the input. + * `JSONSchemaFormMixin#uiSchemaStyled` without mutating the input. The defaults suppress + * per-field labels; fields listed in {@link PROVIDER_FIELD_LABELS} for `providerName` + * re-enable them with an explicit title instead of rendering bare inputs. */ -export declare function mergeUiSchemaWithDefaultFieldStyles(uiSchema: UiSchema): UiSchema; +export declare function mergeUiSchemaWithDefaultFieldStyles(uiSchema: UiSchema, providerName?: string): UiSchema; //# sourceMappingURL=importantSettingsFormUtils.d.ts.map \ No newline at end of file diff --git a/dist/components/subworkflows/importantSettingsFormUtils.d.ts.map b/dist/components/subworkflows/importantSettingsFormUtils.d.ts.map index 5a0f771..ab5cb34 100644 --- a/dist/components/subworkflows/importantSettingsFormUtils.d.ts.map +++ b/dist/components/subworkflows/importantSettingsFormUtils.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"importantSettingsFormUtils.d.ts","sourceRoot":"","sources":["../../../src/components/subworkflows/importantSettingsFormUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;GAGG;AACH,MAAM,MAAM,6BAA6B,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;CACtB,CAAC;AAmBF;;;GAGG;AACH,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAoBhF"} \ No newline at end of file +{"version":3,"file":"importantSettingsFormUtils.d.ts","sourceRoot":"","sources":["../../../src/components/subworkflows/importantSettingsFormUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;GAGG;AACH,MAAM,MAAM,6BAA6B,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;CACtB,CAAC;AA+BF;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAC/C,QAAQ,EAAE,QAAQ,EAClB,YAAY,CAAC,EAAE,MAAM,GACtB,QAAQ,CAiCV"} \ No newline at end of file diff --git a/dist/components/subworkflows/importantSettingsFormUtils.js b/dist/components/subworkflows/importantSettingsFormUtils.js index 910ad05..e26689e 100644 --- a/dist/components/subworkflows/importantSettingsFormUtils.js +++ b/dist/components/subworkflows/importantSettingsFormUtils.js @@ -14,23 +14,49 @@ function defaultFieldStylesForMerge() { const { classNames: _omitClassNames, ...rest } = raw; return rest; } +/** + * Field labels the wode provider uiSchemas do not carry themselves (SOF-8024, quick win 1.2). + * Keyed by provider `name`, then schema field. Units are engine-specific (Ry for Quantum + * ESPRESSO), which the provider's own description already states, so labels stay unitless. + */ +const PROVIDER_FIELD_LABELS = { + cutoffs: { + wavefunction: "Wavefunction cutoff", + density: "Charge density cutoff", + }, +}; /** * Shallow-merges default layout into each top-level entry of `uiSchema`, matching - * `JSONSchemaFormMixin#uiSchemaStyled` without mutating the input. + * `JSONSchemaFormMixin#uiSchemaStyled` without mutating the input. The defaults suppress + * per-field labels; fields listed in {@link PROVIDER_FIELD_LABELS} for `providerName` + * re-enable them with an explicit title instead of rendering bare inputs. */ -export function mergeUiSchemaWithDefaultFieldStyles(uiSchema) { +export function mergeUiSchemaWithDefaultFieldStyles(uiSchema, providerName) { const defaultFieldStyles = defaultFieldStylesForMerge(); + const fieldLabels = (providerName && PROVIDER_FIELD_LABELS[providerName]) || {}; return Object.fromEntries(Object.keys(uiSchema).map((key) => { const value = uiSchema[key]; if (value === false) { return [key, false]; } if (value && typeof value === "object" && !Array.isArray(value)) { + const label = fieldLabels[key]; + const labelOverrides = label + ? { + "ui:title": label, + "ui:options": { + ...defaultFieldStyles["ui:options"], + label: true, + title: true, + }, + } + : {}; return [ key, { ...value, ...defaultFieldStyles, + ...labelOverrides, }, ]; } diff --git a/dist/components/units/UnitModal.js b/dist/components/units/UnitModal.js index 1cccfa9..2833579 100644 --- a/dist/components/units/UnitModal.js +++ b/dist/components/units/UnitModal.js @@ -25,7 +25,7 @@ export default function UnitModal({ id = "", title = "", className, onClose, uni onUpdate(unit.toJSON()); }; const renderHeaderCustom = () => { - return (_jsxs(_Fragment, { children: [_jsx(DialogTitle, { component: "div", children: _jsxs(Grid, { container: true, children: [_jsxs(Grid, { item: true, container: true, justifyContent: "space-between", children: [_jsx(Grid, { item: true, children: _jsx(Typography, { variant: "h5", children: "Unit settings" }) }), _jsx(Grid, { item: true, children: _jsx(IconButton, { id: `${id}-close-button`, onClick: onClose, children: _jsx(IconByName, { name: "actions.close", fontSize: "small" }) }) })] }), _jsx(Grid, { item: true, xs: 12, children: _jsx(EntityNameComponent, { editable: editable, value: unit.name, subtitle: { type: unit.type }, description: unit.flowchartId, onUpdate: (name) => onNameUpdate(name), icon: ENTITY_ICONS.unit, status: getUnitStatusCls(unit.status), descriptionLabel: "flowchartId" }) })] }) }), _jsx(Divider, {})] })); + return (_jsxs(_Fragment, { children: [_jsx(DialogTitle, { component: "div", children: _jsxs(Grid, { container: true, children: [_jsxs(Grid, { item: true, container: true, justifyContent: "space-between", children: [_jsx(Grid, { item: true, children: _jsx(Typography, { variant: "h5", children: `${unit.name} — ${unit.type} unit` }) }), _jsx(Grid, { item: true, children: _jsx(IconButton, { id: `${id}-close-button`, onClick: onClose, children: _jsx(IconByName, { name: "actions.close", fontSize: "small" }) }) })] }), _jsx(Grid, { item: true, xs: 12, children: _jsx(EntityNameComponent, { editable: editable, value: unit.name, subtitle: { type: unit.type }, onUpdate: (name) => onNameUpdate(name), icon: ENTITY_ICONS.unit, status: getUnitStatusCls(unit.status) }) })] }) }), _jsx(Divider, {})] })); }; return (_jsxs(Dialog, { open: true, id: id, onClose: onClose, renderHeaderCustom: renderHeaderCustom, renderFooterCustom: () => null, title: title, maxWidth: "lg", className: className ? `UnitModal ${className}` : "UnitModal", scrollable: true, fullWidth: true, children: [_jsx(UnitModalContent, { unit: unit, units: units, onUpdate: onUpdate, adjustable: adjustable, editable: editable, isStandalone: isStandalone, onOutputUpdateRequest: onOutputUpdateRequest, materials: materials, materialsIndex: materialsIndex, onMaterialSwitch: onMaterialSwitch, jobProperties: jobProperties }), _jsx(MetadataComponent, { tags: lodash.get(unit, "tags", []), editable: editable, isSetPublicVisible: false, onUpdateTags: (tags) => { unit.tags = tags; diff --git a/dist/components/workflows/Workflow.d.ts b/dist/components/workflows/Workflow.d.ts index 98f295d..59d0704 100644 --- a/dist/components/workflows/Workflow.d.ts +++ b/dist/components/workflows/Workflow.d.ts @@ -15,6 +15,8 @@ export type WorkflowProps = { onUpdateTags?: (tags: string[]) => void; extraActions?: DropdownAction[]; onSave?: (omitRedirect: boolean) => void; + /** Unsaved changes exist; surfaces on the Save affordance. */ + isDirty?: boolean; onNameUpdate?: (name: string) => void; iconCls?: string; onUnitAdd?: (unitType: UnitType, prepend?: boolean, unitIndex?: number) => void; @@ -66,7 +68,25 @@ export type WorkflowProps = { isDescriptionEditable: boolean; /** Refined job properties for unit modals in job designer; optional elsewhere. */ jobProperties?: WorkflowDesignerProperty[]; + /** + * Shows unit status on cards and flowchart nodes. Off by default: in the + * designer nothing has run, so every unit reports a meaningless "idle". + * Hosts that render a workflow which is actually executing — the job + * designer, once a job leaves draft — turn it on. + */ + showUnitStatus?: boolean; + /** + * Hides each subworkflow's own Compute tab. Set it when the host renders a + * compute surface of its own, as the job designer does — otherwise the same + * screen offers two things called "Compute" and the reader has to guess + * which one the job will run with. + */ + hideComputeSubTab?: boolean; + /** See {@link SubworkflowProps.useUnitInspector}. */ + useUnitInspector?: boolean; + /** See {@link WorkflowDefaultLayoutProps.useHostTheme}. */ + useHostTheme?: boolean; }; -export declare function Workflow({ workflow, metaProperties, onUpdate, onOutputUpdateRequest, onUpdateTags, extraActions, onSave, onNameUpdate, iconCls, onUnitAdd, onUnitAddSubworkflowFromConfig, onUnitRemove, onUnitUpdate, onSubworkflowUnitUpdate, materials, materialsIndex, jobHasParent, onMaterialSwitch, showHeaderPager, onHeaderPagerUpdate, dialogs, createMetaProperty, accountUsers, accountUsersIsLoading, profile, publicAccount, clusters, templates, isStandalone, isHeaderCompact, editable, adjustable, isLoading, showHeader, isMethodDataLoading, materialsSet, isMap, isSetPublicVisible, showMetadata, showHistory, workflowHistory, onIsMultiMaterialChanged, onRender, renderAtJobLevel, workflowRenderGeneration, isDescriptionEditable, jobProperties, }: WorkflowProps): React.JSX.Element; +export declare function Workflow({ workflow, metaProperties, onUpdate, onOutputUpdateRequest, onUpdateTags, extraActions, onSave, isDirty, onNameUpdate, iconCls, onUnitAdd, onUnitAddSubworkflowFromConfig, onUnitRemove, onUnitUpdate, onSubworkflowUnitUpdate, materials, materialsIndex, jobHasParent, onMaterialSwitch, showHeaderPager, onHeaderPagerUpdate, dialogs, createMetaProperty, accountUsers, accountUsersIsLoading, profile, publicAccount, clusters, templates, isStandalone, isHeaderCompact, editable, adjustable, isLoading, showHeader, isMethodDataLoading, materialsSet, isMap, isSetPublicVisible, showMetadata, showHistory, workflowHistory, onIsMultiMaterialChanged, onRender, renderAtJobLevel, workflowRenderGeneration, isDescriptionEditable, jobProperties, hideComputeSubTab, useUnitInspector, useHostTheme, showUnitStatus, }: WorkflowProps): React.JSX.Element; export {}; //# sourceMappingURL=Workflow.d.ts.map \ No newline at end of file diff --git a/dist/components/workflows/Workflow.d.ts.map b/dist/components/workflows/Workflow.d.ts.map index a698ff5..895d911 100644 --- a/dist/components/workflows/Workflow.d.ts.map +++ b/dist/components/workflows/Workflow.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"Workflow.d.ts","sourceRoot":"","sources":["../../../src/components/workflows/Workflow.tsx"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"Workflow.d.ts","sourceRoot":"","sources":["../../../src/components/workflows/Workflow.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/components/workflows/Workflow.js b/dist/components/workflows/Workflow.js index 462e5db..1169afd 100644 --- a/dist/components/workflows/Workflow.js +++ b/dist/components/workflows/Workflow.js @@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime"; import IconByName from "@mat3ra/cove/dist/mui/components/icon"; import { Workflow as WodeWorkflow } from "@mat3ra/wode"; import { UnitType } from "@mat3ra/wode/dist/js/enums"; -import { getUnitStatusCls, getWorkflowStatusCls } from "@mat3ra/wove"; +import { getUnitStatusCls, getWorkflowStatusCls, WoveDisplayOptionsProvider } from "@mat3ra/wove"; import Box from "@mui/material/Box"; import findIndex from "lodash/findIndex"; import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"; @@ -11,8 +11,10 @@ import { getWorkflowDesignerTabResetKey } from "./workflowDesignerTabState"; const noop = () => undefined; const EMPTY_META_PROPERTIES = []; const noopUnitAddSubworkflowFromConfig = (_config, _prependOrPasteIndex, _unitIndex) => undefined; -export function Workflow({ workflow, metaProperties = EMPTY_META_PROPERTIES, onUpdate = noop, onOutputUpdateRequest, onUpdateTags, extraActions = [], onSave, onNameUpdate, iconCls, onUnitAdd = noop, onUnitAddSubworkflowFromConfig = noopUnitAddSubworkflowFromConfig, onUnitRemove = noop, onUnitUpdate = noop, onSubworkflowUnitUpdate = noop, materials = [], materialsIndex, jobHasParent = false, onMaterialSwitch, showHeaderPager = false, onHeaderPagerUpdate, dialogs, createMetaProperty = async (_property) => undefined, accountUsers, accountUsersIsLoading, profile, publicAccount, clusters = [], templates, isStandalone = false, isHeaderCompact, editable = false, adjustable = false, isLoading = false, showHeader = true, isMethodDataLoading = false, materialsSet, isMap, isSetPublicVisible, showMetadata = true, showHistory = false, workflowHistory = [], onIsMultiMaterialChanged, onRender, renderAtJobLevel = false, workflowRenderGeneration, isDescriptionEditable, jobProperties, }) { +export function Workflow({ workflow, metaProperties = EMPTY_META_PROPERTIES, onUpdate = noop, onOutputUpdateRequest, onUpdateTags, extraActions = [], onSave, isDirty = false, onNameUpdate, iconCls, onUnitAdd = noop, onUnitAddSubworkflowFromConfig = noopUnitAddSubworkflowFromConfig, onUnitRemove = noop, onUnitUpdate = noop, onSubworkflowUnitUpdate = noop, materials = [], materialsIndex, jobHasParent = false, onMaterialSwitch, showHeaderPager = false, onHeaderPagerUpdate, dialogs, createMetaProperty = async (_property) => undefined, accountUsers, accountUsersIsLoading, profile, publicAccount, clusters = [], templates, isStandalone = false, isHeaderCompact, editable = false, adjustable = false, isLoading = false, showHeader = true, isMethodDataLoading = false, materialsSet, isMap, isSetPublicVisible, showMetadata = true, showHistory = false, workflowHistory = [], onIsMultiMaterialChanged, onRender, renderAtJobLevel = false, workflowRenderGeneration, isDescriptionEditable, jobProperties, hideComputeSubTab = false, useUnitInspector = false, useHostTheme = false, showUnitStatus = false, }) { const [unitIndex, setUnitIndex] = useState(0); + // Identifiers are noise for the person reading a workflow; opt-in per session. + const [showDeveloperInfo, setShowDeveloperInfo] = useState(false); const [isRelaxationToggled, setIsRelaxationToggled] = useState(false); const [isMultiMaterialToggled, setIsMultiMaterialToggled] = useState(() => Boolean(workflow.isMultiMaterial)); const [areWorkflowContentExpanded, setAreWorkflowContentExpanded] = useState(false); @@ -169,6 +171,18 @@ export function Workflow({ workflow, metaProperties = EMPTY_META_PROPERTIES, onU showCheckIcon: isMultiMaterialToggled, id: "toggle-multi-material", }, + { + // Identifiers are hidden by default; this is how someone + // debugging gets them back without them being on show for + // everyone else. + isShown: true, + content: "Developer info", + onClick: (_action, _event) => { + setShowDeveloperInfo((isShown) => !isShown); + }, + showCheckIcon: showDeveloperInfo, + id: "toggle-developer-info", + }, ]; }, [ adjustable, @@ -177,6 +191,7 @@ export function Workflow({ workflow, metaProperties = EMPTY_META_PROPERTIES, onU isRelaxationToggled, toggleIsMultiMaterial, toggleRelaxation, + showDeveloperInfo, ]); const getActions = useCallback(() => { return [...getDefaultActions(), ...extraActions]; @@ -194,11 +209,12 @@ export function Workflow({ workflow, metaProperties = EMPTY_META_PROPERTIES, onU return { isShown: Boolean(editable && isStandalone), isLoading, + isDirty, onSave: (omitRedirect) => { onSave === null || onSave === void 0 ? void 0 : onSave(omitRedirect !== null && omitRedirect !== void 0 ? omitRedirect : false); }, }; - }, [editable, isLoading, isStandalone, onSave]); + }, [editable, isLoading, isStandalone, onSave, isDirty]); const getDropdownProps = useCallback(() => { return { isShown: true, @@ -207,5 +223,5 @@ export function Workflow({ workflow, metaProperties = EMPTY_META_PROPERTIES, onU buttonContent: "Select Workflow Actions", }; }, [getActions]); - return (_jsx(Box, { "data-workflow-render-generation": workflowRenderGeneration, children: _jsx(WorkflowDefaultLayout, { entity: workflow, unitIndex: unitIndex, isMap: isMap, materials: materials, materialsIndex: materialsIndex, materialsSet: materialsSet, jobHasParent: jobHasParent, editable: Boolean(editable), adjustable: Boolean(adjustable), isLoading: isLoading, showHeader: showHeader, isHeaderCompact: isHeaderCompact, isStandalone: isStandalone, isMethodDataLoading: isMethodDataLoading, isSetPublicVisible: isSetPublicVisible, showMetadata: showMetadata, showHistory: showHistory, workflowHistory: workflowHistory, iconCls: iconCls, onNameUpdate: onNameUpdate, onUpdateTags: onUpdateTags, onUnitAdd: onUnitAdd, onUnitAddSubworkflowFromConfig: onUnitAddSubworkflowFromConfig, onUnitUpdate: onUnitUpdate, onSubworkflowUnitUpdate: onSubworkflowUnitUpdate, onMapWorkflowUpdate: onMapWorkflowUpdate, onUnitSelect: onUnitSelect, onUpdateUnitIndex: onUpdateUnitIndex, handleUnitRemove: handleUnitRemove, onUnitNameUpdate: onUnitNameUpdate, areWorkflowContentExpanded: areWorkflowContentExpanded, toggleExpandWorkflowContent: toggleExpandWorkflowContent, headerStatusCls: headerStatusCls, getPagerProps: getPagerProps, getSaveBtnProps: getSaveBtnProps, getDropdownProps: getDropdownProps, isDescriptionEditable: isDescriptionEditable, onDescriptionUpdate: onDescriptionUpdate, dialogs: dialogs, metaProperties: metaProperties, onMaterialSwitch: onMaterialSwitch, onOutputUpdateRequest: onOutputUpdateRequest, accountUsers: accountUsers, accountUsersIsLoading: accountUsersIsLoading, profile: profile, publicAccount: publicAccount, clusters: clusters, templates: templates, createMetaProperty: createMetaProperty, jobProperties: jobProperties, subworkflowActiveTabIndexById: subworkflowActiveTabIndexById, onSubworkflowActiveTabIndexChange: onSubworkflowActiveTabIndexChange }) })); + return (_jsx(WoveDisplayOptionsProvider, { showDeveloperInfo: showDeveloperInfo, showStatus: showUnitStatus, children: _jsx(Box, { "data-workflow-render-generation": workflowRenderGeneration, children: _jsx(WorkflowDefaultLayout, { entity: workflow, unitIndex: unitIndex, isMap: isMap, materials: materials, materialsIndex: materialsIndex, materialsSet: materialsSet, jobHasParent: jobHasParent, editable: Boolean(editable), adjustable: Boolean(adjustable), isLoading: isLoading, showHeader: showHeader, isHeaderCompact: isHeaderCompact, isStandalone: isStandalone, isMethodDataLoading: isMethodDataLoading, isSetPublicVisible: isSetPublicVisible, showMetadata: showMetadata, showHistory: showHistory, workflowHistory: workflowHistory, iconCls: iconCls, onNameUpdate: onNameUpdate, onUpdateTags: onUpdateTags, onUnitAdd: onUnitAdd, onUnitAddSubworkflowFromConfig: onUnitAddSubworkflowFromConfig, onUnitUpdate: onUnitUpdate, onSubworkflowUnitUpdate: onSubworkflowUnitUpdate, onMapWorkflowUpdate: onMapWorkflowUpdate, onUnitSelect: onUnitSelect, onUpdateUnitIndex: onUpdateUnitIndex, handleUnitRemove: handleUnitRemove, onUnitNameUpdate: onUnitNameUpdate, areWorkflowContentExpanded: areWorkflowContentExpanded, toggleExpandWorkflowContent: toggleExpandWorkflowContent, headerStatusCls: headerStatusCls, getPagerProps: getPagerProps, getSaveBtnProps: getSaveBtnProps, getDropdownProps: getDropdownProps, isDescriptionEditable: isDescriptionEditable, onDescriptionUpdate: onDescriptionUpdate, dialogs: dialogs, metaProperties: metaProperties, onMaterialSwitch: onMaterialSwitch, onOutputUpdateRequest: onOutputUpdateRequest, accountUsers: accountUsers, accountUsersIsLoading: accountUsersIsLoading, profile: profile, publicAccount: publicAccount, clusters: clusters, templates: templates, createMetaProperty: createMetaProperty, jobProperties: jobProperties, hideComputeSubTab: hideComputeSubTab, useUnitInspector: useUnitInspector, useHostTheme: useHostTheme, subworkflowActiveTabIndexById: subworkflowActiveTabIndexById, onSubworkflowActiveTabIndexChange: onSubworkflowActiveTabIndexChange }) }) })); } diff --git a/dist/components/workflows/WorkflowDefaultLayout.d.ts b/dist/components/workflows/WorkflowDefaultLayout.d.ts index 254af65..39c3eb1 100644 --- a/dist/components/workflows/WorkflowDefaultLayout.d.ts +++ b/dist/components/workflows/WorkflowDefaultLayout.d.ts @@ -85,6 +85,19 @@ export type WorkflowDefaultLayoutProps = { /** Subworkflow inner tabs (Overview, Important settings, …); owned by {@link Workflow} so remounts of {@link Subworkflow} do not reset them. */ subworkflowActiveTabIndexById: Record; onSubworkflowActiveTabIndexChange: (subworkflowId: string, tabIndex: number) => void; + /** See {@link SubworkflowProps.hideComputeSubTab}. */ + hideComputeSubTab?: boolean; + /** See {@link SubworkflowProps.useUnitInspector}. */ + useUnitInspector?: boolean; + /** + * Renders under the host's theme instead of forcing cove's old light one. + * + * The designer has always pinned itself to `oldLightMaterialUITheme`, which + * is why a dark host frames a white canvas: the shell is dark, and this + * subtree is not. Opt-in, because hosts that expect the light designer today + * would otherwise be restyled without asking. + */ + useHostTheme?: boolean; }; export declare function WorkflowDefaultLayout(props: WorkflowDefaultLayoutProps): React.JSX.Element; //# sourceMappingURL=WorkflowDefaultLayout.d.ts.map \ No newline at end of file diff --git a/dist/components/workflows/WorkflowDefaultLayout.d.ts.map b/dist/components/workflows/WorkflowDefaultLayout.d.ts.map index 3ef5ce0..812eb38 100644 --- a/dist/components/workflows/WorkflowDefaultLayout.d.ts.map +++ b/dist/components/workflows/WorkflowDefaultLayout.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"WorkflowDefaultLayout.d.ts","sourceRoot":"","sources":["../../../src/components/workflows/WorkflowDefaultLayout.tsx"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"WorkflowDefaultLayout.d.ts","sourceRoot":"","sources":["../../../src/components/workflows/WorkflowDefaultLayout.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/components/workflows/WorkflowDefaultLayout.js b/dist/components/workflows/WorkflowDefaultLayout.js index 4a44b3e..1faaf9b 100644 --- a/dist/components/workflows/WorkflowDefaultLayout.js +++ b/dist/components/workflows/WorkflowDefaultLayout.js @@ -17,7 +17,7 @@ import { WorkflowValidationAlert } from "./WorkflowValidationAlert"; const MapWorkflowDesigner = React.lazy(() => import("./Map").then((module) => ({ default: module.MapWorkflowDesigner }))); export function WorkflowDefaultLayout(props) { var _a, _b; - const { entity, unitIndex, isMap, materials, materialsIndex, materialsSet, jobHasParent = false, editable, adjustable, isLoading, showHeader, isHeaderCompact, isStandalone, isMethodDataLoading, isSetPublicVisible, showMetadata, showHistory, workflowHistory, iconCls, onNameUpdate, onUpdateTags, onUnitAdd, onUnitAddSubworkflowFromConfig, onUnitUpdate, onSubworkflowUnitUpdate, onMapWorkflowUpdate, onUnitSelect, onUpdateUnitIndex, handleUnitRemove, onUnitNameUpdate, areWorkflowContentExpanded, toggleExpandWorkflowContent, headerStatusCls, getPagerProps, getSaveBtnProps, getDropdownProps, isDescriptionEditable, onDescriptionUpdate, dialogs, metaProperties, onMaterialSwitch, onOutputUpdateRequest, accountUsers, accountUsersIsLoading, profile, publicAccount, clusters, templates, createMetaProperty, jobProperties, subworkflowActiveTabIndexById, onSubworkflowActiveTabIndexChange, } = props; + const { entity, unitIndex, isMap, materials, materialsIndex, materialsSet, jobHasParent = false, editable, adjustable, isLoading, showHeader, isHeaderCompact, isStandalone, isMethodDataLoading, isSetPublicVisible, showMetadata, showHistory, workflowHistory, iconCls, onNameUpdate, onUpdateTags, onUnitAdd, onUnitAddSubworkflowFromConfig, onUnitUpdate, onSubworkflowUnitUpdate, onMapWorkflowUpdate, onUnitSelect, onUpdateUnitIndex, handleUnitRemove, onUnitNameUpdate, areWorkflowContentExpanded, toggleExpandWorkflowContent, headerStatusCls, getPagerProps, getSaveBtnProps, getDropdownProps, isDescriptionEditable, onDescriptionUpdate, dialogs, metaProperties, onMaterialSwitch, onOutputUpdateRequest, accountUsers, accountUsersIsLoading, profile, publicAccount, clusters, templates, createMetaProperty, jobProperties, subworkflowActiveTabIndexById, onSubworkflowActiveTabIndexChange, hideComputeSubTab, useUnitInspector, useHostTheme = false, } = props; const { EntityHeaderComponent, MetadataComponent, HistoryComponent } = useWorkflowComponents(); const unit = entity.unitInstances[unitIndex]; if (!unit) { @@ -35,10 +35,11 @@ export function WorkflowDefaultLayout(props) { const leftColumnGridProps = isMap ? { md: 12, lg: true } : { md: 12, lg: 4 }; const rightColumnGridProps = isMap ? { md: 12, lg: true } : { md: 12, lg: 8 }; const { pseudoUploadReduxDialog, unitTypeReduxDialog } = dialogs; - return (_jsx(ThemeProvider, { theme: oldLightMaterialUITheme, children: _jsxs("div", { className: "workflow-with-name-and-metadata", children: [showHeader && (_jsx(EntityHeaderComponent, { isCompact: isHeaderCompact, icon: ENTITY_ICONS.workflow, name: String((_a = entity.name) !== null && _a !== void 0 ? _a : ""), subtitle: { - applications: entity.usedApplicationNames.join(", "), - }, description: get(entity, "description"), isLoading: isLoading, editable: Boolean(editable), onNameUpdate: onNameUpdate, iconCls: iconCls, id: "workflow-designer-header", pagerProps: getPagerProps(), saveBtnProps: getSaveBtnProps(), dropdownProps: getDropdownProps(), descriptionEditorTitle: "Workflow Description", item: entity, isDescriptionEditable: isDescriptionEditable, onDescriptionUpdate: onDescriptionUpdate })), _jsxs(Grid, { container: true, sx: { backgroundColor: "background.paper" }, children: [_jsx(Grid, { ...leftColumnGridProps, item: true, sx: { - borderRight: "1px solid #cecece", - backgroundColor: "background.default", - }, children: _jsx(Box, { className: "workflow-flowchart-container", sx: { height: "100%", p: 2 }, children: _jsx(WorkflowUnitsFlowchart, { editable: Boolean(editable), onUnitRemove: handleUnitRemove, onSubworkflowUnitUpdate: onSubworkflowUnitUpdate, workflow: entity, activeUnit: unit, onClick: onUnitSelect, isCardContentExpanded: areWorkflowContentExpanded, headerStatusCls: headerStatusCls }) }) }), _jsxs(Grid, { className: "workflow-subworkflow-container", item: true, sx: { display: "flex", flexDirection: "column" }, ...rightColumnGridProps, children: [_jsx(WorkflowValidationAlert, { workflow: entity }), unit.type === UnitType.subworkflow && (_jsxs(_Fragment, { children: [_jsx(SubworkflowHeader, { unit: unit, adjustable: Boolean(adjustable), editable: Boolean(editable), subworkflow: subworkflow, onUnitRemove: handleUnitRemove, headerStatusCls: headerStatusCls, onUnitNameUpdate: onUnitNameUpdate, unitIndex: unitIndex, onUnitAdd: onUnitAdd, onUnitAddSubworkflowFromConfig: onUnitAddSubworkflowFromConfig, onUpdateUnitIndex: onUpdateUnitIndex, onSubworkflowUnitUpdate: onSubworkflowUnitUpdate, areWorkflowContentExpanded: areWorkflowContentExpanded, toggleExpandWorkflowContent: toggleExpandWorkflowContent, workflow: entity, materials: materials, materialsIndex: materialsIndex, materialsSet: materialsSet, jobHasParent: jobHasParent }), subworkflow ? (_jsx(Subworkflow, { className: "card-body", subworkflow: subworkflow, activeTabIndex: (_b = subworkflowActiveTabIndexById[subworkflow.id]) !== null && _b !== void 0 ? _b : 0, onActiveTabIndexChange: (tabIndex) => onSubworkflowActiveTabIndexChange(subworkflow.id, tabIndex), onUpdate: onSubworkflowUnitUpdate, isStandalone: isStandalone, isMethodDataLoading: isMethodDataLoading, editable: Boolean(editable), adjustable: Boolean(adjustable), onMaterialSwitch: onMaterialSwitch, materials: materials, materialsIndex: materialsIndex, metaProperties: metaProperties, onOutputUpdateRequest: onOutputUpdateRequest, accountUsers: accountUsers, accountUsersIsLoading: accountUsersIsLoading, currentUser: profile.user.entity, clusters: clusters, pseudoUploadReduxDialog: pseudoUploadReduxDialog, unitTypeReduxDialog: unitTypeReduxDialog, profile: profile, publicAccount: publicAccount, createMetaProperty: createMetaProperty, jobProperties: jobProperties }, subworkflow.id)) : null] })), unit.type === UnitType.map && (_jsx(React.Suspense, { fallback: null, children: _jsx(MapWorkflowDesigner, { className: "card-body", unit: unit, workflow: mapWorkflow, onUpdate: onUnitUpdate, onWorkflowUpdate: onMapWorkflowUpdate, editable: Boolean(editable), adjustable: Boolean(adjustable), onMaterialSwitch: onMaterialSwitch, materials: materials, materialsIndex: materialsIndex, iconCls: iconCls, onOutputUpdateRequest: onOutputUpdateRequest, parentWorkflow: entity, accountUsers: accountUsers, accountUsersIsLoading: accountUsersIsLoading, currentUser: profile.user.entity, publicAccount: publicAccount, profile: profile, clusters: clusters, dialogs: dialogs, templates: templates, isDescriptionEditable: isDescriptionEditable, metaProperties: metaProperties }) })), unit.type === UnitType.error && (_jsx(Box, { className: "card-body", sx: { p: 2 }, children: _jsx(ErrorUnitContent, { unit: unit }) }))] })] }), _jsx(Divider, {}), showMetadata && (_jsx(MetadataComponent, { tags: get(entity, "tags", []), editable: Boolean(editable), isSetPublicVisible: isSetPublicVisible, onUpdateTags: onUpdateTags, publicAccount: publicAccount.entity })), _jsx(Divider, {}), showHistory && _jsx(HistoryComponent, { items: workflowHistory })] }) })); + const content = (_jsxs("div", { className: "workflow-with-name-and-metadata", children: [showHeader && (_jsx(EntityHeaderComponent, { isCompact: isHeaderCompact, icon: ENTITY_ICONS.workflow, name: String((_a = entity.name) !== null && _a !== void 0 ? _a : ""), subtitle: { + applications: entity.usedApplicationNames.join(", "), + }, description: get(entity, "description"), isLoading: isLoading, editable: Boolean(editable), onNameUpdate: onNameUpdate, iconCls: iconCls, id: "workflow-designer-header", pagerProps: getPagerProps(), saveBtnProps: getSaveBtnProps(), dropdownProps: getDropdownProps(), descriptionEditorTitle: "Workflow Description", item: entity, isDescriptionEditable: isDescriptionEditable, onDescriptionUpdate: onDescriptionUpdate })), _jsxs(Grid, { container: true, sx: { backgroundColor: "background.paper" }, children: [_jsx(Grid, { ...leftColumnGridProps, item: true, sx: { + borderRight: "1px solid #cecece", + backgroundColor: "background.default", + }, children: _jsx(Box, { className: "workflow-flowchart-container", sx: { height: "100%", p: 2 }, children: _jsx(WorkflowUnitsFlowchart, { editable: Boolean(editable), onUnitRemove: handleUnitRemove, onSubworkflowUnitUpdate: onSubworkflowUnitUpdate, workflow: entity, activeUnit: unit, onClick: onUnitSelect, isCardContentExpanded: areWorkflowContentExpanded, headerStatusCls: headerStatusCls }) }) }), _jsxs(Grid, { className: "workflow-subworkflow-container", item: true, sx: { display: "flex", flexDirection: "column" }, ...rightColumnGridProps, children: [_jsx(WorkflowValidationAlert, { workflow: entity }), unit.type === UnitType.subworkflow && (_jsxs(_Fragment, { children: [_jsx(SubworkflowHeader, { unit: unit, adjustable: Boolean(adjustable), editable: Boolean(editable), subworkflow: subworkflow, onUnitRemove: handleUnitRemove, headerStatusCls: headerStatusCls, onUnitNameUpdate: onUnitNameUpdate, unitIndex: unitIndex, onUnitAdd: onUnitAdd, onUnitAddSubworkflowFromConfig: onUnitAddSubworkflowFromConfig, onUpdateUnitIndex: onUpdateUnitIndex, onSubworkflowUnitUpdate: onSubworkflowUnitUpdate, areWorkflowContentExpanded: areWorkflowContentExpanded, toggleExpandWorkflowContent: toggleExpandWorkflowContent, workflow: entity, materials: materials, materialsIndex: materialsIndex, materialsSet: materialsSet, jobHasParent: jobHasParent }), subworkflow ? (_jsx(Subworkflow, { className: "card-body", subworkflow: subworkflow, activeTabIndex: (_b = subworkflowActiveTabIndexById[subworkflow.id]) !== null && _b !== void 0 ? _b : 0, hideComputeSubTab: hideComputeSubTab, useUnitInspector: useUnitInspector, onActiveTabIndexChange: (tabIndex) => onSubworkflowActiveTabIndexChange(subworkflow.id, tabIndex), onUpdate: onSubworkflowUnitUpdate, isStandalone: isStandalone, isMethodDataLoading: isMethodDataLoading, editable: Boolean(editable), adjustable: Boolean(adjustable), onMaterialSwitch: onMaterialSwitch, materials: materials, materialsIndex: materialsIndex, metaProperties: metaProperties, onOutputUpdateRequest: onOutputUpdateRequest, accountUsers: accountUsers, accountUsersIsLoading: accountUsersIsLoading, currentUser: profile.user.entity, clusters: clusters, pseudoUploadReduxDialog: pseudoUploadReduxDialog, unitTypeReduxDialog: unitTypeReduxDialog, profile: profile, publicAccount: publicAccount, createMetaProperty: createMetaProperty, jobProperties: jobProperties }, subworkflow.id)) : null] })), unit.type === UnitType.map && (_jsx(React.Suspense, { fallback: null, children: _jsx(MapWorkflowDesigner, { className: "card-body", unit: unit, workflow: mapWorkflow, onUpdate: onUnitUpdate, onWorkflowUpdate: onMapWorkflowUpdate, editable: Boolean(editable), adjustable: Boolean(adjustable), onMaterialSwitch: onMaterialSwitch, materials: materials, materialsIndex: materialsIndex, iconCls: iconCls, onOutputUpdateRequest: onOutputUpdateRequest, parentWorkflow: entity, accountUsers: accountUsers, accountUsersIsLoading: accountUsersIsLoading, currentUser: profile.user.entity, publicAccount: publicAccount, profile: profile, clusters: clusters, dialogs: dialogs, templates: templates, isDescriptionEditable: isDescriptionEditable, metaProperties: metaProperties }) })), unit.type === UnitType.error && (_jsx(Box, { className: "card-body", sx: { p: 2 }, children: _jsx(ErrorUnitContent, { unit: unit }) }))] })] }), _jsx(Divider, {}), showMetadata && (_jsx(MetadataComponent, { tags: get(entity, "tags", []), editable: Boolean(editable), isSetPublicVisible: isSetPublicVisible, onUpdateTags: onUpdateTags, publicAccount: publicAccount.entity })), _jsx(Divider, {}), showHistory && _jsx(HistoryComponent, { items: workflowHistory })] })); + return useHostTheme ? (content) : (_jsx(ThemeProvider, { theme: oldLightMaterialUITheme, children: content })); } diff --git a/dist/standalone/index.js b/dist/standalone/index.js index 22e6954..4d27998 100644 --- a/dist/standalone/index.js +++ b/dist/standalone/index.js @@ -107,6 +107,9 @@ function App() { return siIndex >= 0 ? siIndex : 0; }); const selectedMaterial = allMaterials[materialIndex]; + const [isDirty, setIsDirty] = useState(false); + // Phase 3.3 surface, opt-in: the demo is where it gets reviewed before a host flips it on. + const [useUnitInspector, setUseUnitInspector] = useState(true); // Re-key the designer when either selection changes so it re-mounts cleanly const designerKey = `${workflowIndex}-${materialIndex}`; const handleSave = useCallback(async () => { @@ -128,13 +131,19 @@ function App() { WebkitTextFillColor: "transparent", mr: 2, flexShrink: 0, - }, children: "Workflow Designer" }), _jsxs(FormControl, { size: "small", sx: { minWidth: 300 }, children: [_jsx(InputLabel, { id: "workflow-select-label", children: "Workflow" }), _jsx(Select, { labelId: "workflow-select-label", id: "workflow-select", value: workflowIndex, label: "Workflow", onChange: (e) => setWorkflowIndex(Number(e.target.value)), children: allWorkflowJsons.map((wf, i) => { + }, children: "Workflow Designer" }), _jsxs(FormControl, { size: "small", sx: { minWidth: 300 }, children: [_jsx(InputLabel, { id: "workflow-select-label", children: "Workflow" }), _jsx(Select, { labelId: "workflow-select-label", id: "workflow-select", value: workflowIndex, label: "Workflow", onChange: (e) => { + setWorkflowIndex(Number(e.target.value)); + setIsDirty(false); + }, children: allWorkflowJsons.map((wf, i) => { var _a; return (_jsx(MenuItem, { value: i, children: (_a = wf === null || wf === void 0 ? void 0 : wf.name) !== null && _a !== void 0 ? _a : `Workflow ${i + 1}` }, i)); - }) })] }), appName && (_jsx(Chip, { label: appName, size: "small", variant: "outlined", color: "primary" })), _jsx(Divider, { orientation: "vertical", flexItem: true }), _jsxs(FormControl, { size: "small", sx: { minWidth: 260 }, children: [_jsx(InputLabel, { id: "material-select-label", children: "Material" }), _jsx(Select, { labelId: "material-select-label", id: "material-select", value: materialIndex, label: "Material", onChange: (e) => setMaterialIndex(Number(e.target.value)), children: allMaterials.map((mat, i) => { + }) })] }), appName && (_jsx(Chip, { label: appName, size: "small", variant: "outlined", color: "primary" })), _jsx(Divider, { orientation: "vertical", flexItem: true }), _jsxs(FormControl, { size: "small", sx: { minWidth: 260 }, children: [_jsx(InputLabel, { id: "material-select-label", children: "Material" }), _jsx(Select, { labelId: "material-select-label", id: "material-select", value: materialIndex, label: "Material", onChange: (e) => { + setMaterialIndex(Number(e.target.value)); + setIsDirty(false); + }, children: allMaterials.map((mat, i) => { var _a, _b; return (_jsx(MenuItem, { value: i, children: (_b = (_a = mat === null || mat === void 0 ? void 0 : mat.name) !== null && _a !== void 0 ? _a : mat === null || mat === void 0 ? void 0 : mat.formula) !== null && _b !== void 0 ? _b : `Material ${i + 1}` }, i)); - }) })] }), _jsx(Chip, { label: `${allWorkflowJsons.length} workflows · ${allMaterials.length} materials`, size: "small", variant: "outlined", color: "secondary", sx: { ml: "auto" } })] }) }), _jsx(Box, { sx: { height: "calc(100vh - 72px)", overflow: "auto" }, children: _jsx(WorkflowDesignerContainer, { initialWorkflow: wodeWorkflow, defaultMaterial: selectedMaterial, editable: true, showHistory: false, workflowHistory: { list: [], loading: false }, isStandalone: true, adjustable: true, showHeader: true, showMetadata: false, accountUsers: [], accountUsersIsLoading: false, profile: { + }) })] }), isDirty && (_jsx(Chip, { label: "\u25CF Unsaved changes", size: "small", variant: "outlined", color: "warning", "data-tid": "dirty-indicator" })), _jsx(Chip, { label: useUnitInspector ? "Unit inspector: on" : "Unit inspector: off", size: "small", variant: useUnitInspector ? "filled" : "outlined", color: "primary", onClick: () => setUseUnitInspector((on) => !on), "data-tid": "unit-inspector-toggle" }), _jsx(Chip, { label: `${allWorkflowJsons.length} workflows · ${allMaterials.length} materials`, size: "small", variant: "outlined", color: "secondary", sx: { ml: "auto" } })] }) }), _jsx(Box, { sx: { height: "calc(100vh - 72px)", overflow: "auto" }, children: _jsx(WorkflowDesignerContainer, { useUnitInspector: useUnitInspector, useHostTheme: true, initialWorkflow: wodeWorkflow, defaultMaterial: selectedMaterial, editable: true, showHistory: false, workflowHistory: { list: [], loading: false }, isStandalone: true, adjustable: true, showHeader: true, showMetadata: false, accountUsers: [], accountUsersIsLoading: false, profile: { user: { entity: { id: "1" } }, account: { entity: { id: "1" } }, personalAccount: { entity: { id: "1" } }, @@ -147,7 +156,7 @@ function App() { nodes: 1, queue: "D", timeLimit: "01:00:00", - }), generateEntityId: () => crypto.randomUUID(), openDocumentationDialog: undefined }, designerKey) })] })); + }), generateEntityId: () => crypto.randomUUID(), openDocumentationDialog: undefined, onDirtyChange: setIsDirty }, `${designerKey}-${useUnitInspector}`) })] })); } // --------------------------------------------------------------------------- // Mount diff --git a/package.json b/package.json index 541fae8..3b52693 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "scripts": { "build": "tsc && npm run copy-css", "copy-css": "mkdir -p dist/stylesheets && cp src/stylesheets/* dist/stylesheets/ 2>/dev/null || true", + "prepare": "husky install", "transpile": "tsc", "test": "node --import tsx --test tests/*.tests.ts", "lint": "eslint src tests && prettier --check src tests", @@ -52,21 +53,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@mat3ra/cove": "2026.7.18-4", - "@mat3ra/wave.js": "2026.7.18-1", + "@exabyte-io/eslint-config": "^2025.1.15-0", "@mat3ra/ade": "2026.5.29-0", + "@mat3ra/ave": "2026.7.21-0", "@mat3ra/code": "2026.5.27-0", + "@mat3ra/cove": "2026.7.18-4", "@mat3ra/esse": "2026.7.14-0", + "@mat3ra/ive": "2026.7.18-0", "@mat3ra/mode": "2026.6.3-0", + "@mat3ra/move": "2026.7.18-1", + "@mat3ra/prove": "2026.7.18-0", "@mat3ra/standata": "2026.7.13-0", "@mat3ra/tsconfig": "^2024.6.3-0", + "@mat3ra/utils": "2026.7.9-0", + "@mat3ra/wave.js": "2026.7.18-1", "@mat3ra/wode": "2026.7.9-0", - "@mat3ra/ave": "2026.7.21-0", - "@mat3ra/ive": "2026.7.18-0", - "@mat3ra/move": "2026.7.18-1", - "@mat3ra/prove": "2026.7.18-0", "@mat3ra/wove": "2026.7.21-0", - "@mat3ra/utils": "2026.7.9-0", "@mui/icons-material": "^5.11.9", "@mui/lab": "^5.0.0-alpha.120", "@mui/material": "^5.11.9", @@ -77,21 +79,9 @@ "@types/node": "^20.11.30", "@types/react": "^17.0.2", "@types/react-dom": "^17.0.2", - "@vitejs/plugin-react": "^4.3.4", - "lodash": "^4.17.4", - "mathjs": "^5.10.3", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "reactflow": "^11.7.2", - "tsx": "^4.22.4", - "typescript": "^5.6.6", - "underscore": "^1.8.3", - "underscore.string": "^3.3.4", - "vite": "^6.0.7", - "vite-plugin-node-polyfills": "^0.25.0", - "@exabyte-io/eslint-config": "^2025.1.15-0", "@typescript-eslint/eslint-plugin": "^5.9.1", "@typescript-eslint/parser": "^5.9.1", + "@vitejs/plugin-react": "^4.3.4", "babel-eslint": "^10.1.0", "eslint": "^7.32.0", "eslint-config-airbnb": "^19.0.2", @@ -107,6 +97,18 @@ "eslint-plugin-react": "^7.30.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-simple-import-sort": "^7.0.0", - "prettier": "2.5.1" + "husky": "^7.0.4", + "lodash": "^4.17.4", + "mathjs": "^5.10.3", + "prettier": "2.5.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "reactflow": "^11.7.2", + "tsx": "^4.22.4", + "typescript": "^5.6.6", + "underscore": "^1.8.3", + "underscore.string": "^3.3.4", + "vite": "^6.0.7", + "vite-plugin-node-polyfills": "^0.25.0" } } diff --git a/src/WorkflowDesignerContainer.tsx b/src/WorkflowDesignerContainer.tsx index 517f864..14b506c 100644 --- a/src/WorkflowDesignerContainer.tsx +++ b/src/WorkflowDesignerContainer.tsx @@ -69,6 +69,10 @@ type WorkflowDesignerContainerBaseProps = { openDocumentationDialog?: (searchText: string) => void; /** Fires when unsaved-changes state flips; lets the shell mark Save / guard navigation. */ onDirtyChange?: (isDirty: boolean) => void; + /** See {@link SubworkflowProps.useUnitInspector}. */ + useUnitInspector?: boolean; + /** See {@link WorkflowDefaultLayoutProps.useHostTheme}. */ + useHostTheme?: boolean; }; export type WorkflowDesignerContainerProps = WorkflowDesignerContainerBaseProps; @@ -121,6 +125,8 @@ export default function WorkflowDesignerContainer(containerProps: WorkflowDesign getDefaultComputeConfig, generateEntityId, onDirtyChange, + useUnitInspector, + useHostTheme, } = containerProps; const workflowComponents: WorkflowComponents = useMemo( @@ -394,6 +400,8 @@ export default function WorkflowDesignerContainer(containerProps: WorkflowDesign setRemoveUndoState(null)} /> void; } -function ImportantSettingsForUnit({ +export function ImportantSettingsForUnit({ unit, unitIndex, onContextChanged, diff --git a/src/components/subworkflows/Subworkflow.tsx b/src/components/subworkflows/Subworkflow.tsx index d303f89..cf03ae3 100644 --- a/src/components/subworkflows/Subworkflow.tsx +++ b/src/components/subworkflows/Subworkflow.tsx @@ -38,6 +38,7 @@ import type { import { useWorkflowComponents } from "../../WorkflowComponentsContext"; import { UndoSnackbar, type UndoSnackbarState } from "../common/UndoSnackbar"; import UnitModal from "../units/UnitModal"; +import UnitInspectorDrawer from "./UnitInspectorDrawer"; import { ImportantSettings } from "./ImportantSettings"; import { SubworkflowExecutionUnitDetailsRow } from "./SubworkflowExecutionUnitDetailsRow"; import { SubworkflowMethodPanel } from "./SubworkflowMethodPanel"; @@ -72,6 +73,22 @@ export type SubworkflowProps = { jobProperties?: WorkflowDesignerProperty[]; activeTabIndex: number; onActiveTabIndexChange: (tabIndex: number) => void; + /** + * Drops this subworkflow's own Compute tab. Set it when the host already + * shows a compute surface of its own — the job designer does, and two places + * called "Compute" on one screen leave the reader guessing which one the job + * will actually run with. + */ + hideComputeSubTab?: boolean; + /** + * Clicking a unit in the flowchart opens its settings in a side drawer, + * instead of the reader leaving for the Settings tab and finding that unit + * among all the others. + * + * Opt-in per host: the tabs stay exactly as they are, so a host with tests or + * documentation against them is unaffected until it flips this. + */ + useUnitInspector?: boolean; }; export const TAB_NAVIGATION_CONFIG = { @@ -97,6 +114,10 @@ export const TAB_NAVIGATION_CONFIG = { }, } as const; +const COMPUTE_TAB_NAME = TAB_NAVIGATION_CONFIG.compute.itemName; +/** Compute is the last tab, which is what lets it be dropped without renumbering the rest. */ +const COMPUTE_TAB_INDEX = Object.keys(TAB_NAVIGATION_CONFIG).indexOf("compute"); + export function Subworkflow({ subworkflow, onUpdate, @@ -122,6 +143,8 @@ export function Subworkflow({ jobProperties, activeTabIndex, onActiveTabIndexChange, + useUnitInspector = false, + hideComputeSubTab = false, }: SubworkflowProps) { const { getDefaultComputeConfig } = useWorkflowComponents(); const [unitIndex, setUnitIndex] = useState(0); @@ -293,16 +316,24 @@ export function Subworkflow({ [applyToSubworkflow], ); + const [isInspectorOpen, setIsInspectorOpen] = useState(false); + const onUnitSelect = useCallback( (unit: { flowchartId: string }) => { const index = subworkflow.units.findIndex((u) => u.flowchartId === unit.flowchartId); if (index > -1) { setUnitIndex((prev) => (index !== prev ? index : prev)); + if (useUnitInspector) setIsInspectorOpen(true); } }, - [subworkflow.units], + [subworkflow.units, useUnitInspector], ); + // The selected index survives units being added or removed; the drawer must + // not go on showing settings for a unit that is no longer there. + const inspectedUnit = + isInspectorOpen && useUnitInspector ? subworkflow.unitsInstances[unitIndex] ?? null : null; + const setTabIndex = useCallback( (index: number) => { onActiveTabIndexChange(index); @@ -320,26 +351,43 @@ export function Subworkflow({ const tabs: WorkflowDesignerTabItem[] = useMemo( () => - Object.values(TAB_NAVIGATION_CONFIG).map((tab, index) => ({ - ...tab, - href: undefined, - onClick: (event) => { - event.preventDefault(); - setTabIndex(index); - }, - })), - [setTabIndex], + Object.values(TAB_NAVIGATION_CONFIG) + .map((tab, index) => ({ + ...tab, + href: undefined, + onClick: (event: React.MouseEvent) => { + event.preventDefault(); + setTabIndex(index); + }, + })) + // Compute is the last entry, so dropping it leaves the remaining + // tabs on the indices their panels are keyed to. + .filter((tab) => !(hideComputeSubTab && tab.itemName === COMPUTE_TAB_NAME)), + [setTabIndex, hideComputeSubTab], ); + // A subworkflow whose Compute tab was open when the host hid it would + // otherwise be left showing an empty panel. + const visibleTabIndex = + hideComputeSubTab && activeTabIndex === COMPUTE_TAB_INDEX ? 0 : activeTabIndex; + return ( setRemoveUndoState(null)} /> + {useUnitInspector ? ( + setIsInspectorOpen(false)} + onContextChanged={onImportantSettingsContextChanged} + /> + ) : null} - + - - - + {hideComputeSubTab ? null : ( + + + + )} ); diff --git a/src/components/subworkflows/UnitInspectorDrawer.tsx b/src/components/subworkflows/UnitInspectorDrawer.tsx new file mode 100644 index 0000000..b19a1b9 --- /dev/null +++ b/src/components/subworkflows/UnitInspectorDrawer.tsx @@ -0,0 +1,143 @@ +import IconByName from "@mat3ra/cove/dist/mui/components/icon/IconByName"; +import { type AnySubworkflowUnit, type ExecutionUnit } from "@mat3ra/wode"; +import Box from "@mui/material/Box"; +import Drawer from "@mui/material/Drawer"; +import IconButton from "@mui/material/IconButton"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import React from "react"; + +import { ImportantSettingsForUnit } from "./ImportantSettings"; + +/** Width bounds for the drag handle. Narrower than this and the forms stop fitting. */ +const MIN_WIDTH = 320; +const MAX_WIDTH = 900; +const DEFAULT_WIDTH = 460; + +export interface UnitInspectorDrawerProps { + /** The unit to inspect. `null` closes the drawer. */ + unit: AnySubworkflowUnit | null; + unitIndex: number; + onClose: () => void; + onContextChanged: () => void; + id?: string; +} + +function isExecutionUnit(unit: AnySubworkflowUnit): unit is ExecutionUnit { + // Schema `type`, not `instanceof`: units may come from a second compiled copy + // of `@mat3ra/wode` (see the note in ImportantSettings). + return unit.type === "execution"; +} + +/** + * One unit's settings, beside the flowchart that selects it. + * + * Adjusting a unit used to mean leaving the flowchart for the Settings tab, + * finding that unit among all the others, changing it, and coming back to see + * what it did — a bounce between three tabs for one edit, with the diagram that + * gives the change its meaning off screen the whole time. Here the unit stays + * selected in the flowchart while its settings are open next to it. + * + * A plain MUI `Drawer` rather than cove's `ResizableDrawer`: that one is anchored + * to the bottom and resizes on height only, and generalising it to two axes is a + * change to a shared component with its own consumers. The width handle here is + * a few lines and does not put them at risk. + */ +export default function UnitInspectorDrawer({ + unit, + unitIndex, + onClose, + onContextChanged, + id = "unit-inspector-drawer", +}: UnitInspectorDrawerProps) { + const [width, setWidth] = React.useState(DEFAULT_WIDTH); + const isResizing = React.useRef(false); + + React.useEffect(() => { + const resize = (event: MouseEvent) => { + if (!isResizing.current) return; + // The drawer is anchored right, so its width is the distance from the + // pointer to the right edge of the window. + const next = window.innerWidth - event.clientX; + setWidth(Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, next))); + }; + const stop = () => { + isResizing.current = false; + }; + + document.addEventListener("mousemove", resize); + document.addEventListener("mouseup", stop); + + return () => { + document.removeEventListener("mousemove", resize); + document.removeEventListener("mouseup", stop); + }; + }, []); + + return ( + + { + isResizing.current = true; + }} + sx={{ + position: "absolute", + left: 0, + top: 0, + bottom: 0, + width: 6, + cursor: "col-resize", + "&:hover": { bgcolor: "action.hover" }, + }} + /> + + + + + {unit?.name ?? "Unit"} + + + Unit {unitIndex + 1} + {unit?.type ? ` · ${unit.type}` : ""} + + + + + + + + + {unit && isExecutionUnit(unit) ? ( + + ) : ( + + {unit + ? `A ${unit.type} unit has no important settings to adjust.` + : "Select a unit in the flowchart."} + + )} + + + ); +} diff --git a/src/components/workflows/Workflow.tsx b/src/components/workflows/Workflow.tsx index c643b84..e6a5b08 100644 --- a/src/components/workflows/Workflow.tsx +++ b/src/components/workflows/Workflow.tsx @@ -5,7 +5,7 @@ import type { Template } from "@mat3ra/ade"; import { type MaterialsSet, type OrderedMaterial, Workflow as WodeWorkflow } from "@mat3ra/wode"; import { UnitType } from "@mat3ra/wode/dist/js/enums"; import type { AnyWorkflowUnit } from "@mat3ra/wode/dist/js/units/factory"; -import { getUnitStatusCls, getWorkflowStatusCls } from "@mat3ra/wove"; +import { getUnitStatusCls, getWorkflowStatusCls, WoveDisplayOptionsProvider } from "@mat3ra/wove"; import Box from "@mui/material/Box"; import findIndex from "lodash/findIndex"; import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"; @@ -95,6 +95,24 @@ export type WorkflowProps = { isDescriptionEditable: boolean; /** Refined job properties for unit modals in job designer; optional elsewhere. */ jobProperties?: WorkflowDesignerProperty[]; + /** + * Shows unit status on cards and flowchart nodes. Off by default: in the + * designer nothing has run, so every unit reports a meaningless "idle". + * Hosts that render a workflow which is actually executing — the job + * designer, once a job leaves draft — turn it on. + */ + showUnitStatus?: boolean; + /** + * Hides each subworkflow's own Compute tab. Set it when the host renders a + * compute surface of its own, as the job designer does — otherwise the same + * screen offers two things called "Compute" and the reader has to guess + * which one the job will run with. + */ + hideComputeSubTab?: boolean; + /** See {@link SubworkflowProps.useUnitInspector}. */ + useUnitInspector?: boolean; + /** See {@link WorkflowDefaultLayoutProps.useHostTheme}. */ + useHostTheme?: boolean; }; const noop = (): undefined => undefined; @@ -156,8 +174,14 @@ export function Workflow({ workflowRenderGeneration, isDescriptionEditable, jobProperties, + hideComputeSubTab = false, + useUnitInspector = false, + useHostTheme = false, + showUnitStatus = false, }: WorkflowProps) { const [unitIndex, setUnitIndex] = useState(0); + // Identifiers are noise for the person reading a workflow; opt-in per session. + const [showDeveloperInfo, setShowDeveloperInfo] = useState(false); const [isRelaxationToggled, setIsRelaxationToggled] = useState(false); const [isMultiMaterialToggled, setIsMultiMaterialToggled] = useState(() => Boolean(workflow.isMultiMaterial), @@ -369,6 +393,18 @@ export function Workflow({ showCheckIcon: isMultiMaterialToggled, id: "toggle-multi-material", }, + { + // Identifiers are hidden by default; this is how someone + // debugging gets them back without them being on show for + // everyone else. + isShown: true, + content: "Developer info", + onClick: (_action, _event) => { + setShowDeveloperInfo((isShown) => !isShown); + }, + showCheckIcon: showDeveloperInfo, + id: "toggle-developer-info", + }, ]; }, [ adjustable, @@ -377,6 +413,7 @@ export function Workflow({ isRelaxationToggled, toggleIsMultiMaterial, toggleRelaxation, + showDeveloperInfo, ]); const getActions = useCallback(() => { @@ -414,61 +451,69 @@ export function Workflow({ }, [getActions]); return ( - - - + + + + + ); } diff --git a/src/components/workflows/WorkflowDefaultLayout.tsx b/src/components/workflows/WorkflowDefaultLayout.tsx index a97d166..5ce886a 100644 --- a/src/components/workflows/WorkflowDefaultLayout.tsx +++ b/src/components/workflows/WorkflowDefaultLayout.tsx @@ -125,6 +125,19 @@ export type WorkflowDefaultLayoutProps = { /** Subworkflow inner tabs (Overview, Important settings, …); owned by {@link Workflow} so remounts of {@link Subworkflow} do not reset them. */ subworkflowActiveTabIndexById: Record; onSubworkflowActiveTabIndexChange: (subworkflowId: string, tabIndex: number) => void; + /** See {@link SubworkflowProps.hideComputeSubTab}. */ + hideComputeSubTab?: boolean; + /** See {@link SubworkflowProps.useUnitInspector}. */ + useUnitInspector?: boolean; + /** + * Renders under the host's theme instead of forcing cove's old light one. + * + * The designer has always pinned itself to `oldLightMaterialUITheme`, which + * is why a dark host frames a white canvas: the shell is dark, and this + * subtree is not. Opt-in, because hosts that expect the light designer today + * would otherwise be restyled without asking. + */ + useHostTheme?: boolean; }; export function WorkflowDefaultLayout(props: WorkflowDefaultLayoutProps) { @@ -181,6 +194,9 @@ export function WorkflowDefaultLayout(props: WorkflowDefaultLayoutProps) { jobProperties, subworkflowActiveTabIndexById, onSubworkflowActiveTabIndexChange, + hideComputeSubTab, + useUnitInspector, + useHostTheme = false, } = props; const { EntityHeaderComponent, MetadataComponent, HistoryComponent } = useWorkflowComponents(); @@ -208,178 +224,181 @@ export function WorkflowDefaultLayout(props: WorkflowDefaultLayoutProps) { const { pseudoUploadReduxDialog, unitTypeReduxDialog } = dialogs; - return ( - -
- {showHeader && ( - - )} + const content = ( +
+ {showHeader && ( + + )} - - - - + + + + + + + + {unit.type === UnitType.subworkflow && ( + <> + - - - - - {unit.type === UnitType.subworkflow && ( - <> - - {/* + {/* key={subworkflow.id} remounts when the user picks another flowchart branch. Inner tab index is held on {@link Workflow} (not Subworkflow) so job.render() remounts do not reset Important settings, while leaving the job Workflow tab unmounts Workflow and returns to Overview on the next visit. */} - {subworkflow ? ( - - onSubworkflowActiveTabIndexChange( - subworkflow.id, - tabIndex, - ) - } - onUpdate={onSubworkflowUnitUpdate} - isStandalone={isStandalone} - isMethodDataLoading={isMethodDataLoading} - editable={Boolean(editable)} - adjustable={Boolean(adjustable)} - onMaterialSwitch={onMaterialSwitch} - materials={materials} - materialsIndex={materialsIndex} - metaProperties={metaProperties} - onOutputUpdateRequest={onOutputUpdateRequest} - accountUsers={accountUsers} - accountUsersIsLoading={accountUsersIsLoading} - currentUser={profile.user.entity} - clusters={clusters} - pseudoUploadReduxDialog={pseudoUploadReduxDialog} - unitTypeReduxDialog={unitTypeReduxDialog} - profile={profile} - publicAccount={publicAccount} - createMetaProperty={createMetaProperty} - jobProperties={jobProperties} - /> - ) : null} - - )} - {unit.type === UnitType.map && ( - - + onSubworkflowActiveTabIndexChange(subworkflow.id, tabIndex) + } + onUpdate={onSubworkflowUnitUpdate} + isStandalone={isStandalone} + isMethodDataLoading={isMethodDataLoading} editable={Boolean(editable)} adjustable={Boolean(adjustable)} onMaterialSwitch={onMaterialSwitch} materials={materials} materialsIndex={materialsIndex} - iconCls={iconCls} + metaProperties={metaProperties} onOutputUpdateRequest={onOutputUpdateRequest} - parentWorkflow={entity} accountUsers={accountUsers} accountUsersIsLoading={accountUsersIsLoading} currentUser={profile.user.entity} - publicAccount={publicAccount} - profile={profile} clusters={clusters} - dialogs={dialogs} - templates={templates} - isDescriptionEditable={isDescriptionEditable} - metaProperties={metaProperties} + pseudoUploadReduxDialog={pseudoUploadReduxDialog} + unitTypeReduxDialog={unitTypeReduxDialog} + profile={profile} + publicAccount={publicAccount} + createMetaProperty={createMetaProperty} + jobProperties={jobProperties} /> - - )} - {unit.type === UnitType.error && ( - - - - )} - + ) : null} + + )} + {unit.type === UnitType.map && ( + + + + )} + {unit.type === UnitType.error && ( + + + + )} - - {showMetadata && ( - - )} - - {showHistory && } -
- + + + {showMetadata && ( + + )} + + {showHistory && } +
+ ); + + return useHostTheme ? ( + content + ) : ( + {content} ); } diff --git a/src/standalone/index.tsx b/src/standalone/index.tsx index f34505d..f3f864c 100644 --- a/src/standalone/index.tsx +++ b/src/standalone/index.tsx @@ -136,6 +136,8 @@ function App() { ); const selectedMaterial = allMaterials[materialIndex]; const [isDirty, setIsDirty] = useState(false); + // Phase 3.3 surface, opt-in: the demo is where it gets reviewed before a host flips it on. + const [useUnitInspector, setUseUnitInspector] = useState(true); // Re-key the designer when either selection changes so it re-mounts cleanly const designerKey = `${workflowIndex}-${materialIndex}`; @@ -238,6 +240,14 @@ function App() { data-tid="dirty-indicator" /> )} + setUseUnitInspector((on) => !on)} + data-tid="unit-inspector-toggle" + />