diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index e48e1d5dc2..f0a0605584 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -20,6 +20,7 @@ import {Room} from "matrix-js-sdk/src/models/room"; import RoomAvatar from "../avatars/RoomAvatar"; import SpaceStore from "../../../stores/SpaceStore"; +import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore"; import NotificationBadge from "../rooms/NotificationBadge"; import {RovingAccessibleButton} from "../../../accessibility/roving/RovingAccessibleButton"; import {RovingAccessibleTooltipButton} from "../../../accessibility/roving/RovingAccessibleTooltipButton"; @@ -68,8 +69,14 @@ export class SpaceItem extends React.PureComponent { constructor(props) { super(props); + const collapsed = SpaceTreeLevelLayoutStore.instance.getSpaceCollapsedState( + props.space.roomId, + this.props.parents, + !props.isNested, // default to collapsed for root items + ); + this.state = { - collapsed: !props.isNested, // default to collapsed for root items + collapsed: collapsed, contextMenuPosition: null, }; } @@ -78,7 +85,14 @@ export class SpaceItem extends React.PureComponent { if (this.props.onExpand && this.state.collapsed) { this.props.onExpand(); } - this.setState({collapsed: !this.state.collapsed}); + const newCollapsedState = !this.state.collapsed; + + SpaceTreeLevelLayoutStore.instance.setSpaceCollapsedState( + this.props.space.roomId, + this.props.parents, + newCollapsedState, + ); + this.setState({collapsed: newCollapsedState}); // don't bubble up so encapsulating button for space // doesn't get triggered evt.stopPropagation(); diff --git a/src/stores/SpaceTreeLevelLayoutStore.ts b/src/stores/SpaceTreeLevelLayoutStore.ts new file mode 100644 index 0000000000..424e9f4012 --- /dev/null +++ b/src/stores/SpaceTreeLevelLayoutStore.ts @@ -0,0 +1,48 @@ +/* +Copyright 2021 Šimon Brandner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +const getSpaceCollapsedKey = (roomId: string, parents: Set): string => { + const separator = "/"; + let path = ""; + if (parents) { + for (const entry of parents.entries()) { + path += entry + separator; + } + } + return `mx_space_collapsed_${path + roomId}`; +}; + +export default class SpaceTreeLevelLayoutStore { + private static internalInstance: SpaceTreeLevelLayoutStore; + + public static get instance(): SpaceTreeLevelLayoutStore { + if (!SpaceTreeLevelLayoutStore.internalInstance) { + SpaceTreeLevelLayoutStore.internalInstance = new SpaceTreeLevelLayoutStore(); + } + return SpaceTreeLevelLayoutStore.internalInstance; + } + + public setSpaceCollapsedState(roomId: string, parents: Set, collapsed: boolean) { + // XXX: localStorage doesn't allow booleans + localStorage.setItem(getSpaceCollapsedKey(roomId, parents), collapsed.toString()); + } + + public getSpaceCollapsedState(roomId: string, parents: Set, fallback: boolean): boolean { + const collapsedLocalStorage = localStorage.getItem(getSpaceCollapsedKey(roomId, parents)); + // XXX: localStorage doesn't allow booleans + return collapsedLocalStorage ? collapsedLocalStorage === "true" : fallback; + } +}