From 7382bf8ffbb4758e2a6acb3cdc79f841c4282e41 Mon Sep 17 00:00:00 2001
From: Alexander Platov <sas_lord@mail.ru>
Date: Tue, 31 Oct 2023 12:21:29 +0300
Subject: [PATCH] UBER-1116: saving sidebar changes (#3919)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
---
 packages/ui/src/location.ts                   | 13 +++
 .../sidebar/DepartmentsHierarchy.svelte       |  3 -
 .../src/components/sidebar/Sidebar.svelte     |  2 +-
 .../src/components/sidebar/TreeElement.svelte | 94 -------------------
 .../projects/ProjectSpacePresenter.svelte     | 13 ++-
 .../components/navigator/TreeElement.svelte   | 18 +++-
 .../src/components/navigator/TreeNode.svelte  |  2 +-
 .../src/components/SavedView.svelte           |  7 +-
 .../src/components/navigator/SpacesNav.svelte |  2 +-
 .../components/navigator/StarredNav.svelte    |  2 +-
 10 files changed, 44 insertions(+), 112 deletions(-)
 delete mode 100644 plugins/hr-resources/src/components/sidebar/TreeElement.svelte

diff --git a/packages/ui/src/location.ts b/packages/ui/src/location.ts
index d467f82bbb..8ca3aae3df 100644
--- a/packages/ui/src/location.ts
+++ b/packages/ui/src/location.ts
@@ -186,3 +186,16 @@ export function navigate (location: PlatformLocation, store = true): boolean {
   }
   return false
 }
+
+const COLLAPSED = 'COLLAPSED'
+export const getCollapsedKey = (_id: string): string => `${getCurrentLocation().path[1]}_${_id}_collapsed`
+
+export const getTreeCollapsed = (_id: any): boolean => {
+  if (_id === undefined || _id === 'undefined') return false
+  return localStorage.getItem(getCollapsedKey(_id as string)) === COLLAPSED
+}
+
+export const setTreeCollapsed = (_id: any, collapsed: boolean): void => {
+  if (_id === undefined || _id === 'undefined') return
+  localStorage.setItem(getCollapsedKey(_id), collapsed ? COLLAPSED : '')
+}
diff --git a/plugins/hr-resources/src/components/sidebar/DepartmentsHierarchy.svelte b/plugins/hr-resources/src/components/sidebar/DepartmentsHierarchy.svelte
index 84f16d87d8..d379aa1db0 100644
--- a/plugins/hr-resources/src/components/sidebar/DepartmentsHierarchy.svelte
+++ b/plugins/hr-resources/src/components/sidebar/DepartmentsHierarchy.svelte
@@ -20,11 +20,8 @@
   import { getClient } from '@hcengineering/presentation'
   import { Action, IconEdit } from '@hcengineering/ui'
   import { getActions as getContributedActions, TreeElement } from '@hcengineering/view-resources'
-
   import hr from '../../plugin'
 
-  // import TreeElement from './TreeElement.svelte'
-
   export let departments: Ref<Department>[]
   export let descendants: Map<Ref<Department>, Department[]>
   export let departmentById: Map<Ref<Department>, Department>
diff --git a/plugins/hr-resources/src/components/sidebar/Sidebar.svelte b/plugins/hr-resources/src/components/sidebar/Sidebar.svelte
index 3b096bbe6e..91cb5111e5 100644
--- a/plugins/hr-resources/src/components/sidebar/Sidebar.svelte
+++ b/plugins/hr-resources/src/components/sidebar/Sidebar.svelte
@@ -37,7 +37,7 @@
     <NavHeader label={hr.string.HRApplication} />
 
     <Scroller shrink>
-      <TreeNode label={hr.string.Departments} node>
+      <TreeNode _id={'tree-hr'} label={hr.string.Departments} node>
         <DepartmentsHierarchy {departments} {descendants} {departmentById} selected={department} on:selected />
       </TreeNode>
       <div class="antiNav-space" />
