diff --git a/app/packages/core/src/components/MainSpace/MainSpace.tsx b/app/packages/core/src/components/MainSpace/MainSpace.tsx index 6461bbb6916..1c66eccb3f0 100644 --- a/app/packages/core/src/components/MainSpace/MainSpace.tsx +++ b/app/packages/core/src/components/MainSpace/MainSpace.tsx @@ -1,5 +1,11 @@ -import { SpacesRoot, usePanelsState, useSpaces } from "@fiftyone/spaces"; -import { constants, useSessionSpaces } from "@fiftyone/state"; +import { + SpaceNodeJSON, + SpacesRoot, + SpaceTree, + usePanelsState, + useSpaces, +} from "@fiftyone/spaces"; +import { constants, useSessionSpaces, useUnboundState } from "@fiftyone/state"; import { isEqual, size } from "lodash"; import React, { useEffect, useRef } from "react"; @@ -13,29 +19,45 @@ function MainSpace() { sessionSpaces ); const [panelsState, setPanelsState] = usePanelsState(); - const oldSpaces = useRef(spaces); + const oldSpaces = useRef(spaces); const oldPanelsState = useRef(panelsState); const isMounted = useRef(false); + const unboundState = useUnboundState({ + spaces, + sessionSpaces, + panelsState, + sessionPanelsState, + updateSpaces, + setPanelsState, + setSessionSpaces, + }); useEffect(() => clearSpaces, [clearSpaces]); + // Update local spaces layout to latest session spaces layout useEffect(() => { + const { spaces, updateSpaces } = unboundState; if (!spaces.equals(sessionSpaces)) { updateSpaces(sessionSpaces); } - }, [sessionSpaces]); + }, [unboundState, sessionSpaces]); + // Update local panels state to latest session panels state useEffect(() => { + const { panelsState, setPanelsState } = unboundState; if (size(sessionPanelsState) && !isEqual(sessionPanelsState, panelsState)) { setPanelsState(sessionPanelsState); } - }, [sessionPanelsState]); + }, [unboundState, sessionPanelsState]); + // Update session spaces layout and panels state to latest local spaces layout and panels state useEffect(() => { if (!isMounted.current) { isMounted.current = true; return; } + const { sessionSpaces, sessionPanelsState, setSessionSpaces } = + unboundState; const serializedSpaces = spaces.toJSON(); const spacesUpdated = !spaces.equals(sessionSpaces) && !spaces.equals(oldSpaces.current); @@ -47,14 +69,7 @@ function MainSpace() { } oldSpaces.current = serializedSpaces; oldPanelsState.current = panelsState; - }, [ - oldSpaces, - panelsState, - sessionSpaces, - sessionPanelsState, - setSessionSpaces, - spaces, - ]); + }, [unboundState, oldSpaces, panelsState, spaces]); return ; } diff --git a/app/packages/core/src/plugins/SchemaIO/components/DynamicIO.tsx b/app/packages/core/src/plugins/SchemaIO/components/DynamicIO.tsx index 4991d08cb95..8800ed0bec6 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/DynamicIO.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/DynamicIO.tsx @@ -1,18 +1,17 @@ import { PluginComponentType, useActivePlugins } from "@fiftyone/plugins"; +import { useUnboundState } from "@fiftyone/state"; import { isNullish } from "@fiftyone/utilities"; import { get, isEqual, set } from "lodash"; -import { useEffect, useMemo } from "react"; +import React, { useEffect, useMemo } from "react"; import { isPathUserChanged } from "../hooks"; import { getComponent, getErrorsForView, isCompositeView, - isEditableView, isInitialized, } from "../utils"; import { AncestorsType, SchemaType, ViewPropsType } from "../utils/types"; import ContainerizedComponent from "./ContainerizedComponent"; -import { useUnboundState } from "@fiftyone/state"; export default function DynamicIO(props: ViewPropsType) { const { schema, onChange } = props; @@ -73,28 +72,31 @@ function useStateInitializer(props: ViewPropsType) { const computedSchema = getComputedSchema(props); const { default: defaultValue } = computedSchema; const shouldInitialize = useMemo(() => { - return !isCompositeView(computedSchema) && isEditableView(computedSchema); + return !isCompositeView(computedSchema); }, [computedSchema]); const basicData = useMemo(() => { if (shouldInitialize) { return data; } }, [shouldInitialize, data]); - const unboundState = useUnboundState({ computedSchema, props }); + const unboundState = useUnboundState({ computedSchema, props, data }); useEffect(() => { const { computedSchema, props } = unboundState; - const { data, path, root_id } = props || {}; + const { data, path, root_id, otherProps = {} } = props || {}; + const { updatableDefaultValue = true } = otherProps; + const updateToDefault = updatableDefaultValue ? true : isNullish(data); if ( shouldInitialize && - !isEqual(data, defaultValue) && - !isPathUserChanged(path, root_id) && + updateToDefault && !isNullish(defaultValue) && - !isInitialized(props) + !isPathUserChanged(path, root_id) && + !isInitialized(props) && + !isEqual(data, defaultValue) ) { onChange(path, defaultValue, computedSchema); } - }, [defaultValue, onChange, unboundState]); + }, [shouldInitialize, defaultValue, onChange, unboundState]); useEffect(() => { if (basicData) { diff --git a/app/packages/operators/src/CustomPanel.tsx b/app/packages/operators/src/CustomPanel.tsx index a5b1aafcde0..385380dd42a 100644 --- a/app/packages/operators/src/CustomPanel.tsx +++ b/app/packages/operators/src/CustomPanel.tsx @@ -1,20 +1,22 @@ +import { useTrackEvent } from "@fiftyone/analytics"; import { CenteredStack, CodeBlock, scrollable } from "@fiftyone/components"; import { clearUseKeyStores } from "@fiftyone/core/src/plugins/SchemaIO/hooks"; import { PanelSkeleton, usePanelLoading, + usePanelState, useSetPanelCloseEffect, } from "@fiftyone/spaces"; import * as fos from "@fiftyone/state"; import { Box, Typography } from "@mui/material"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import OperatorIO from "./OperatorIO"; import { PANEL_LOAD_TIMEOUT } from "./constants"; import { useActivePanelEventsCount } from "./hooks"; import { Property } from "./types"; import { CustomPanelProps, useCustomPanelHooks } from "./useCustomPanelHooks"; -import { useTrackEvent } from "@fiftyone/analytics"; import usePanelEvent from "./usePanelEvent"; +import { isNullish } from "@fiftyone/utilities"; export function CustomPanel(props: CustomPanelProps) { const { panelId, dimensions, panelName, panelLabel, isModalPanel } = props; @@ -95,6 +97,7 @@ export function CustomPanel(props: CustomPanelProps) { onPathChange={handlePanelStatePathChange} shouldClearUseKeyStores={false} isModalPanel={isModalPanel} + updatableDefaultValue={false} /> @@ -128,9 +131,24 @@ export function defineCustomPanel({ on_change_spaces, panel_name, panel_label, + reset_state, }) { return (props) => { const { dimensions, panelNode, isModalPanel } = props; + const [state, setState] = usePanelState(); + const [localState] = usePanelState(null, null, true); + const hasState = !isNullish(state?.state); + const isLoaded = localState?.loaded; + const isReset = useRef(false); + + if (!isLoaded && hasState && reset_state) { + if (!isReset.current) { + setState({}); + isReset.current = true; + } + return null; + } + return (