diff --git a/applications/browser-extension/end-to-end-tests/tests/pageEditor/saveMod.spec.ts b/applications/browser-extension/end-to-end-tests/tests/pageEditor/saveMod.spec.ts index ab4a9004f5..4c7302d10e 100644 --- a/applications/browser-extension/end-to-end-tests/tests/pageEditor/saveMod.spec.ts +++ b/applications/browser-extension/end-to-end-tests/tests/pageEditor/saveMod.spec.ts @@ -52,7 +52,6 @@ test("can save a new trigger mod", async ({ test("#9349: can save new mod with multiple components", async ({ page, - extensionId, newPageEditorPage, }) => { await page.goto("/"); diff --git a/applications/browser-extension/package.json b/applications/browser-extension/package.json index 8f4b54b603..6e1d9089f1 100644 --- a/applications/browser-extension/package.json +++ b/applications/browser-extension/package.json @@ -105,7 +105,7 @@ "kbar": "^0.1.0-beta.45", "lodash-es": "^4.17.21", "mark.js": "^8.11.1", - "marked": "^14.1.3", + "marked": "^15.0.0", "memoize-one": "^6.0.0", "mustache": "^4.2.0", "nunjucks": "^3.2.4", diff --git a/applications/browser-extension/src/components/formBuilder/edit/ActiveField.tsx b/applications/browser-extension/src/components/formBuilder/edit/ActiveField.tsx index 81cbf8fd73..1c66c76161 100644 --- a/applications/browser-extension/src/components/formBuilder/edit/ActiveField.tsx +++ b/applications/browser-extension/src/components/formBuilder/edit/ActiveField.tsx @@ -3,7 +3,6 @@ import { type SelectStringOption, type SetActiveField, } from "@/components/formBuilder/formBuilderTypes"; -import FieldEditor from "./fieldEditor/FieldEditor"; import { moveStringInArray, getNormalizedUiOrder, @@ -13,6 +12,7 @@ import FieldTemplate from "@/components/form/FieldTemplate"; import LayoutWidget from "@/components/LayoutWidget"; import { findLast } from "lodash"; import { type FormikErrors } from "formik"; +import FieldEditor from "@/components/formBuilder/edit/fieldEditor/FieldEditor"; export const ActiveField: React.FC<{ name: string; diff --git a/applications/browser-extension/src/components/formBuilder/formBuilderHelpers.ts b/applications/browser-extension/src/components/formBuilder/formBuilderHelpers.ts index a938d1f473..fbf74e93d6 100644 --- a/applications/browser-extension/src/components/formBuilder/formBuilderHelpers.ts +++ b/applications/browser-extension/src/components/formBuilder/formBuilderHelpers.ts @@ -66,11 +66,6 @@ export const FIELD_TYPES_WITHOUT_DEFAULT = [ propertyType: "string", propertyFormat: "data-url", }), - stringifyUiType({ - propertyType: "string", - uiWidget: "richText", - propertyFormat: "html", - }), stringifyUiType({ propertyType: "string", uiWidget: "imageCrop", diff --git a/applications/browser-extension/src/components/formBuilder/widgets/RichTextWidget.tsx b/applications/browser-extension/src/components/formBuilder/widgets/RichTextWidget.tsx index bd535147f8..61c19c4654 100644 --- a/applications/browser-extension/src/components/formBuilder/widgets/RichTextWidget.tsx +++ b/applications/browser-extension/src/components/formBuilder/widgets/RichTextWidget.tsx @@ -28,6 +28,7 @@ const RichTextWidget: React.FunctionComponent = ({ disabled, readonly, options, + value }) => { const { database } = options; @@ -45,6 +46,7 @@ const RichTextWidget: React.FunctionComponent = ({ }} editable={!(disabled || readonly)} assetDatabaseId={validateUUID(database)} + content={typeof value === "string" ? value : ""} /> ); }; diff --git a/applications/browser-extension/src/components/richTextEditor/ErrorToast.tsx b/applications/browser-extension/src/components/richTextEditor/ErrorToast.tsx index 660fba6fa4..565278445f 100644 --- a/applications/browser-extension/src/components/richTextEditor/ErrorToast.tsx +++ b/applications/browser-extension/src/components/richTextEditor/ErrorToast.tsx @@ -35,10 +35,12 @@ const ErrorToast: React.FC = ({ error, onClose }) => ( animation={false} delay={5000} > + {error} + ); diff --git a/applications/browser-extension/src/components/richTextEditor/RichTextEditor.module.scss b/applications/browser-extension/src/components/richTextEditor/RichTextEditor.module.scss index 5601949162..386fa69221 100644 --- a/applications/browser-extension/src/components/richTextEditor/RichTextEditor.module.scss +++ b/applications/browser-extension/src/components/richTextEditor/RichTextEditor.module.scss @@ -44,8 +44,6 @@ } .error { - display: flex !important; - align-items: center; color: #dc3545; padding: 4px 8px; font-size: 0.875rem; @@ -53,6 +51,11 @@ bottom: 4px; left: 4px; + span { + display: flex; + align-items: center; + } + :global(.btn) { margin-left: 8px; color: #dc3545; diff --git a/applications/browser-extension/src/pageEditor/hooks/useCreateModFromUnsavedMod.ts b/applications/browser-extension/src/pageEditor/hooks/useCreateModFromUnsavedMod.ts index f27bd8f52f..3dbe9d0a2f 100644 --- a/applications/browser-extension/src/pageEditor/hooks/useCreateModFromUnsavedMod.ts +++ b/applications/browser-extension/src/pageEditor/hooks/useCreateModFromUnsavedMod.ts @@ -116,6 +116,14 @@ function useCreateModFromUnsavedMod(): UseCreateModFromUnsavedModReturn { if (activeModId === unsavedModId) { // If the mod list item is selected, reselect the mod item using the new id dispatch(editorActions.setActiveModId(newModId)); + // Preserve the activeModComponentId if there is one + if (activeModComponentFormState?.uuid) { + dispatch( + editorActions.setActiveModComponentId( + activeModComponentFormState.uuid, + ), + ); + } } else if ( activeModComponentFormState?.modMetadata.id === unsavedModId ) { diff --git a/applications/browser-extension/src/pageEditor/hooks/useEnsureFormStates.ts b/applications/browser-extension/src/pageEditor/hooks/useEnsureFormStates.ts index 4e0153ae8d..a5979deab6 100644 --- a/applications/browser-extension/src/pageEditor/hooks/useEnsureFormStates.ts +++ b/applications/browser-extension/src/pageEditor/hooks/useEnsureFormStates.ts @@ -18,7 +18,7 @@ import { actions } from "@/pageEditor/store/editor/editorSlice"; import { modComponentToFormState } from "@/pageEditor/starterBricks/adapter"; import { - selectCurrentModId, + selectActiveModId, selectGetUntouchedActivatedModComponentsForMod, } from "@/pageEditor/store/editor/editorSelectors"; import { useDispatch, useSelector } from "react-redux"; @@ -30,13 +30,13 @@ import { useEffect } from "react"; */ function useEnsureFormStates(): void { const dispatch = useDispatch(); - const currentModId = useSelector(selectCurrentModId); + const activeModId = useSelector(selectActiveModId); const getUntouchedActivatedModComponentsForMod = useSelector( selectGetUntouchedActivatedModComponentsForMod, ); - const untouchedModComponents = currentModId - ? getUntouchedActivatedModComponentsForMod(currentModId) + const untouchedModComponents = activeModId + ? getUntouchedActivatedModComponentsForMod(activeModId) : null; useEffect(() => { diff --git a/applications/browser-extension/src/pageEditor/hooks/useRegisterDraftModInstanceOnAllFrames.ts b/applications/browser-extension/src/pageEditor/hooks/useRegisterDraftModInstanceOnAllFrames.ts index 21047bcb15..6ee735139b 100644 --- a/applications/browser-extension/src/pageEditor/hooks/useRegisterDraftModInstanceOnAllFrames.ts +++ b/applications/browser-extension/src/pageEditor/hooks/useRegisterDraftModInstanceOnAllFrames.ts @@ -31,7 +31,7 @@ import { useDispatch, useSelector } from "react-redux"; import { selectActiveModComponentFormState, selectActiveModComponentId, - selectCurrentModId, + selectActiveModId, selectEditorUpdateKey, selectGetModDraftStateForModId, } from "@/pageEditor/store/editor/editorSelectors"; @@ -113,7 +113,7 @@ function updateDraftModInstance() { const state = getState(); const activeModComponentId = selectActiveModComponentId(state); - const modId = selectCurrentModId(state); + const modId = selectActiveModId(state); if (!modId) { // Skip if the modId has somehow become null before the microtask for this async method got scheduled @@ -175,7 +175,7 @@ function updateDraftModInstance() { */ function useRegisterDraftModInstanceOnAllFrames(): void { const dispatch = useDispatch(); - const modId = useSelector(selectCurrentModId); + const modId = useSelector(selectActiveModId); const editorUpdateKey = useSelector(selectEditorUpdateKey); assertNotNullish(modId, "modId is required"); diff --git a/applications/browser-extension/src/pageEditor/modListingPanel/ModListItem.tsx b/applications/browser-extension/src/pageEditor/modListingPanel/ModListItem.tsx index 458e2885ac..44142e75fa 100644 --- a/applications/browser-extension/src/pageEditor/modListingPanel/ModListItem.tsx +++ b/applications/browser-extension/src/pageEditor/modListingPanel/ModListItem.tsx @@ -29,7 +29,7 @@ import { import { useDispatch, useSelector } from "react-redux"; import cx from "classnames"; import { - selectActiveModComponentFormState, + selectActiveModComponentId, selectActiveModId, selectDirtyMetadataForModId, selectExpandedModId, @@ -47,13 +47,12 @@ const ModListItem: React.FC< const dispatch = useDispatch(); const activeModId = useSelector(selectActiveModId); const expandedModId = useSelector(selectExpandedModId); - const activeModComponentFormState = useSelector( - selectActiveModComponentFormState, - ); + const activeModComponentId = useSelector(selectActiveModComponentId); const { id: modId, name: savedName, version: activatedVersion } = modMetadata; - const isActive = activeModId === modId; + const isModComponentSelected = activeModComponentId != null; + const isModSelected = activeModId === modId && !isModComponentSelected; const isExpanded = expandedModId === modId; // TODO: Fix this so it pulls from registry, after registry single-item-api-fetch is implemented @@ -61,10 +60,6 @@ const ModListItem: React.FC< const { data: modDefinition } = useGetModDefinitionQuery({ modId }); const latestModVersion = modDefinition?.metadata?.version; - // Set the alternate background if a mod component in this mod is active - const hasModBackground = - activeModComponentFormState?.modMetadata.id === modId; - const dirtyName = useSelector(selectDirtyMetadataForModId(modId))?.name; const name = dirtyName ?? savedName ?? "Loading..."; @@ -81,16 +76,19 @@ const ModListItem: React.FC< eventKey={modId} as={ListGroup.Item} className={cx(styles.root, "list-group-item-action", { - [styles.modBackground ?? ""]: hasModBackground, + // Set the alternate background if a mod component in this mod is active + [styles.modBackground ?? ""]: isModSelected || isModComponentSelected, })} tabIndex={0} // Avoid using `button` because this item includes more buttons #2343 - active={isActive} - key={`mod-${modId}`} + active={isModSelected} + key={modId} onClick={() => { dispatch(actions.setActiveModId(modId)); // Collapse if the user clicks the mod item when it's already active/selected in the listing pane dispatch( - actions.setExpandedModId(isExpanded && isActive ? null : modId), + actions.setExpandedModId( + isExpanded && isModSelected ? null : modId, + ), ); }} > diff --git a/applications/browser-extension/src/pageEditor/modListingPanel/ModSidebarListItems.tsx b/applications/browser-extension/src/pageEditor/modListingPanel/ModSidebarListItems.tsx index 24146efa13..5538102997 100644 --- a/applications/browser-extension/src/pageEditor/modListingPanel/ModSidebarListItems.tsx +++ b/applications/browser-extension/src/pageEditor/modListingPanel/ModSidebarListItems.tsx @@ -77,25 +77,33 @@ const ModSidebarListItems: React.FunctionComponent = () => { ], ); - const listItems = filteredSidebarItems.map((sidebarItem) => { - const { modMetadata, modComponents } = sidebarItem; + const listItems = useMemo( + () => + filteredSidebarItems.map((sidebarItem) => { + const { modMetadata, modComponents } = sidebarItem; - return ( - - {modComponents.map((modComponentSidebarItem) => ( - - ))} - - ); - }); + return ( + + {modComponents.map((modComponentSidebarItem) => ( + + ))} + + ); + }), + [ + availableActivatedModComponentIds, + availableDraftModComponentIds, + filteredSidebarItems, + ], + ); return ( <> diff --git a/applications/browser-extension/src/pageEditor/modListingPanel/modals/CreateModModal.tsx b/applications/browser-extension/src/pageEditor/modListingPanel/modals/CreateModModal.tsx index 04667f3e10..8e440980b7 100644 --- a/applications/browser-extension/src/pageEditor/modListingPanel/modals/CreateModModal.tsx +++ b/applications/browser-extension/src/pageEditor/modListingPanel/modals/CreateModModal.tsx @@ -25,7 +25,7 @@ import { useDispatch, useSelector } from "react-redux"; import { getModalDataSelector, selectActiveModComponentFormState, - selectCurrentModId, + selectActiveModId, selectEditorModalVisibilities, selectModMetadataMap, } from "@/pageEditor/store/editor/editorSelectors"; @@ -140,8 +140,8 @@ const CreateModModalBody: React.FC<{ onHide: () => void }> = ({ onHide }) => { const dispatch = useDispatch(); const isMounted = useIsMounted(); - const currentModId = useSelector(selectCurrentModId); - assertNotNullish(currentModId, "Expected mod or mod component to be active"); + const activeModId = useSelector(selectActiveModId); + assertNotNullish(activeModId, "Expected mod or mod component to be active"); const activeModComponentFormState = useSelector( selectActiveModComponentFormState, @@ -158,11 +158,11 @@ const CreateModModalBody: React.FC<{ onHide: () => void }> = ({ onHide }) => { const { createModFromComponent } = useCreateModFromModComponent(); const { data: modDefinition, isFetching: isModDefinitionFetching } = - useOptionalModDefinition(currentModId); + useOptionalModDefinition(activeModId); const formSchema = useFormSchema(); - const initialFormState = useInitialFormState(currentModId); + const initialFormState = useInitialFormState(activeModId); const onSubmit: OnSubmit = async (values, helpers) => { if (isModDefinitionFetching) { @@ -187,10 +187,10 @@ const CreateModModalBody: React.FC<{ onHide: () => void }> = ({ onHide }) => { ) { // Handle "Save As" case where the mod is unsaved or the user no longer has access to the mod definition assertNotNullish( - currentModId, + activeModId, "Expected mod to be selected in the editor", ); - await createModFromUnsavedMod(currentModId, values); + await createModFromUnsavedMod(activeModId, values); } else { await createModFromMod(modDefinition, values, modalData); } diff --git a/applications/browser-extension/src/pageEditor/modListingPanel/modals/SaveAsNewModModal.tsx b/applications/browser-extension/src/pageEditor/modListingPanel/modals/SaveAsNewModModal.tsx index dc22a63d2f..ae3441d2dc 100644 --- a/applications/browser-extension/src/pageEditor/modListingPanel/modals/SaveAsNewModModal.tsx +++ b/applications/browser-extension/src/pageEditor/modListingPanel/modals/SaveAsNewModModal.tsx @@ -20,7 +20,7 @@ import { useDispatch, useSelector } from "react-redux"; import { actions } from "@/pageEditor/store/editor/editorSlice"; import { Button, Modal } from "react-bootstrap"; import { - selectCurrentModId, + selectActiveModId, selectEditorModalVisibilities, } from "@/pageEditor/store/editor/editorSelectors"; import { useOptionalModDefinition } from "@/modDefinitions/modDefinitionHooks"; @@ -32,7 +32,7 @@ const SaveAsNewModModal: React.FC = () => { selectEditorModalVisibilities, ); - const modId = useSelector(selectCurrentModId); + const modId = useSelector(selectActiveModId); const { data: mod, isFetching } = useOptionalModDefinition(modId); const modName = mod?.metadata?.name ?? "this mod"; diff --git a/applications/browser-extension/src/pageEditor/modals/addBrickModal/useAddBrick.ts b/applications/browser-extension/src/pageEditor/modals/addBrickModal/useAddBrick.ts index 7f8b459947..bf5ede63df 100644 --- a/applications/browser-extension/src/pageEditor/modals/addBrickModal/useAddBrick.ts +++ b/applications/browser-extension/src/pageEditor/modals/addBrickModal/useAddBrick.ts @@ -74,7 +74,9 @@ function useAddBrick(): AddBrick { brick, compact([ "input" as OutputKey, - ...Object.values(pipelineMap).map((x) => x.blockConfig.outputKey), + ...Object.values(pipelineMap ?? {}).map( + (x) => x.blockConfig.outputKey, + ), ]), ); const newBrick = createNewConfiguredBrick(brick.id, { diff --git a/applications/browser-extension/src/pageEditor/store/editor/editorInvariantMiddleware.ts b/applications/browser-extension/src/pageEditor/store/editor/editorInvariantMiddleware.ts index e9c9c46779..f81845e0db 100644 --- a/applications/browser-extension/src/pageEditor/store/editor/editorInvariantMiddleware.ts +++ b/applications/browser-extension/src/pageEditor/store/editor/editorInvariantMiddleware.ts @@ -18,15 +18,15 @@ import type { AnyAction, Dispatch, Middleware } from "@reduxjs/toolkit"; import { type EditorRootState } from "@/pageEditor/store/editor/pageEditorTypes"; import { - selectActiveModComponentId, + selectActiveModComponentFormState, selectActiveModId, selectAllDeletedModComponentIds, - selectCurrentModId, selectExpandedModId, selectModComponentFormStates, } from "@/pageEditor/store/editor/editorSelectors"; import type { EmptyObject } from "type-fest"; import { uniqBy } from "lodash"; +import { isInternalRegistryId } from "@/utils/registryUtils"; class InvariantViolationError extends Error { override name = "InvariantViolationError"; @@ -48,17 +48,28 @@ class InvariantViolationError extends Error { // XXX: in production, should we be attempting to auto-fix these invariants? export function assertEditorInvariants(state: EditorRootState): void { // Assert that a mod and mod component item cannot be selected at the same time - if (selectActiveModId(state) && selectActiveModComponentId(state)) { + const activeModId = selectActiveModId(state); + const activeModComponent = selectActiveModComponentFormState(state); + + if ( + activeModId && + activeModComponent && + activeModId !== activeModComponent?.modMetadata.id && + // When saving, the activeModId and activeModComponent.modMetadata.id aren't updated at the same time. + !isInternalRegistryId(activeModId) + ) { + // Should we dispatch(actions.setActiveModComponentId(null)) + // Would need to change the behavior of the action to handle null throw new InvariantViolationError( - "activeModId and activeModComponentId are both set", + "activeModComponent is not a part of the activeMod", ); } // Assert that the expanded mod must correspond to the selected mod or mod component const expandedModId = selectExpandedModId(state); - if (expandedModId && selectCurrentModId(state) !== expandedModId) { + if (expandedModId && activeModId !== expandedModId) { throw new InvariantViolationError( - "expandedModId does not match active mod/mod component", + "expandedModId does not match active mod", ); } diff --git a/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorDataPanelSelectors.ts b/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorDataPanelSelectors.ts index 07a9e0324d..5737c6e92f 100644 --- a/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorDataPanelSelectors.ts +++ b/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorDataPanelSelectors.ts @@ -21,7 +21,7 @@ import type { DataPanelTabKey } from "@/pageEditor/tabs/editTab/dataPanel/dataPa import { createSelector } from "@reduxjs/toolkit"; import type { DataPanelTabUIState } from "@/pageEditor/store/editor/uiStateTypes"; import { selectActiveBrickConfigurationUIState } from "@/pageEditor/store/editor/editorSelectors/editorPipelineSelectors"; -import { selectCurrentModId } from "@/pageEditor/store/editor/editorSelectors/editorModSelectors"; +import { selectActiveModId } from "@/pageEditor/store/editor/editorSelectors/editorNavigationSelectors"; export const selectIsDataPanelExpanded = ({ editor }: EditorRootState) => editor.isDataPanelExpanded; @@ -55,9 +55,9 @@ export function selectNodeDataPanelTabState( */ export const selectCurrentFindInModQuery = createSelector( ({ editor }: EditorRootState) => editor.findInModQueryByModId, - selectCurrentModId, + selectActiveModId, (findInModQueryByModId, modId) => { - assertNotNullish(modId, "Expected currentModId"); + assertNotNullish(modId, "Expected activeModId"); // eslint-disable-next-line security/detect-object-injection -- registry id return findInModQueryByModId[modId] ?? { query: "" }; }, diff --git a/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorModSelectors.ts b/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorModSelectors.ts index 847daa71e1..700a06e255 100644 --- a/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorModSelectors.ts +++ b/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorModSelectors.ts @@ -23,13 +23,11 @@ import { selectModInstances } from "@/store/modComponents/modInstanceSelectors"; import mapModDefinitionToModMetadata from "@/modDefinitions/util/mapModDefinitionToModMetadata"; import { normalizeModOptionsDefinition } from "@/utils/modUtils"; import { - selectActiveModComponentFormState, selectGetModComponentFormStateByModComponentId, selectIsModComponentDirtyById, selectModComponentFormStates, selectNotDeletedActivatedModComponents, } from "@/pageEditor/store/editor/editorSelectors/editorModComponentSelectors"; -import { selectActiveModId } from "@/pageEditor/store/editor/editorSelectors/editorNavigationSelectors"; import type { ModMetadata } from "@/types/modComponentTypes"; import type { UUID } from "@/types/stringTypes"; import { assertNotNullish } from "@/utils/nullishUtils"; @@ -39,19 +37,6 @@ import { collectModVariablesDefinition, } from "@/store/modComponents/modComponentUtils"; -/** - * Select the mod id associated with the selected mod package or mod component. Should be used if the caller doesn't - * need to know if the mod item or one of its components is selected. - * @see selectActiveModId - * @see selectExpandedModId - */ -export const selectCurrentModId = createSelector( - selectActiveModId, - selectActiveModComponentFormState, - (activeModId, activeModComponentFormState) => - activeModId ?? activeModComponentFormState?.modMetadata.id, -); - /// /// MOD METADATA /// diff --git a/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorNavigationSelectors.ts b/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorNavigationSelectors.ts index b8e82070c3..5a7ea3c1a8 100644 --- a/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorNavigationSelectors.ts +++ b/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorNavigationSelectors.ts @@ -29,9 +29,8 @@ export const selectActiveModComponentId = ({ editor }: EditorRootState) => { }; /** - * Select the id of the mod being edited. NOTE: is null when editing a mod component within the mod. + * Select the id of the mod being edited. * @see selectModId - * @see selectCurrentModId */ export const selectActiveModId = ({ editor }: EditorRootState) => editor.activeModId; diff --git a/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorPipelineSelectors.ts b/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorPipelineSelectors.ts index 14dd456354..9f1c45b3ab 100644 --- a/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorPipelineSelectors.ts +++ b/applications/browser-extension/src/pageEditor/store/editor/editorSelectors/editorPipelineSelectors.ts @@ -73,9 +73,9 @@ export const selectActiveNodeInfo = createSelector( const activeModComponentNodeInfoSelector = createSelector( selectActiveBrickPipelineUIState, (_state: EditorRootState, instanceId: UUID) => instanceId, - (uiState: BrickPipelineUIState, instanceId: UUID) => + (uiState, instanceId) => // eslint-disable-next-line security/detect-object-injection -- using a node uuid - uiState.pipelineMap[instanceId], + uiState?.pipelineMap[instanceId], ); export const selectActiveModComponentNodeInfo = @@ -103,13 +103,13 @@ export const selectActiveNodeEventData = createSelector( export const selectPipelineMap = createSelector( selectActiveBrickPipelineUIState, - (uiState: BrickPipelineUIState) => uiState?.pipelineMap, + (uiState) => uiState?.pipelineMap, ); export const selectCollapsedNodes = createSelector( selectActiveBrickPipelineUIState, - (brickPipelineUIState: BrickPipelineUIState) => - Object.entries(brickPipelineUIState.nodeUIStates) + (brickPipelineUIState) => + Object.entries(brickPipelineUIState?.nodeUIStates ?? {}) .map(([nodeId, { collapsed }]) => (collapsed ? nodeId : null)) .filter((nodeId) => nodeId != null), ); @@ -117,7 +117,7 @@ export const selectCollapsedNodes = createSelector( const parentNodeInfoSelector = createSelector( selectActiveBrickPipelineUIState, (_state: EditorRootState, nodeId: UUID) => nodeId, - (brickPipelineUIState: BrickPipelineUIState, nodeId: UUID) => { + (brickPipelineUIState, nodeId) => { if (brickPipelineUIState == null) { return null; } diff --git a/applications/browser-extension/src/pageEditor/store/editor/editorSlice.ts b/applications/browser-extension/src/pageEditor/store/editor/editorSlice.ts index dd4b1a098b..d46331bdfe 100644 --- a/applications/browser-extension/src/pageEditor/store/editor/editorSlice.ts +++ b/applications/browser-extension/src/pageEditor/store/editor/editorSlice.ts @@ -39,7 +39,7 @@ import { selectActiveBrickConfigurationUIState, selectActiveBrickPipelineUIState, selectActiveModComponentFormState, - selectCurrentModId, + selectActiveModId, selectGetModComponentFormStateByModComponentId, selectGetModComponentFormStatesForMod, selectModComponentFormStates, @@ -350,16 +350,17 @@ export const editorSlice = createSlice({ /// /** - * Activate the mod with the given id. Expands the mod listing pane item if not already expanded + * Select the mod with the given id. Expands the mod listing pane item if not already expanded * @see toggleExpandedModId */ setActiveModId(state, action: PayloadAction) { const modId = action.payload; state.error = null; - state.activeModComponentId = null; state.activeModId = modId; + state.activeModComponentId = null; + if (state.expandedModId !== modId) { state.expandedModId = modId; } @@ -1017,12 +1018,12 @@ export const editorSlice = createSlice({ setDataPanelTabFindQuery(state, action: PayloadAction<{ query: string }>) { const { query } = action.payload; - const currentModId = selectCurrentModId({ + const activeModId = selectActiveModId({ editor: castEditorState(state), }); - assertNotNullish(currentModId, "Expected currentModId"); + assertNotNullish(activeModId, "Expected activeModId"); - state.findInModQueryByModId[currentModId] = { query }; + state.findInModQueryByModId[activeModId] = { query }; }, /** @@ -1128,7 +1129,7 @@ export const persistEditorConfig: PersistConfig = { // Change the type of localStorage to our overridden version so that it can be exported // See: @/store/StorageInterface.ts storage: localStorage as StorageInterface, - version: 13, + version: 14, migrate: createMigrate(migrations, { debug: Boolean(process.env.DEBUG) }), blacklist: Object.keys(initialEphemeralState), }; diff --git a/applications/browser-extension/src/pageEditor/store/editor/editorSliceHelpers.ts b/applications/browser-extension/src/pageEditor/store/editor/editorSliceHelpers.ts index eb56e0556f..218d34f7c4 100644 --- a/applications/browser-extension/src/pageEditor/store/editor/editorSliceHelpers.ts +++ b/applications/browser-extension/src/pageEditor/store/editor/editorSliceHelpers.ts @@ -172,9 +172,8 @@ export function setActiveModComponentId( ) { state.error = null; state.activeModComponentId = modComponentFormState.uuid; - state.activeModId = null; - state.expandedModId = - modComponentFormState.modMetadata.id ?? state.expandedModId; + state.activeModId = modComponentFormState.modMetadata.id; + state.expandedModId = modComponentFormState.modMetadata.id; state.selectionSeq++; ensureBrickPipelineUIState(state, modComponentFormState.uuid); diff --git a/applications/browser-extension/src/pageEditor/store/editor/pageEditorTypes/editorStateMigrated.ts b/applications/browser-extension/src/pageEditor/store/editor/pageEditorTypes/editorStateMigrated.ts index 5368b0e862..6dd2f59606 100644 --- a/applications/browser-extension/src/pageEditor/store/editor/pageEditorTypes/editorStateMigrated.ts +++ b/applications/browser-extension/src/pageEditor/store/editor/pageEditorTypes/editorStateMigrated.ts @@ -388,12 +388,12 @@ export type EditorStateMigratedV11 = Except< */ export type EditorStateMigratedV12 = Pick< EditorStateMigratedV11, + | "deletedModComponentFormStateIdsByModId" | "dirty" - | "dirtyModVariablesDefinitionById" - | "isDimensionsWarningDismissed" | "dirtyModMetadataById" | "dirtyModOptionsArgsById" - | "modComponentFormStates" - | "deletedModComponentFormStateIdsByModId" | "dirtyModOptionsDefinitionById" + | "dirtyModVariablesDefinitionById" + | "isDimensionsWarningDismissed" + | "modComponentFormStates" >; diff --git a/applications/browser-extension/src/pageEditor/tabs/editTab/EditTab.tsx b/applications/browser-extension/src/pageEditor/tabs/editTab/EditTab.tsx index 6cc18f6533..676d4c55d7 100644 --- a/applications/browser-extension/src/pageEditor/tabs/editTab/EditTab.tsx +++ b/applications/browser-extension/src/pageEditor/tabs/editTab/EditTab.tsx @@ -64,7 +64,7 @@ const EditTab: React.FC<{ function copyBrick(instanceId: UUID) { // eslint-disable-next-line security/detect-object-injection -- UUID - const brickToCopy = pipelineMap[instanceId]?.blockConfig; + const brickToCopy = pipelineMap?.[instanceId]?.blockConfig; if (brickToCopy) { dispatch(actions.copyBrickConfig(brickToCopy)); } diff --git a/applications/browser-extension/src/pageEditor/tabs/editTab/dataPanel/tabs/FindTab/useFindInMod.ts b/applications/browser-extension/src/pageEditor/tabs/editTab/dataPanel/tabs/FindTab/useFindInMod.ts index ba1b9bc81b..f1cb8f75bc 100644 --- a/applications/browser-extension/src/pageEditor/tabs/editTab/dataPanel/tabs/FindTab/useFindInMod.ts +++ b/applications/browser-extension/src/pageEditor/tabs/editTab/dataPanel/tabs/FindTab/useFindInMod.ts @@ -17,7 +17,7 @@ import { useSelector } from "react-redux"; import { - selectCurrentModId, + selectActiveModId, selectGetModComponentFormStatesForMod, } from "@/pageEditor/store/editor/editorSelectors"; import { useMemo } from "react"; @@ -40,14 +40,14 @@ function useFindInMod( // Find/search depends on all mod components having form states with instanceIds assigned useEnsureFormStates(); - const currentModId = useSelector(selectCurrentModId); - assertNotNullish(currentModId, "Expected currentModId"); + const activeModId = useSelector(selectActiveModId); + assertNotNullish(activeModId, "Expected activeModId"); const getModComponentFormStatesForMod = useSelector( selectGetModComponentFormStatesForMod, ); - const modComponentFormStates = getModComponentFormStatesForMod(currentModId); + const modComponentFormStates = getModComponentFormStatesForMod(activeModId); const fuse = useAsyncState(async () => { const items = await SearchIndexVisitor.collectItems(modComponentFormStates); diff --git a/applications/browser-extension/src/store/editorMigrations.ts b/applications/browser-extension/src/store/editorMigrations.ts index 59d6668cd9..653a8d513e 100644 --- a/applications/browser-extension/src/store/editorMigrations.ts +++ b/applications/browser-extension/src/store/editorMigrations.ts @@ -88,6 +88,10 @@ export const migrations: MigrationManifest = { 13: (state: EditorStateMigratedV12 & PersistedState) => // Added findInModOptionsByModId to EditorStateSynced resetEditorStateSynced(state), + 14: (state: EditorStateMigratedV12 & PersistedState) => + // Reset synced state to resolve issues from requiring an + // activeModId to have an activeModComponentId + resetEditorStateSynced(state), }; export function migrateIntegrationDependenciesV1toV2( diff --git a/applications/browser-extension/src/store/editorStorage.test.ts b/applications/browser-extension/src/store/editorStorage.test.ts index 3483bf2a98..dc669db9d1 100644 --- a/applications/browser-extension/src/store/editorStorage.test.ts +++ b/applications/browser-extension/src/store/editorStorage.test.ts @@ -91,6 +91,7 @@ describe("editorStorage", () => { "persist:editor", { ...targetEditorState, + activeModId: null, // When the mod's form states are added, they become active. On remove the active/expanded props are reset activeModComponentId: null, expandedModId: null, diff --git a/package-lock.json b/package-lock.json index c3fdff35ae..6203d8ba00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,7 +109,7 @@ "kbar": "^0.1.0-beta.45", "lodash-es": "^4.17.21", "mark.js": "^8.11.1", - "marked": "^14.1.3", + "marked": "^15.0.0", "memoize-one": "^6.0.0", "mustache": "^4.2.0", "nunjucks": "^3.2.4", @@ -20913,9 +20913,9 @@ } }, "node_modules/marked": { - "version": "14.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.3.tgz", - "integrity": "sha512-ZibJqTULGlt9g5k4VMARAktMAjXoVnnr+Y3aCqW1oDftcV4BA3UmrBifzXoZyenHRk75csiPu9iwsTj4VNBT0g==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.0.tgz", + "integrity": "sha512-0mouKmBROJv/WSHJBPZZyYofUgawMChnD5je/g+aOBXsHDjb/IsnTQj7mnhQZu+qPJmRQ0ecX3mLGEUm3BgwYA==", "bin": { "marked": "bin/marked.js" },