diff --git a/newIDE/app/src/CompactPropertiesEditor/TopLevelCollapsibleSection.js b/newIDE/app/src/CompactPropertiesEditor/TopLevelCollapsibleSection.js new file mode 100644 index 000000000000..2c42658d376a --- /dev/null +++ b/newIDE/app/src/CompactPropertiesEditor/TopLevelCollapsibleSection.js @@ -0,0 +1,77 @@ +// @flow + +import * as React from 'react'; +import Text from '../UI/Text'; +import { Separator } from '../CompactPropertiesEditor'; +import ShareExternal from '../UI/CustomSvgIcons/ShareExternal'; +import ChevronArrowDownWithRoundedBorder from '../UI/CustomSvgIcons/ChevronArrowDownWithRoundedBorder'; +import ChevronArrowRightWithRoundedBorder from '../UI/CustomSvgIcons/ChevronArrowRightWithRoundedBorder'; +import { Column, Line } from '../UI/Grid'; +import IconButton from '../UI/IconButton'; +import { LineStackLayout } from '../UI/Layout'; +import Add from '../UI/CustomSvgIcons/Add'; +import { textEllipsisStyle } from '../UI/TextEllipsis'; +import { styles } from '../ObjectEditor/CompactObjectPropertiesEditor'; + +type Props = { + title: React.Node, + isFolded: boolean, + toggleFolded: () => void, + renderContent: () => React.Node, + renderContentAsHiddenWhenFolded?: boolean, + noContentMargin?: boolean, + onOpenFullEditor?: () => void, + onAdd?: (() => void) | null, +}; + +export const TopLevelCollapsibleSection = ({ + title, + isFolded, + toggleFolded, + renderContent, + renderContentAsHiddenWhenFolded, + noContentMargin, + onOpenFullEditor, + onAdd, +}: Props): React.Node => ( + <> + + + + + + {isFolded ? ( + + ) : ( + + )} + + + {title} + + + + {onOpenFullEditor && ( + + + + )} + {onAdd && ( + + + + )} + + + + + {isFolded ? ( + renderContentAsHiddenWhenFolded ? ( +
{renderContent()}
+ ) : null + ) : ( + renderContent() + )} +
+ +); diff --git a/newIDE/app/src/InstancesEditor/CompactInstancePropertiesEditor/index.js b/newIDE/app/src/InstancesEditor/CompactInstancePropertiesEditor/index.js index c30d8139fbd8..799e50845a13 100644 --- a/newIDE/app/src/InstancesEditor/CompactInstancePropertiesEditor/index.js +++ b/newIDE/app/src/InstancesEditor/CompactInstancePropertiesEditor/index.js @@ -9,7 +9,6 @@ import CompactPropertiesEditor, { import propertiesMapToSchema from '../../PropertiesEditor/PropertiesMapToSchema'; import { type Schema } from '../../PropertiesEditor/PropertiesEditorSchema'; import getObjectByName from '../../Utils/GetObjectByName'; -import IconButton from '../../UI/IconButton'; import { Line, Column, Spacer, marginsSize } from '../../UI/Grid'; import Text from '../../UI/Text'; import { type UnsavedChanges } from '../../MainFrame/UnsavedChangesContext'; @@ -18,7 +17,6 @@ import EventsRootVariablesFinder from '../../Utils/EventsRootVariablesFinder'; import VariablesList, { type HistoryHandler, } from '../../VariablesList/VariablesList'; -import ShareExternal from '../../UI/CustomSvgIcons/ShareExternal'; import useForceUpdate from '../../Utils/UseForceUpdate'; import ErrorBoundary from '../../UI/ErrorBoundary'; import { @@ -30,10 +28,10 @@ import TileSetVisualizer, { type TileMapTileSelection, } from '../TileSetVisualizer'; import { - TopLevelCollapsibleSection, CollapsibleSubPanel, type TitleBarButton, } from '../../ObjectEditor/CompactObjectPropertiesEditor'; +import { TopLevelCollapsibleSection } from '../../CompactPropertiesEditor/TopLevelCollapsibleSection'; import { ColumnStackLayout } from '../../UI/Layout'; import Link from '../../UI/Link'; import { IconContainer } from '../../UI/IconContainer'; @@ -41,6 +39,7 @@ import { getHelpLink } from '../../Utils/HelpLink'; import Window from '../../Utils/Window'; import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource'; import { usePersistedScrollPosition } from '../../Utils/UsePersistedScrollPosition'; +import { usePersistedCollapsedSection } from '../../Utils/UsePersistedCollapsedSection'; import EmptyMessage from '../../UI/EmptyMessage'; import CompactInstanceBehaviorsEditorService from './CompactInstanceBehaviorsEditorService'; import { exceptionallyGuardAgainstDeadObject } from '../../Utils/IsNullPtr'; @@ -172,7 +171,7 @@ export const CompactInstancePropertiesEditor = ({ .map((instance: gdInitialInstance) => '' + instance.ptr) .join(';'); - const persistedScrollId = React.useMemo( + const persistedPanelStateId = React.useMemo( () => { if (!instances.length || !scrollKey) return null; @@ -193,9 +192,16 @@ export const CompactInstancePropertiesEditor = ({ project, scrollViewRef, scrollKey, - persistedScrollId, - persistedScrollType: 'instances-of-object', + persistedPanelStateId: persistedPanelStateId, + persistedPanelStateType: 'instances-of-object', }); + const { isSectionFolded, toggleSectionFolded } = usePersistedCollapsedSection( + { + project, + persistedPanelStateId: persistedPanelStateId, + persistedPanelStateType: 'instances-of-object', + } + ); const { object, instanceSchema, allVisibleBehaviors } = React.useMemo<{| object?: gdObject, @@ -521,52 +527,41 @@ export const CompactInstancePropertiesEditor = ({ /> ) : null} {object && shouldDisplayVariablesList && variablesContainer ? ( - <> - - - - - Instance Variables - - - { - editInstanceVariables(instance); - }} - > - - - - - - - object && layout - ? EventsRootVariablesFinder.findAllObjectVariables( - project.getCurrentPlatform(), - project, - layout, - object.getName() - ) - : [] - } - historyHandler={historyHandler} - toolbarIconStyle={styles.icon} - compactEmptyPlaceholderText={ - There are no variables on this instance. - } - isListLocked={true} - /> - + Instance Variables} + isFolded={isSectionFolded('variables')} + toggleFolded={() => toggleSectionFolded('variables')} + onOpenFullEditor={() => editInstanceVariables(instance)} + noContentMargin + renderContent={() => ( + + object && layout + ? EventsRootVariablesFinder.findAllObjectVariables( + project.getCurrentPlatform(), + project, + layout, + object.getName() + ) + : [] + } + historyHandler={historyHandler} + toolbarIconStyle={styles.icon} + compactEmptyPlaceholderText={ + There are no variables on this instance. + } + isListLocked={true} + /> + )} + /> ) : null} diff --git a/newIDE/app/src/LayersList/CompactLayerPropertiesEditor/CompactEffectsListEditor.js b/newIDE/app/src/LayersList/CompactLayerPropertiesEditor/CompactEffectsListEditor.js index 80c6a0f9d329..792844a4df1f 100644 --- a/newIDE/app/src/LayersList/CompactLayerPropertiesEditor/CompactEffectsListEditor.js +++ b/newIDE/app/src/LayersList/CompactLayerPropertiesEditor/CompactEffectsListEditor.js @@ -23,10 +23,9 @@ import { getHelpLink } from '../../Utils/HelpLink'; import Window from '../../Utils/Window'; import Link from '../../UI/Link'; import { CompactPropertiesEditorByVisibility } from '../../CompactPropertiesEditor/CompactPropertiesEditorByVisibility'; -import { - TopLevelCollapsibleSection, - CollapsibleSubPanel, -} from '../../ObjectEditor/CompactObjectPropertiesEditor'; +import { CollapsibleSubPanel } from '../../ObjectEditor/CompactObjectPropertiesEditor'; +import { TopLevelCollapsibleSection } from '../../CompactPropertiesEditor/TopLevelCollapsibleSection'; +import { usePersistedCollapsedSection } from '../../Utils/UsePersistedCollapsedSection'; export const styles = { icon: { @@ -68,6 +67,7 @@ type Props = {| onEffectAdded: () => void, layerRenderingType: '2d' | '3d', target: 'object' | 'layer', + persistedPanelStateId: string, |}; export const CompactEffectsListEditor = ({ @@ -82,9 +82,19 @@ export const CompactEffectsListEditor = ({ onEffectAdded, layerRenderingType, target, + persistedPanelStateId, }: Props): React.Node => { const forceUpdate = useForceUpdate(); - const [isEffectsFolded, setEffectsFolded] = React.useState(false); + + const { + isSectionFolded, + toggleSectionFolded, + setSectionFolded, + } = usePersistedCollapsedSection({ + project, + persistedPanelStateId: persistedPanelStateId, + persistedPanelStateType: 'layer', + }); // Effects: const { @@ -109,6 +119,27 @@ export const CompactEffectsListEditor = ({ const filteredEffectMetadata = layerRenderingType === '3d' ? all3DEffectMetadata : all2DEffectMetadata; + const effects = mapFor( + 0, + effectsContainer.getEffectsCount(), + (index: number) => { + const effect: gdEffect = effectsContainer.getEffectAt(index); + const effectType = effect.getEffectType(); + const effectMetadata = getEnumeratedEffectMetadata( + allEffectMetadata, + effectType + ); + return { effect, effectMetadata }; + } + ).filter( + ({ effectMetadata }) => + !effectMetadata || + (layerRenderingType !== '3d' && + !effectMetadata.isMarkedAsOnlyWorkingFor3D) || + (layerRenderingType !== '2d' && + !effectMetadata.isMarkedAsOnlyWorkingFor2D) + ); + return ( 2D effects ) } - isFolded={isEffectsFolded} - toggleFolded={() => setEffectsFolded(!isEffectsFolded)} + isFolded={isSectionFolded(layerRenderingType + '-effects')} + toggleFolded={() => toggleSectionFolded(layerRenderingType + '-effects')} onOpenFullEditor={onOpenFullEditor} - onAdd={() => addEffect(layerRenderingType === '3d')} + onAdd={() => { + addEffect(layerRenderingType === '3d'); + setSectionFolded(layerRenderingType + '-effects', false); + }} renderContent={() => ( - {effectsContainer.getEffectsCount() === 0 && ( + {effects.length === 0 && ( {target === 'object' ? ( @@ -166,86 +200,71 @@ export const CompactEffectsListEditor = ({ )} )} - {mapFor(0, effectsContainer.getEffectsCount(), (index: number) => { - const effect: gdEffect = effectsContainer.getEffectAt(index); - const effectType = effect.getEffectType(); - const effectMetadata = getEnumeratedEffectMetadata( - allEffectMetadata, - effectType - ); - - return !effectMetadata || - (layerRenderingType !== '3d' && - !effectMetadata.isMarkedAsOnlyWorkingFor3D) || - (layerRenderingType !== '2d' && - !effectMetadata.isMarkedAsOnlyWorkingFor2D) ? ( - ( - - chooseEffectType(effect, type)} - > - {filteredEffectMetadata.map(effectMetadata => ( - - ))} - - {effectMetadata && ( - Nothing to configure for this effect. + {effects.map(({ effect, effectMetadata }) => ( + ( + + chooseEffectType(effect, type)} + > + {filteredEffectMetadata.map(effectMetadata => ( + - )} - - )} - isFolded={effect.isFolded()} - toggleFolded={() => { - effect.setFolded(!effect.isFolded()); - forceUpdate(); - }} - title={effect.getName()} - titleBarButtons={[ - { - id: 'effect-visibility', - icon: effect.isEnabled() - ? VisibilityIcon - : VisibilityOffIcon, - label: effect.isEnabled() ? t`Hide effect` : t`Show effect`, - onClick: () => { - effect.setEnabled(!effect.isEnabled()); - onEffectsUpdated(); - forceUpdate(); - }, + ))} + + {effectMetadata && ( + Nothing to configure for this effect. + } + onRefreshAllFields={forceUpdate} + /> + )} + + )} + isFolded={effect.isFolded()} + toggleFolded={() => { + effect.setFolded(!effect.isFolded()); + forceUpdate(); + }} + title={effect.getName()} + titleBarButtons={[ + { + id: 'effect-visibility', + icon: effect.isEnabled() ? VisibilityIcon : VisibilityOffIcon, + label: effect.isEnabled() ? t`Hide effect` : t`Show effect`, + onClick: () => { + effect.setEnabled(!effect.isEnabled()); + onEffectsUpdated(); + forceUpdate(); }, - { - id: 'remove-effect', - icon: RemoveIcon, - label: t`Remove effect`, - onClick: () => { - removeEffect(effect); - onEffectsUpdated(); - }, + }, + { + id: 'remove-effect', + icon: RemoveIcon, + label: t`Remove effect`, + onClick: () => { + removeEffect(effect); + onEffectsUpdated(); }, - ]} - /> - ) : null; - })} + }, + ]} + /> + ))} )} /> diff --git a/newIDE/app/src/LayersList/CompactLayerPropertiesEditor/index.js b/newIDE/app/src/LayersList/CompactLayerPropertiesEditor/index.js index 856b05ce14db..2aa580480290 100644 --- a/newIDE/app/src/LayersList/CompactLayerPropertiesEditor/index.js +++ b/newIDE/app/src/LayersList/CompactLayerPropertiesEditor/index.js @@ -23,7 +23,7 @@ import { type Schema } from '../../PropertiesEditor/PropertiesEditorSchema'; import { CompactEffectsListEditor } from './CompactEffectsListEditor'; import { useForceRecompute } from '../../Utils/UseForceUpdate'; import { usePersistedScrollPosition } from '../../Utils/UsePersistedScrollPosition'; -import { TopLevelCollapsibleSection } from '../../ObjectEditor/CompactObjectPropertiesEditor'; +import { TopLevelCollapsibleSection } from '../../CompactPropertiesEditor/TopLevelCollapsibleSection'; export const styles = { icon: { @@ -95,14 +95,14 @@ export const CompactLayerPropertiesEditor = ({ // Layers have no persistent UUID, so the name is used (like for scenes). // The base layer has an empty name, so a placeholder is used instead. - const persistedScrollId = layer.getName() || 'base-layer'; + const persistedPanelStateId = layer.getName() || 'base-layer'; const onScroll = usePersistedScrollPosition({ project, scrollViewRef, scrollKey, - persistedScrollId, - persistedScrollType: 'layer', + persistedPanelStateId: persistedPanelStateId, + persistedPanelStateType: 'layer', }); const layerPropertiesSchema = React.useMemo( @@ -197,6 +197,7 @@ export const CompactLayerPropertiesEditor = ({ onEffectsUpdated={() => onLayersModified([layer])} onOpenFullEditor={() => onEditLayerEffects(layer)} onEffectAdded={onEffectAdded} + persistedPanelStateId={persistedPanelStateId} /> )} {layer.getRenderingType() !== '2d' && !layer.isLightingLayer() && ( @@ -212,6 +213,7 @@ export const CompactLayerPropertiesEditor = ({ onEffectsUpdated={() => onLayersModified([layer])} onOpenFullEditor={() => onEditLayerEffects(layer)} onEffectAdded={onEffectAdded} + persistedPanelStateId={persistedPanelStateId} /> )} diff --git a/newIDE/app/src/MainFrame/Preferences/PreferencesContext.js b/newIDE/app/src/MainFrame/Preferences/PreferencesContext.js index d6907770f3e8..b40883ee54ea 100644 --- a/newIDE/app/src/MainFrame/Preferences/PreferencesContext.js +++ b/newIDE/app/src/MainFrame/Preferences/PreferencesContext.js @@ -177,7 +177,14 @@ export const allAlertMessages: Array<{ */ export type EditorStateForProject = {| editorTabs: EditorTabsPersistedState | null, - propertiesPanelScroll: { [string]: { [string]: number } }, + propertiesPanel: { + [string]: { + [string]: { + scrollPosition: number, + collapsedSections: { [string]: boolean }, + }, + }, + }, |}; // $FlowFixMe[deprecated-utility] diff --git a/newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js b/newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js index 6720e3fa41fe..5635635ec9ee 100644 --- a/newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js +++ b/newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js @@ -1303,7 +1303,7 @@ export default class PreferencesProvider extends React.Component { return { editorTabs: editorState.editorTabs == null ? null : editorState.editorTabs, - propertiesPanelScroll: editorState.propertiesPanelScroll || {}, + propertiesPanel: editorState.propertiesPanel || {}, }; } @@ -1324,7 +1324,7 @@ export default class PreferencesProvider extends React.Component { projectId ] || { editorTabs: null, - propertiesPanelScroll: {}, + propertiesPanel: {}, }; const mergedEditorState: EditorStateForProject = { ...previousEditorState, diff --git a/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/index.js b/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/index.js index b3aa34ee8bb9..21cd63d27141 100644 --- a/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/index.js +++ b/newIDE/app/src/ObjectEditor/CompactObjectPropertiesEditor/index.js @@ -11,10 +11,8 @@ import { type ProjectScopedContainersAccessor } from '../../InstructionOrExpress import ErrorBoundary from '../../UI/ErrorBoundary'; import ScrollView, { type ScrollViewInterface } from '../../UI/ScrollView'; import { Column, Line, Spacer, marginsSize } from '../../UI/Grid'; -import { Separator } from '../../CompactPropertiesEditor'; import Text from '../../UI/Text'; import IconButton from '../../UI/IconButton'; -import ShareExternal from '../../UI/CustomSvgIcons/ShareExternal'; import EventsRootVariablesFinder from '../../Utils/EventsRootVariablesFinder'; import { type ObjectEditorTab } from '../../ObjectEditor/ObjectEditorDialog'; import CompactBehaviorsEditorService from './CompactBehaviorsEditorService'; @@ -26,8 +24,6 @@ import RemoveIcon from '../../UI/CustomSvgIcons/Remove'; import useForceUpdate from '../../Utils/UseForceUpdate'; import ChevronArrowRight from '../../UI/CustomSvgIcons/ChevronArrowRight'; import ChevronArrowBottom from '../../UI/CustomSvgIcons/ChevronArrowBottom'; -import ChevronArrowDownWithRoundedBorder from '../../UI/CustomSvgIcons/ChevronArrowDownWithRoundedBorder'; -import ChevronArrowRightWithRoundedBorder from '../../UI/CustomSvgIcons/ChevronArrowRightWithRoundedBorder'; import Add from '../../UI/CustomSvgIcons/Add'; import Trash from '../../UI/CustomSvgIcons/Trash'; import Edit from '../../UI/CustomSvgIcons/ShareExternal'; @@ -37,6 +33,7 @@ import Object3d from '../../UI/CustomSvgIcons/Object3d'; import Object2d from '../../UI/CustomSvgIcons/Object2d'; import { mapFor } from '../../Utils/MapFor'; import { usePersistedScrollPosition } from '../../Utils/UsePersistedScrollPosition'; +import { usePersistedCollapsedSection } from '../../Utils/UsePersistedCollapsedSection'; import CompactSelectField from '../../UI/CompactSelectField'; import SelectOption from '../../UI/SelectOption'; import { ChildObjectPropertiesEditor } from './ChildObjectPropertiesEditor'; @@ -67,6 +64,7 @@ import { type FieldChoices, } from '../../PropertiesEditor/PropertiesEditorSchema'; import useVariablesContainerRefactoring from '../../VariablesList/useVariablesContainerRefactoring'; +import { TopLevelCollapsibleSection } from '../../CompactPropertiesEditor/TopLevelCollapsibleSection'; const gd: libGDevelop = global.gd; @@ -165,67 +163,6 @@ export const CollapsibleSubPanel = ({ ); -export const TopLevelCollapsibleSection = ({ - title, - isFolded, - toggleFolded, - renderContent, - renderContentAsHiddenWhenFolded, - noContentMargin, - onOpenFullEditor, - onAdd, -}: {| - title: React.Node, - isFolded: boolean, - toggleFolded: () => void, - renderContent: () => React.Node, - renderContentAsHiddenWhenFolded?: boolean, - noContentMargin?: boolean, - onOpenFullEditor?: () => void, - onAdd?: (() => void) | null, -|}): React.Node => ( - <> - - - - - - {isFolded ? ( - - ) : ( - - )} - - - {title} - - - - {onOpenFullEditor && ( - - - - )} - {onAdd && ( - - - - )} - - - - - {isFolded ? ( - renderContentAsHiddenWhenFolded ? ( -
{renderContent()}
- ) : null - ) : ( - renderContent() - )} -
- -); - const resourcesPreloadingFieldChoices: Array = [ { value: 'with-scene', @@ -322,9 +259,6 @@ export const CompactObjectPropertiesEditor = ({ isBehaviorListLocked, }: Props): React.Node => { const forceUpdate = useForceUpdate(); - const [isPropertiesFolded, setIsPropertiesFolded] = React.useState(false); - const [isBehaviorsFolded, setIsBehaviorsFolded] = React.useState(false); - const [isVariablesFolded, setIsVariablesFolded] = React.useState(false); const [newVariantDialogOpen, setNewVariantDialogOpen] = React.useState(false); const [ duplicateAndEditVariantDialogOpen, @@ -540,14 +474,23 @@ export const CompactObjectPropertiesEditor = ({ .map((instance: gdObject) => '' + instance.ptr) .join(';'); - const persistedScrollId = object.getPersistentUuid(); + const persistedPanelStateId = object.getPersistentUuid(); const onScroll = usePersistedScrollPosition({ project, scrollViewRef, scrollKey, - persistedScrollId, - persistedScrollType: 'object', + persistedPanelStateId: persistedPanelStateId, + persistedPanelStateType: 'object', + }); + const { + isSectionFolded, + setSectionFolded, + toggleSectionFolded, + } = usePersistedCollapsedSection({ + project, + persistedPanelStateId: persistedPanelStateId, + persistedPanelStateType: 'object', }); // Variable refactoring: snapshot on object selection, apply on deselection/unmount. @@ -655,8 +598,8 @@ export const CompactObjectPropertiesEditor = ({ Properties} - isFolded={isPropertiesFolded} - toggleFolded={() => setIsPropertiesFolded(!isPropertiesFolded)} + isFolded={isSectionFolded('properties')} + toggleFolded={() => toggleSectionFolded('properties')} onOpenFullEditor={openFullEditor} renderContent={() => ( @@ -808,8 +751,8 @@ export const CompactObjectPropertiesEditor = ({ /> Behaviors} - isFolded={isBehaviorsFolded} - toggleFolded={() => setIsBehaviorsFolded(!isBehaviorsFolded)} + isFolded={isSectionFolded('behaviors')} + toggleFolded={() => toggleSectionFolded('behaviors')} onOpenFullEditor={() => onEditObject(object, 'behaviors')} onAdd={isBehaviorListLocked ? null : openNewBehaviorDialog} renderContent={() => ( @@ -891,8 +834,8 @@ export const CompactObjectPropertiesEditor = ({ {variablesContainer && ( Object Variables} - isFolded={isVariablesFolded} - toggleFolded={() => setIsVariablesFolded(!isVariablesFolded)} + isFolded={isSectionFolded('variables')} + toggleFolded={() => toggleSectionFolded('variables')} onOpenFullEditor={() => onEditObject(object, 'variables')} onAdd={ isVariableListLocked @@ -901,7 +844,7 @@ export const CompactObjectPropertiesEditor = ({ if (variablesListRef.current) { variablesListRef.current.addVariable(); } - setIsVariablesFolded(false); + setSectionFolded('variables', false); } } renderContentAsHiddenWhenFolded={ @@ -968,6 +911,7 @@ export const CompactObjectPropertiesEditor = ({ onEffectsUpdated={() => onObjectsModified([object])} onOpenFullEditor={() => onEditObject(object, 'effects')} onEffectAdded={onEffectAdded} + persistedPanelStateId={persistedPanelStateId} /> )} diff --git a/newIDE/app/src/ObjectGroupEditor/CompactObjectGroupPropertiesEditor.js b/newIDE/app/src/ObjectGroupEditor/CompactObjectGroupPropertiesEditor.js index 6ef7b771df99..d76e5b04c8ba 100644 --- a/newIDE/app/src/ObjectGroupEditor/CompactObjectGroupPropertiesEditor.js +++ b/newIDE/app/src/ObjectGroupEditor/CompactObjectGroupPropertiesEditor.js @@ -17,6 +17,7 @@ import { ColumnStackLayout, LineStackLayout } from '../UI/Layout'; import useForceUpdate from '../Utils/UseForceUpdate'; import ObjectGroup from '../UI/CustomSvgIcons/ObjectGroup'; import { usePersistedScrollPosition } from '../Utils/UsePersistedScrollPosition'; +import { usePersistedCollapsedSection } from '../Utils/UsePersistedCollapsedSection'; import Help from '../UI/CustomSvgIcons/Help'; import { getHelpLink } from '../Utils/HelpLink'; import Window from '../Utils/Window'; @@ -25,10 +26,8 @@ import Link from '../UI/Link'; import useVariablesContainerRefactoring from '../VariablesList/useVariablesContainerRefactoring'; import { type ObjectGroupEditorTab } from './EditedObjectGroupEditorDialog'; import CompactObjectGroupEditor from './CompactObjectGroupEditor'; -import { - TopLevelCollapsibleSection, - CollapsibleSubPanel, -} from '../ObjectEditor/CompactObjectPropertiesEditor'; +import { CollapsibleSubPanel } from '../ObjectEditor/CompactObjectPropertiesEditor'; +import { TopLevelCollapsibleSection } from '../CompactPropertiesEditor/TopLevelCollapsibleSection'; import { useManageObjectBehaviors } from '../BehaviorsEditor'; import CompactBehaviorsEditorService from '../ObjectEditor/CompactObjectPropertiesEditor/CompactBehaviorsEditorService'; import { IconContainer } from '../UI/IconContainer'; @@ -121,9 +120,6 @@ export const CompactObjectGroupPropertiesEditor = ({ }: Props): React.Node => { const forceUpdate = useForceUpdate(); const { isMobile } = useResponsiveWindowSize(); - const [isObjectsFolded, setIsObjectsFolded] = React.useState(false); - const [isVariablesFolded, setIsVariablesFolded] = React.useState(false); - const [isBehaviorsFolded, setIsBehaviorsFolded] = React.useState(false); const variablesListRef = React.useRef(null); const groupVariablesContainer = React.useMemo( @@ -147,14 +143,23 @@ export const CompactObjectGroupPropertiesEditor = ({ // Object groups have no persistent UUID, so the name is used // (like for scenes). - const persistedScrollId = objectGroup.getName(); + const persistedPanelStateId = objectGroup.getName(); const onScroll = usePersistedScrollPosition({ project, scrollViewRef, scrollKey, - persistedScrollId, - persistedScrollType: 'objectGroup', + persistedPanelStateId: persistedPanelStateId, + persistedPanelStateType: 'objectGroup', + }); + const { + isSectionFolded, + setSectionFolded, + toggleSectionFolded, + } = usePersistedCollapsedSection({ + project, + persistedPanelStateId, + persistedPanelStateType: 'objectGroup', }); const objects: Array = mapVector( @@ -310,8 +315,8 @@ export const CompactObjectGroupPropertiesEditor = ({ Objects} - isFolded={isObjectsFolded} - toggleFolded={() => setIsObjectsFolded(!isObjectsFolded)} + isFolded={isSectionFolded('objects')} + toggleFolded={() => toggleSectionFolded('objects')} onOpenFullEditor={openFullEditor} renderContent={() => ( @@ -334,8 +339,8 @@ export const CompactObjectGroupPropertiesEditor = ({ /> Behaviors} - isFolded={isBehaviorsFolded} - toggleFolded={() => setIsBehaviorsFolded(!isBehaviorsFolded)} + isFolded={isSectionFolded('behaviors')} + toggleFolded={() => toggleSectionFolded('behaviors')} onOpenFullEditor={undefined} onAdd={isBehaviorListLocked ? null : openNewBehaviorDialog} renderContent={() => ( @@ -417,8 +422,8 @@ export const CompactObjectGroupPropertiesEditor = ({ /> Object Variables} - isFolded={isVariablesFolded} - toggleFolded={() => setIsVariablesFolded(!isVariablesFolded)} + isFolded={isSectionFolded('variables')} + toggleFolded={() => toggleSectionFolded('variables')} onOpenFullEditor={() => onEditObjectGroup(objectGroup, 'variables') } @@ -429,7 +434,7 @@ export const CompactObjectGroupPropertiesEditor = ({ if (variablesListRef.current) { variablesListRef.current.addVariable(); } - setIsVariablesFolded(false); + setSectionFolded('variables', false); } } renderContentAsHiddenWhenFolded={ diff --git a/newIDE/app/src/SceneEditor/CompactScenePropertiesEditor/index.js b/newIDE/app/src/SceneEditor/CompactScenePropertiesEditor/index.js index d417544fa49e..cfb78f4b914b 100644 --- a/newIDE/app/src/SceneEditor/CompactScenePropertiesEditor/index.js +++ b/newIDE/app/src/SceneEditor/CompactScenePropertiesEditor/index.js @@ -15,16 +15,15 @@ import { Trans } from '@lingui/macro'; import IconButton from '../../UI/IconButton'; import EventsRootVariablesFinder from '../../Utils/EventsRootVariablesFinder'; import { CompactBehaviorSharedDataPropertiesEditor } from './CompactBehaviorSharedDataPropertiesEditor'; -import { - TopLevelCollapsibleSection, - CollapsibleSubPanel, -} from '../../ObjectEditor/CompactObjectPropertiesEditor'; +import { CollapsibleSubPanel } from '../../ObjectEditor/CompactObjectPropertiesEditor'; +import { TopLevelCollapsibleSection } from '../../CompactPropertiesEditor/TopLevelCollapsibleSection'; import { type ResourceManagementProps } from '../../ResourcesList/ResourceSource'; import { ColumnStackLayout, LineStackLayout } from '../../UI/Layout'; import { IconContainer } from '../../UI/IconContainer'; import useForceUpdate from '../../Utils/UseForceUpdate'; import SceneIcon from '../../UI/CustomSvgIcons/Scene'; import { usePersistedScrollPosition } from '../../Utils/UsePersistedScrollPosition'; +import { usePersistedCollapsedSection } from '../../Utils/UsePersistedCollapsedSection'; import Help from '../../UI/CustomSvgIcons/Help'; import { getHelpLink } from '../../Utils/HelpLink'; import Window from '../../Utils/Window'; @@ -90,9 +89,6 @@ export const CompactScenePropertiesEditor = ({ historyHandler, }: Props): React.Node => { const forceUpdate = useForceUpdate(); - const [isPropertiesFolded, setIsPropertiesFolded] = React.useState(false); - const [isBehaviorsFolded, setIsBehaviorsFolded] = React.useState(false); - const [isVariablesFolded, setIsVariablesFolded] = React.useState(false); const variablesListRef = React.useRef(null); const allVisibleBehaviors = scene @@ -113,14 +109,23 @@ export const CompactScenePropertiesEditor = ({ const scrollViewRef = React.useRef(null); const scrollKey = 'scene-' + scene.ptr; - const persistedScrollId = scene.getName(); + const persistedPanelStateId = scene.getName(); const onScroll = usePersistedScrollPosition({ project, scrollViewRef, scrollKey, - persistedScrollId, - persistedScrollType: 'scene', + persistedPanelStateId, + persistedPanelStateType: 'scene', + }); + const { + isSectionFolded, + setSectionFolded, + toggleSectionFolded, + } = usePersistedCollapsedSection({ + project, + persistedPanelStateId, + persistedPanelStateType: 'scene', }); // Variable refactoring: snapshot on mount, apply on unmount/scene change. @@ -188,8 +193,8 @@ export const CompactScenePropertiesEditor = ({ Properties} - isFolded={isPropertiesFolded} - toggleFolded={() => setIsPropertiesFolded(!isPropertiesFolded)} + isFolded={isSectionFolded('properties')} + toggleFolded={() => toggleSectionFolded('properties')} renderContent={() => ( 0 && ( Behaviors} - isFolded={isBehaviorsFolded} - toggleFolded={() => setIsBehaviorsFolded(!isBehaviorsFolded)} + isFolded={isSectionFolded('behaviors')} + toggleFolded={() => toggleSectionFolded('behaviors')} renderContent={() => ( {allVisibleBehaviors.map(behaviorSharedData => { @@ -271,14 +276,14 @@ export const CompactScenePropertiesEditor = ({ )} Scene Variables} - isFolded={isVariablesFolded} - toggleFolded={() => setIsVariablesFolded(!isVariablesFolded)} + isFolded={isSectionFolded('variables')} + toggleFolded={() => toggleSectionFolded('variables')} onOpenFullEditor={() => openSceneVariables()} onAdd={() => { if (variablesListRef.current) { variablesListRef.current.addVariable(); } - setIsVariablesFolded(false); + setSectionFolded('variables', false); }} renderContentAsHiddenWhenFolded={ true /* Allows to keep a ref to the variables list for add button to work. */ diff --git a/newIDE/app/src/Utils/UsePersistedCollapsedSection.js b/newIDE/app/src/Utils/UsePersistedCollapsedSection.js new file mode 100644 index 000000000000..622f79a3d997 --- /dev/null +++ b/newIDE/app/src/Utils/UsePersistedCollapsedSection.js @@ -0,0 +1,92 @@ +// @flow +import * as React from 'react'; +import PreferencesContext from '../MainFrame/Preferences/PreferencesContext'; + +type Props = {| + project: gdProject, + persistedPanelStateType: + | 'instances-of-object' + | 'object' + | 'scene' + | 'objectGroup' + | 'layer', + persistedPanelStateId: string | null, +|}; + +export const usePersistedCollapsedSection = ({ + project, + persistedPanelStateType, + persistedPanelStateId, +}: Props): { + isSectionFolded: (sectionId: string) => boolean, + setSectionFolded: (sectionId: string, isCollapsed: boolean) => void, + toggleSectionFolded: (sectionId: string) => void, +} => { + const { + getEditorStateForProject, + setEditorStateForProject, + } = React.useContext(PreferencesContext); + const projectId = project.getProjectUuid(); + + const isSectionFolded = React.useCallback( + (sectionId: string): boolean => { + const editorStateForProject = getEditorStateForProject(projectId); + if (!editorStateForProject || !persistedPanelStateId) return false; + + return editorStateForProject.propertiesPanel[persistedPanelStateType]?.[ + persistedPanelStateId + ]?.collapsedSections[sectionId]; + }, + [ + getEditorStateForProject, + persistedPanelStateId, + persistedPanelStateType, + projectId, + ] + ); + + const setSectionFolded = React.useCallback( + (sectionId: string, isCollapsed: boolean): void => { + if (!persistedPanelStateId) return; + + const currentEditorState = getEditorStateForProject(projectId); + setEditorStateForProject(projectId, { + propertiesPanel: { + ...currentEditorState?.propertiesPanel, + [persistedPanelStateType]: { + ...currentEditorState?.propertiesPanel[persistedPanelStateType], + [persistedPanelStateId]: { + ...currentEditorState?.propertiesPanel[persistedPanelStateType]?.[ + persistedPanelStateId + ], + collapsedSections: { + ...currentEditorState?.propertiesPanel[ + persistedPanelStateType + ]?.[persistedPanelStateId]?.collapsedSections, + [sectionId]: isCollapsed, + }, + }, + }, + }, + }); + }, + [ + getEditorStateForProject, + persistedPanelStateId, + persistedPanelStateType, + projectId, + setEditorStateForProject, + ] + ); + + const toggleSectionFolded = React.useCallback( + (sectionId: string) => + setSectionFolded(sectionId, !isSectionFolded(sectionId)), + [isSectionFolded, setSectionFolded] + ); + + return React.useMemo( + () => ({ isSectionFolded, setSectionFolded, toggleSectionFolded }), + [isSectionFolded, setSectionFolded, toggleSectionFolded] + ); +}; diff --git a/newIDE/app/src/Utils/UsePersistedScrollPosition.js b/newIDE/app/src/Utils/UsePersistedScrollPosition.js index 1a6d03771edf..2fe26a5e4d08 100644 --- a/newIDE/app/src/Utils/UsePersistedScrollPosition.js +++ b/newIDE/app/src/Utils/UsePersistedScrollPosition.js @@ -7,13 +7,13 @@ type Props = {| project: gdProject, scrollViewRef: {| current: ?ScrollViewInterface |}, scrollKey: string, - persistedScrollType: + persistedPanelStateType: | 'instances-of-object' | 'object' | 'scene' | 'objectGroup' | 'layer', - persistedScrollId: string | null, + persistedPanelStateId: string | null, saveDebounceTimeInMs?: number, |}; @@ -21,8 +21,8 @@ export const usePersistedScrollPosition = ({ project, scrollViewRef, scrollKey, - persistedScrollType, - persistedScrollId, + persistedPanelStateType, + persistedPanelStateId, saveDebounceTimeInMs = 300, }: Props): (() => void) => { const { @@ -45,16 +45,16 @@ export const usePersistedScrollPosition = ({ React.useLayoutEffect( () => { const scrollView = scrollViewRef.current; - if (!persistedScrollId || !scrollView) { + if (!persistedPanelStateId || !scrollView) { return; } const editorStateForProject = getEditorStateForProject(projectId); if (!editorStateForProject) return; const scrollPosition = - editorStateForProject.propertiesPanelScroll[persistedScrollType]?.[ - persistedScrollId - ]; + editorStateForProject.propertiesPanel[persistedPanelStateType]?.[ + persistedPanelStateId + ]?.scrollPosition; if (!Number.isFinite(scrollPosition)) { return; } @@ -63,18 +63,18 @@ export const usePersistedScrollPosition = ({ }, [ getEditorStateForProject, - persistedScrollId, - persistedScrollType, + persistedPanelStateId, + persistedPanelStateType, projectId, scrollKey, scrollViewRef, ] ); - return React.useCallback( + const onScroll = React.useCallback( () => { const scrollView = scrollViewRef.current; - if (!scrollView || !persistedScrollId) return; + if (!scrollView || !persistedPanelStateId) return; if (saveScrollTimeoutId.current) { clearTimeout(saveScrollTimeoutId.current); } @@ -83,11 +83,16 @@ export const usePersistedScrollPosition = ({ const currentEditorState = getEditorStateForProject(projectId); setEditorStateForProject(projectId, { - propertiesPanelScroll: { - ...currentEditorState?.propertiesPanelScroll, - [persistedScrollType]: { - ...currentEditorState?.propertiesPanelScroll[persistedScrollType], - [persistedScrollId]: scrollView.getScrollPosition(), + propertiesPanel: { + ...currentEditorState?.propertiesPanel, + [persistedPanelStateType]: { + ...currentEditorState?.propertiesPanel[persistedPanelStateType], + [persistedPanelStateId]: { + ...currentEditorState?.propertiesPanel[ + persistedPanelStateType + ]?.[persistedPanelStateId], + scrollPosition: scrollView.getScrollPosition(), + }, }, }, }); @@ -95,12 +100,14 @@ export const usePersistedScrollPosition = ({ }, [ getEditorStateForProject, - persistedScrollId, - persistedScrollType, + persistedPanelStateId, + persistedPanelStateType, projectId, saveDebounceTimeInMs, scrollViewRef, setEditorStateForProject, ] ); + + return onScroll; };