feat(ui): migrate from lodash.isEqual to objectEquals

This commit is contained in:
psychedelicious
2025-06-25 18:36:17 +10:00
parent 7aefa8f36b
commit b5acc204a8
19 changed files with 76 additions and 50 deletions

View File

@@ -1,13 +1,13 @@
import { objectEquals } from '@observ33r/object-equals';
import { createDraftSafeSelectorCreator, createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
import { isEqual } from 'lodash-es';
/**
* A memoized selector creator that uses LRU cache and lodash's isEqual for equality check.
* A memoized selector creator that uses LRU cache and @observ33r/object-equals's objectEquals for equality check.
*/
export const createMemoizedSelector = createSelectorCreator({
memoize: lruMemoize,
memoizeOptions: {
resultEqualityCheck: isEqual,
resultEqualityCheck: objectEquals,
},
argsMemoize: lruMemoize,
});

View File

@@ -1,8 +1,8 @@
import { objectEquals } from '@observ33r/object-equals';
import { createAction } from '@reduxjs/toolkit';
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { $baseUrl } from 'app/store/nanostores/baseUrl';
import { isEqual } from 'lodash-es';
import { atom } from 'nanostores';
import { api } from 'services/api';
import { modelsApi } from 'services/api/endpoints/models';
@@ -64,7 +64,7 @@ export const addSocketConnectedEventListener = (startAppListening: AppStartListe
const nextQueueStatusData = await queueStatusRequest.unwrap();
// If the queue hasn't changed, we don't need to do anything.
if (isEqual(prevQueueStatusData?.queue, nextQueueStatusData.queue)) {
if (objectEquals(prevQueueStatusData?.queue, nextQueueStatusData.queue)) {
return;
}

View File

@@ -1,3 +1,4 @@
import { objectEquals } from '@observ33r/object-equals';
import { Mutex } from 'async-mutex';
import { deepClone } from 'common/util/deepClone';
import { withResultAsync } from 'common/util/result';
@@ -12,7 +13,6 @@ import { getKonvaNodeDebugAttrs, loadImage } from 'features/controlLayers/konva/
import type { CanvasImageState } from 'features/controlLayers/store/types';
import { t } from 'i18next';
import Konva from 'konva';
import { isEqual } from 'lodash-es';
import type { Logger } from 'roarr';
import { getImageDTOSafe } from 'services/api/endpoints/images';
@@ -198,7 +198,7 @@ export class CanvasObjectImage extends CanvasModuleBase {
const { image } = state;
const { width, height } = image;
if (force || (!isEqual(this.state, state) && !this.isLoading)) {
if (force || (!objectEquals(this.state, state) && !this.isLoading)) {
const release = await this.mutex.acquire();
try {

View File

@@ -1,3 +1,4 @@
import { objectEquals } from '@observ33r/object-equals';
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSelector, createSlice } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
@@ -6,7 +7,7 @@ import { getPrefixedId } from 'features/controlLayers/konva/util';
import { canvasMetadataRecalled } from 'features/controlLayers/store/canvasSlice';
import type { FLUXReduxImageInfluence, RefImagesState } from 'features/controlLayers/store/types';
import { zModelIdentifierField } from 'features/nodes/types/common';
import { clamp, isEqual } from 'lodash-es';
import { clamp } from 'lodash-es';
import type { ApiModelConfig, FLUXReduxModelConfig, ImageDTO, IPAdapterModelConfig } from 'services/api/types';
import { assert } from 'tsafe';
import type { PartialDeep } from 'type-fest';
@@ -102,7 +103,7 @@ export const refImagesSlice = createSlice({
return;
}
if (isEqual(oldModel, entity.config.model)) {
if (objectEquals(oldModel, entity.config.model)) {
// Nothing changed, so we don't need to do anything
return;
}

View File

@@ -1,7 +1,8 @@
import { objectEquals } from '@observ33r/object-equals';
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { isEqual, uniq } from 'lodash-es';
import { uniq } from 'lodash-es';
import type { BoardRecordOrderBy } from 'services/api/types';
import type { BoardId, ComparisonMode, GalleryState, GalleryView, OrderDir } from './types';
@@ -53,7 +54,7 @@ export const gallerySlice = createSlice({
}
// If the selected image is different from the current selection, clear the selection and select the new image
if (!isEqual(state.selection[0], selectedImageName)) {
if (state.selection[0] !== selectedImageName) {
state.selection = [selectedImageName];
return;
}
@@ -74,7 +75,7 @@ export const gallerySlice = createSlice({
}
// If the new selection is different, update the selection
if (!isEqual(newSelection, state.selection)) {
if (!objectEquals(newSelection, state.selection)) {
state.selection = newSelection;
return;
}

View File

@@ -1,10 +1,10 @@
import { objectEquals } from '@observ33r/object-equals';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useInputFieldTemplateOrThrow } from 'features/nodes/hooks/useInputFieldTemplateOrThrow';
import { fieldValueReset } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { isEqual } from 'lodash-es';
import { useCallback, useMemo } from 'react';
export const useInputFieldDefaultValue = (nodeId: string, fieldName: string) => {
@@ -19,7 +19,7 @@ export const useInputFieldDefaultValue = (nodeId: string, fieldName: string) =>
return;
}
const value = node.data.inputs[fieldName]?.value;
return !isEqual(value, fieldTemplate.default);
return !objectEquals(value, fieldTemplate.default);
}),
[fieldName, fieldTemplate.default, nodeId]
);

View File

@@ -1,9 +1,9 @@
import { objectEquals } from '@observ33r/object-equals';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { fieldValueReset } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { isEqual } from 'lodash-es';
import { useCallback, useMemo } from 'react';
const uniqueNonexistentValue = Symbol('uniqueNonexistentValue');
@@ -32,7 +32,7 @@ export const useInputFieldInitialFormValue = (elementId: string, nodeId: string,
return;
}
const value = node.data.inputs[fieldName]?.value;
return !isEqual(value, initialValue);
return !objectEquals(value, initialValue);
}),
[fieldName, initialValue, nodeId]
);

View File

@@ -1,3 +1,4 @@
import { objectEquals } from '@observ33r/object-equals';
import type { EdgeChange, NodeChange } from '@xyflow/react';
import { logger } from 'app/logging/logger';
import { getStore } from 'app/store/nanostores/store';
@@ -16,7 +17,7 @@ import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupied
import { validateConnection } from 'features/nodes/store/util/validateConnection';
import type { AnyEdge, AnyNode } from 'features/nodes/types/invocation';
import { t } from 'i18next';
import { isEqual, uniqWith } from 'lodash-es';
import { uniqWith } from 'lodash-es';
import { v4 as uuidv4 } from 'uuid';
const log = logger('workflows');
@@ -44,7 +45,7 @@ const _pasteSelection = (withEdgesToCopiedNodes?: boolean) => {
if (withEdgesToCopiedNodes) {
const edgesToCopiedNodes = deepClone($edgesToCopiedNodes.get());
copiedEdges = uniqWith([...copiedEdges, ...edgesToCopiedNodes], isEqual);
copiedEdges = uniqWith([...copiedEdges, ...edgesToCopiedNodes], objectEquals);
}
// Calculate an offset to reposition nodes to surround the cursor position, maintaining relative positioning

View File

@@ -1,5 +1,6 @@
import { objectEquals } from '@observ33r/object-equals';
import type { FieldType } from 'features/nodes/types/field';
import { isEqual, omit } from 'lodash-es';
import { omit } from 'lodash-es';
/**
* Checks if two types are equal. If the field types have original types, those are also compared. Any match is
@@ -13,16 +14,16 @@ export const areTypesEqual = (firstType: FieldType, secondType: FieldType) => {
const _secondType = 'originalType' in secondType ? omit(secondType, 'originalType') : secondType;
const _originalFirstType = 'originalType' in firstType ? firstType.originalType : null;
const _originalSecondType = 'originalType' in secondType ? secondType.originalType : null;
if (isEqual(_firstType, _secondType)) {
if (objectEquals(_firstType, _secondType)) {
return true;
}
if (_originalSecondType && isEqual(_firstType, _originalSecondType)) {
if (_originalSecondType && objectEquals(_firstType, _originalSecondType)) {
return true;
}
if (_originalFirstType && isEqual(_originalFirstType, _secondType)) {
if (_originalFirstType && objectEquals(_originalFirstType, _secondType)) {
return true;
}
if (_originalFirstType && _originalSecondType && isEqual(_originalFirstType, _originalSecondType)) {
if (_originalFirstType && _originalSecondType && objectEquals(_originalFirstType, _originalSecondType)) {
return true;
}
return false;

View File

@@ -1,6 +1,7 @@
import { objectEquals } from '@observ33r/object-equals';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { type ModelIdentifierField, zModelIdentifierField } from 'features/nodes/types/common';
import { forEach, groupBy, isEqual, unset, values } from 'lodash-es';
import { forEach, groupBy, unset, values } from 'lodash-es';
import type {
AnyInvocation,
AnyInvocationIncMetadata,
@@ -169,7 +170,7 @@ export class Graph {
source: { node_id: fromNode.id, field: fromField },
destination: { node_id: toNode.id, field: toField },
};
const edgeAlreadyExists = this._graph.edges.some((e) => isEqual(e, edge));
const edgeAlreadyExists = this._graph.edges.some((e) => objectEquals(e, edge));
assert(!edgeAlreadyExists, `Edge ${Graph.edgeToString(edge)} already exists`);
this._graph.edges.push(edge);
return edge;
@@ -182,7 +183,7 @@ export class Graph {
* @raises `AssertionError` if an edge with the same source and destination already exists.
*/
addEdgeFromObj(edge: Edge): Edge {
const edgeAlreadyExists = this._graph.edges.some((e) => isEqual(e, edge));
const edgeAlreadyExists = this._graph.edges.some((e) => objectEquals(e, edge));
assert(!edgeAlreadyExists, `Edge ${Graph.edgeToString(edge)} already exists`);
this._graph.edges.push(edge);
return edge;
@@ -275,11 +276,11 @@ export class Graph {
}
/**
* INTERNAL: Delete _all_ matching edges from the graph. Uses _.isEqual for comparison.
* INTERNAL: Delete _all_ matching edges from the graph. Uses _.objectEquals for comparison.
* @param edge The edge to delete
*/
private _deleteEdge(edge: Edge): void {
this._graph.edges = this._graph.edges.filter((e) => !isEqual(e, edge));
this._graph.edges = this._graph.edges.filter((e) => !objectEquals(e, edge));
}
/**
@@ -317,7 +318,7 @@ export class Graph {
this.getNode(edge.source.node_id);
this.getNode(edge.destination.node_id);
assert(
!this._graph.edges.filter((e) => e !== edge).find((e) => isEqual(e, edge)),
!this._graph.edges.filter((e) => e !== edge).find((e) => objectEquals(e, edge)),
`Duplicate edge: ${Graph.edgeToString(edge)}`
);
}

View File

@@ -1,3 +1,4 @@
import { objectEquals } from '@observ33r/object-equals';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
@@ -6,7 +7,6 @@ import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
import type { Dimensions } from 'features/controlLayers/store/types';
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
import { isEqual } from 'lodash-es';
import type { Invocation } from 'services/api/types';
type AddFLUXFillArg = {
@@ -52,7 +52,7 @@ export const addFLUXFill = async ({
const fluxFill = g.addNode({ type: 'flux_fill', id: getPrefixedId('flux_fill') });
const needsScaleBeforeProcessing = !isEqual(scaledSize, originalSize);
const needsScaleBeforeProcessing = !objectEquals(scaledSize, originalSize);
if (needsScaleBeforeProcessing) {
// Scale before processing requires some resizing

View File

@@ -1,3 +1,4 @@
import { objectEquals } from '@observ33r/object-equals';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { CanvasState, Dimensions } from 'features/controlLayers/store/types';
@@ -8,7 +9,6 @@ import type {
MainModelLoaderNodes,
VaeSourceNodes,
} from 'features/nodes/util/graph/types';
import { isEqual } from 'lodash-es';
import type { Invocation } from 'services/api/types';
type AddImageToImageArg = {
@@ -45,7 +45,7 @@ export const addImageToImage = async ({
silent: true,
});
if (!isEqual(scaledSize, originalSize)) {
if (!objectEquals(scaledSize, originalSize)) {
// Resize the initial image to the scaled size, denoise, then resize back to the original size
const resizeImageToScaledSize = g.addNode({
type: 'img_resize',

View File

@@ -1,3 +1,4 @@
import { objectEquals } from '@observ33r/object-equals';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
@@ -13,7 +14,6 @@ import type {
MainModelLoaderNodes,
VaeSourceNodes,
} from 'features/nodes/util/graph/types';
import { isEqual } from 'lodash-es';
import type { ImageDTO, Invocation } from 'services/api/types';
type AddInpaintArg = {
@@ -93,7 +93,7 @@ export const addInpaint = async ({
}
);
const needsScaleBeforeProcessing = !isEqual(scaledSize, originalSize);
const needsScaleBeforeProcessing = !objectEquals(scaledSize, originalSize);
if (needsScaleBeforeProcessing) {
// Scale before processing requires some resizing

View File

@@ -1,3 +1,4 @@
import { objectEquals } from '@observ33r/object-equals';
import type { RootState } from 'app/store/store';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getPrefixedId } from 'features/controlLayers/konva/util';
@@ -14,7 +15,6 @@ import type {
MainModelLoaderNodes,
VaeSourceNodes,
} from 'features/nodes/util/graph/types';
import { isEqual } from 'lodash-es';
import type { ImageDTO, Invocation } from 'services/api/types';
type AddOutpaintArg = {
@@ -98,7 +98,7 @@ export const addOutpaint = async ({
const infill = getInfill(g, params);
const needsScaleBeforeProcessing = !isEqual(scaledSize, originalSize);
const needsScaleBeforeProcessing = !objectEquals(scaledSize, originalSize);
if (needsScaleBeforeProcessing) {
// Scale before processing requires some resizing

View File

@@ -1,8 +1,8 @@
import { objectEquals } from '@observ33r/object-equals';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { Dimensions } from 'features/controlLayers/store/types';
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
import type { LatentToImageNodes } from 'features/nodes/util/graph/types';
import { isEqual } from 'lodash-es';
import type { Invocation } from 'services/api/types';
type AddTextToImageArg = {
@@ -18,7 +18,7 @@ export const addTextToImage = ({
originalSize,
scaledSize,
}: AddTextToImageArg): Invocation<'img_resize' | 'l2i' | 'flux_vae_decode' | 'sd3_l2i' | 'cogview4_l2i'> => {
if (!isEqual(scaledSize, originalSize)) {
if (!objectEquals(scaledSize, originalSize)) {
// We need to resize the output image back to the original size
const resizeImageToOriginalSize = g.addNode({
id: getPrefixedId('resize_image_to_original_size'),

View File

@@ -1,3 +1,4 @@
import { objectEquals } from '@observ33r/object-equals';
import { logger } from 'app/logging/logger';
import { deepClone } from 'common/util/deepClone';
import { parseify } from 'common/util/serialize';
@@ -17,7 +18,7 @@ import {
isInvocationSchemaObject,
} from 'features/nodes/types/openapi';
import { t } from 'i18next';
import { isEqual, reduce } from 'lodash-es';
import { reduce } from 'lodash-es';
import type { OpenAPIV3_1 } from 'openapi-types';
import { serializeError } from 'serialize-error';
import type { JsonObject } from 'type-fest';
@@ -153,7 +154,7 @@ export const parseSchema = (
return inputsAccumulator;
}
if (isStatefulFieldType(fieldType) && originalFieldType && !isEqual(originalFieldType, fieldType)) {
if (isStatefulFieldType(fieldType) && originalFieldType && !objectEquals(originalFieldType, fieldType)) {
fieldType.originalType = deepClone(originalFieldType);
}
@@ -225,7 +226,7 @@ export const parseSchema = (
return outputsAccumulator;
}
if (isStatefulFieldType(fieldType) && originalFieldType && !isEqual(originalFieldType, fieldType)) {
if (isStatefulFieldType(fieldType) && originalFieldType && !objectEquals(originalFieldType, fieldType)) {
fieldType.originalType = deepClone(originalFieldType);
}