Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -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/
6 changes: 6 additions & 0 deletions dist/WorkflowDesignerContainer.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ type WorkflowDesignerContainerBaseProps = {
getDefaultComputeConfig: (cluster?: unknown) => Record<string, unknown>;
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;
Expand Down
2 changes: 1 addition & 1 deletion dist/WorkflowDesignerContainer.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 52 additions & 4 deletions dist/WorkflowDesignerContainer.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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 */
});
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 })] }));
}
25 changes: 25 additions & 0 deletions dist/components/common/BrillouinZone.d.ts
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions dist/components/common/BrillouinZone.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading