feat(ui): more workflow loading standardization

There is now a single entrypoint for loading a workflow - `useLoadWorkflowWithDialog`.

The hook:
Handles loading workflows from various sources. If there are unsaved changes, the user will be prompted to confirm before loading the workflow.

It returns  a function that:
Loads a workflow from various sources. If there are unsaved changes, the user will be prompted to confirm before loading the workflow. The workflow will be loaded immediately if there are no unsaved changes. On success, error or completion, the corresponding callback will be called.

WHEW
This commit is contained in:
psychedelicious
2025-03-12 19:35:11 +10:00
parent 729428084c
commit 83bfbdcad4
15 changed files with 270 additions and 123 deletions

View File

@@ -4,6 +4,12 @@ import { useValidateAndLoadWorkflow } from 'features/workflowLibrary/hooks/useVa
import { workflowLoadedFromFile } from 'features/workflowLibrary/store/actions';
import { useCallback } from 'react';
/**
* Loads a workflow from a file.
*
* You probably should instead use `useLoadWorkflowWithDialog`, which opens a dialog to prevent loss of unsaved changes
* and handles the loading process.
*/
export const useLoadWorkflowFromFile = () => {
const dispatch = useAppDispatch();
const validatedAndLoadWorkflow = useValidateAndLoadWorkflow();
@@ -13,30 +19,37 @@ export const useLoadWorkflowFromFile = () => {
options: {
onSuccess?: (workflow: WorkflowV3) => void;
onError?: () => void;
onCompleted?: () => void;
} = {}
) => {
const reader = new FileReader();
reader.onload = async () => {
const rawJSON = reader.result;
const { onSuccess, onError } = options;
try {
const unvalidatedWorkflow = JSON.parse(rawJSON as string);
const validatedWorkflow = await validatedAndLoadWorkflow(unvalidatedWorkflow);
return new Promise<WorkflowV3 | void>((resolve, reject) => {
const reader = new FileReader();
reader.onload = async () => {
const rawJSON = reader.result;
const { onSuccess, onError, onCompleted } = options;
try {
const unvalidatedWorkflow = JSON.parse(rawJSON as string);
const validatedWorkflow = await validatedAndLoadWorkflow(unvalidatedWorkflow);
if (!validatedWorkflow) {
reader.abort();
if (!validatedWorkflow) {
reader.abort();
onError?.();
return;
}
dispatch(workflowLoadedFromFile());
onSuccess?.(validatedWorkflow);
resolve(validatedWorkflow);
} catch {
// This is catching the error from the parsing the JSON file
onError?.();
return;
reject();
} finally {
onCompleted?.();
}
dispatch(workflowLoadedFromFile());
onSuccess?.(validatedWorkflow);
} catch {
// This is catching the error from the parsing the JSON file
onError?.();
}
};
};
reader.readAsText(file);
reader.readAsText(file);
});
},
[validatedAndLoadWorkflow, dispatch]
);

View File

@@ -8,19 +8,26 @@ import { useLazyGetImageWorkflowQuery } from 'services/api/endpoints/images';
import type { NonNullableGraph } from 'services/api/types';
import { assert } from 'tsafe';
/**
* Loads a workflow from an image.
*
* You probably should instead use `useLoadWorkflowWithDialog`, which opens a dialog to prevent loss of unsaved changes
* and handles the loading process.
*/
export const useLoadWorkflowFromImage = () => {
const { t } = useTranslation();
const [getWorkflowAndGraphFromImage, result] = useLazyGetImageWorkflowQuery();
const [getWorkflowAndGraphFromImage] = useLazyGetImageWorkflowQuery();
const validateAndLoadWorkflow = useValidateAndLoadWorkflow();
const getAndLoadEmbeddedWorkflow = useCallback(
const loadWorkflowFromImage = useCallback(
async (
imageName: string,
options: {
onSuccess?: (workflow: WorkflowV3) => void;
onError?: () => void;
onCompleted?: () => void;
} = {}
) => {
const { onSuccess, onError } = options;
const { onSuccess, onError, onCompleted } = options;
try {
const { workflow, graph } = await getWorkflowAndGraphFromImage(imageName).unwrap();
@@ -52,11 +59,12 @@ export const useLoadWorkflowFromImage = () => {
status: 'error',
});
onError?.();
return;
} finally {
onCompleted?.();
}
},
[getWorkflowAndGraphFromImage, validateAndLoadWorkflow, t]
);
return [getAndLoadEmbeddedWorkflow, result] as const;
return loadWorkflowFromImage;
};

View File

@@ -5,6 +5,12 @@ import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useLazyGetWorkflowQuery, useUpdateOpenedAtMutation, workflowsApi } from 'services/api/endpoints/workflows';
/**
* Loads a workflow from the library.
*
* You probably should instead use `useLoadWorkflowWithDialog`, which opens a dialog to prevent loss of unsaved changes
* and handles the loading process.
*/
export const useLoadWorkflowFromLibrary = () => {
const toast = useToast();
const { t } = useTranslation();
@@ -17,9 +23,10 @@ export const useLoadWorkflowFromLibrary = () => {
options: {
onSuccess?: (workflow: WorkflowV3) => void;
onError?: () => void;
onCompleted?: () => void;
} = {}
) => {
const { onSuccess, onError } = options;
const { onSuccess, onError, onCompleted } = options;
try {
const res = await getWorkflow(workflowId).unwrap();
@@ -39,6 +46,8 @@ export const useLoadWorkflowFromLibrary = () => {
status: 'error',
});
onError?.();
} finally {
onCompleted?.();
}
},
[getWorkflow, validateAndLoadWorkflow, updateOpenedAt, toast, t]

View File

@@ -0,0 +1,39 @@
import type { WorkflowV3 } from 'features/nodes/types/workflow';
import { useValidateAndLoadWorkflow } from 'features/workflowLibrary/hooks/useValidateAndLoadWorkflow';
import { useCallback } from 'react';
/**
* Loads a workflow from an object.
*
* You probably should instead use `useLoadWorkflowWithDialog`, which opens a dialog to prevent loss of unsaved changes
* and handles the loading process.
*/
export const useLoadWorkflowFromObject = () => {
const validateAndLoadWorkflow = useValidateAndLoadWorkflow();
const loadWorkflowFromObject = useCallback(
async (
unvalidatedWorkflow: unknown,
options: {
onSuccess?: (workflow: WorkflowV3) => void;
onError?: () => void;
onCompleted?: () => void;
} = {}
) => {
const { onSuccess, onError, onCompleted } = options;
try {
const validatedWorkflow = await validateAndLoadWorkflow(unvalidatedWorkflow);
if (!validatedWorkflow) {
onError?.();
return;
}
onSuccess?.(validatedWorkflow);
} finally {
onCompleted?.();
}
},
[validateAndLoadWorkflow]
);
return loadWorkflowFromObject;
};

View File

@@ -17,6 +17,21 @@ import { fromZodError } from 'zod-validation-error';
const log = logger('workflows');
/**
* This hook manages the lower-level workflow validation and loading process.
*
* You probably should instead use `useLoadWorkflowWithDialog`, which opens a dialog to prevent loss of unsaved changes
* and handles the loading process.
*
* Internally, `useLoadWorkflowWithDialog` uses these hooks...
*
* - `useLoadWorkflowFromFile`
* - `useLoadWorkflowFromImage`
* - `useLoadWorkflowFromLibrary`
* - `useLoadWorkflowFromObject`
*
* ...each of which internally uses hook.
*/
export const useValidateAndLoadWorkflow = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();