diff --git a/plugins/hr-resources/src/components/sidebar/TreeElement.svelte b/plugins/hr-resources/src/components/sidebar/TreeElement.svelte
deleted file mode 100644
index cad0250c70..0000000000
--- a/plugins/hr-resources/src/components/sidebar/TreeElement.svelte
+++ /dev/null
@@ -1,94 +0,0 @@
-<!--
-// Copyright © 2023 Hardcore Engineering Inc.
-// 
-// Licensed under the Eclipse Public 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 https://www.eclipse.org/legal/epl-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.
--->
-<script lang="ts">
-  import { createEventDispatcher } from 'svelte'
-  import { Doc, Ref } from '@hcengineering/core'
-  import type { Asset, IntlString } from '@hcengineering/platform'
-  import type { AnySvelteComponent, Action } from '@hcengineering/ui'
-  import { Icon, IconChevronDown, IconMoreH, Label, Menu, showPopup } from '@hcengineering/ui'
-
-  export let _id: Ref<Doc> | undefined = undefined
-  export let icon: Asset | AnySvelteComponent | undefined = undefined
-  export let iconProps: Record<string, any> | undefined = undefined
-  export let label: IntlString | undefined = undefined
-  export let title: string | undefined = undefined
-  export let node = false
-  export let parent = false
-  export let collapsed = false
-  export let selected = false
-  export let level = 0
-  export let actions: (originalEvent?: MouseEvent) => Promise<Action[]> = async () => []
-
-  let hovered = false
-  async function onMenuClick (ev: MouseEvent) {
-    showPopup(Menu, { actions: await actions(ev), ctx: _id }, ev.target as HTMLElement, () => {
-      hovered = false
-    })
-    hovered = true
-  }
-
-  $: style = `padding-left: calc(${level} * 1.25rem);`
-
-  const dispatch = createEventDispatcher()
-</script>
-
-<!-- svelte-ignore a11y-click-events-have-key-events -->
-<div
-  class="antiNav-element"
-  class:hovered
-  class:selected
-  class:parent
-  class:collapsed
-  {style}
-  on:click={() => {
-    if (selected) {
-      collapsed = !collapsed
-    }
-    dispatch('click')
-  }}
->
-  <span class="an-element__label" class:title={node}>
-    {#if icon && !parent}
-      <div class="an-element__icon">
-        <Icon {icon} {iconProps} size={'small'} />
-      </div>
-    {/if}
-    <span class="overflow-label">
-      {#if label}<Label {label} />{:else}{title}{/if}
-    </span>
-
-    {#if node}
-      <div
-        class="an-element__icon-arrow"
-        class:collapsed
-        on:click={(e) => {
-          e.stopPropagation()
-          e.preventDefault()
-          collapsed = !collapsed
-        }}
-      >
-        <IconChevronDown size={'small'} />
-      </div>
-    {/if}
-  </span>
-
-  <div class="an-element__tool" on:click|preventDefault|stopPropagation={onMenuClick}>
-    <IconMoreH size={'small'} />
-  </div>
-</div>
-
-{#if node && !collapsed}
-  <div class="antiNav-element__dropbox"><slot /></div>
-{/if}
diff --git a/plugins/tracker-resources/src/components/projects/ProjectSpacePresenter.svelte b/plugins/tracker-resources/src/components/projects/ProjectSpacePresenter.svelte
index 53ab74e589..c6dd6775c9 100644
--- a/plugins/tracker-resources/src/components/projects/ProjectSpacePresenter.svelte
+++ b/plugins/tracker-resources/src/components/projects/ProjectSpacePresenter.svelte
@@ -18,10 +18,11 @@
   import { Project } from '@hcengineering/tracker'
   import {
     IconWithEmoji,
-    getCurrentLocation,
     getPlatformColorDef,
     getPlatformColorForTextDef,
-    themeStore
+    themeStore,
+    getTreeCollapsed,
+    setTreeCollapsed
   } from '@hcengineering/ui'
   import view from '@hcengineering/view'
   import { NavLink, TreeNode } from '@hcengineering/view-resources'
@@ -35,10 +36,8 @@
   export let getActions: Function
   export let deselect: boolean = false
 
-  const COLLAPSED = 'COLLAPSED'
-  const getSpaceCollapsedKey = () => `${getCurrentLocation().path[1]}_${space._id}_collapsed`
-
-  $: collapsed = localStorage.getItem(getSpaceCollapsedKey()) === COLLAPSED
+  let collapsed: boolean = getTreeCollapsed(space._id)
+  $: setTreeCollapsed(space._id, collapsed)
 
   let specials: SpecialNavModel[] = []
 
@@ -78,7 +77,7 @@
     title={space.name}
     folder
     actions={() => getActions(space)}
-    on:click={() => localStorage.setItem(getSpaceCollapsedKey(), collapsed ? '' : COLLAPSED)}
+    on:click={() => (collapsed = !collapsed)}
   >
     {#each specials as special}
       <NavLink space={space._id} special={special.id}>
diff --git a/plugins/view-resources/src/components/navigator/TreeElement.svelte b/plugins/view-resources/src/components/navigator/TreeElement.svelte
index 9110f82715..fda7318c34 100644
--- a/plugins/view-resources/src/components/navigator/TreeElement.svelte
+++ b/plugins/view-resources/src/components/navigator/TreeElement.svelte
@@ -17,10 +17,20 @@
   import type { Doc, Ref } from '@hcengineering/core'
   import type { Asset, IntlString } from '@hcengineering/platform'
   import type { Action, AnySvelteComponent } from '@hcengineering/ui'
-  import { ActionIcon, Icon, IconChevronDown, IconMoreH, Label, Menu, showPopup } from '@hcengineering/ui'
+  import {
+    ActionIcon,
+    Icon,
+    IconChevronDown,
+    IconMoreH,
+    Label,
+    Menu,
+    showPopup,
+    getTreeCollapsed,
+    setTreeCollapsed
+  } from '@hcengineering/ui'
   import { createEventDispatcher } from 'svelte'
 
-  export let _id: Ref<Doc> | undefined = undefined
+  export let _id: Ref<Doc> | string | undefined = undefined
   export let icon: Asset | AnySvelteComponent | undefined = undefined
   export let iconProps: Record<string, any> | undefined = undefined
   export let label: IntlString | undefined = undefined
@@ -31,7 +41,7 @@
   export let indent: boolean = false
   export let folder: boolean = false
   export let level: number = 0
-  export let collapsed: boolean = false
+  export let collapsed: boolean = getTreeCollapsed(_id)
   export let selected: boolean = false
   export let bold: boolean = false
   export let actions: (originalEvent?: MouseEvent) => Promise<Action[]> = async () => []
@@ -45,6 +55,8 @@
   }
 
   const dispatch = createEventDispatcher()
+  $: if (_id) collapsed = getTreeCollapsed(_id)
+  $: setTreeCollapsed(_id, collapsed)
 </script>
 
 <!-- svelte-ignore a11y-click-events-have-key-events -->
diff --git a/plugins/view-resources/src/components/navigator/TreeNode.svelte b/plugins/view-resources/src/components/navigator/TreeNode.svelte
index 0df078fe4d..4e832c4536 100644
--- a/plugins/view-resources/src/components/navigator/TreeNode.svelte
+++ b/plugins/view-resources/src/components/navigator/TreeNode.svelte
@@ -18,7 +18,7 @@
   import type { Action, AnySvelteComponent } from '@hcengineering/ui'
   import TreeElement from './TreeElement.svelte'
 
-  export let _id: Ref<Doc> | undefined = undefined
+  export let _id: Ref<Doc> | string | undefined = undefined
   export let title: string | undefined = undefined
   export let label: IntlString | undefined = undefined
   export let icon: Asset | AnySvelteComponent | undefined = undefined
diff --git a/plugins/workbench-resources/src/components/SavedView.svelte b/plugins/workbench-resources/src/components/SavedView.svelte
index 4869b1fbcc..b862751bc2 100644
--- a/plugins/workbench-resources/src/components/SavedView.svelte
+++ b/plugins/workbench-resources/src/components/SavedView.svelte
@@ -203,7 +203,12 @@
 </script>
 
 {#if shown}
-  <TreeNode label={view.string.FilteredViews} node actions={async () => getActions(availableFilteredViews)}>
+  <TreeNode
+    _id={'tree-saved'}
+    label={view.string.FilteredViews}
+    node
+    actions={async () => getActions(availableFilteredViews)}
+  >
     {#each myFilteredViews as fv}
       <TreeItem
         _id={fv._id}
diff --git a/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte b/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte
index fbfc6e99a0..ae0461e112 100644
--- a/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte
+++ b/plugins/workbench-resources/src/components/navigator/SpacesNav.svelte
@@ -142,7 +142,7 @@
   }
 </script>
 
-<TreeNode label={model.label} node actions={async () => getParentActions()}>
+<TreeNode _id={'tree-' + model.id} label={model.label} node actions={async () => getParentActions()}>
   {#each filteredSpaces as space, i (space._id)}
     {#await getSpacePresenter(client, space._class) then presenter}
       {#if separate && model.specials && i !== 0}<TreeSeparator line />{/if}
diff --git a/plugins/workbench-resources/src/components/navigator/StarredNav.svelte b/plugins/workbench-resources/src/components/navigator/StarredNav.svelte
index aed21d9734..c4b2dbd39a 100644
--- a/plugins/workbench-resources/src/components/navigator/StarredNav.svelte
+++ b/plugins/workbench-resources/src/components/navigator/StarredNav.svelte
@@ -89,7 +89,7 @@
   }
 </script>
 
-<TreeNode {label} node actions={async () => [unStarAll]}>
+<TreeNode _id={'tree-stared'} {label} node actions={async () => [unStarAll]}>
   {#each spaces as space (space._id)}
     {@const model = models.find((p) => p.spaceClass === space._class)}
     {#await getSpacePresenter(client, space._class) then presenter}