diff --git a/plugins/workbench-resources/src/components/Workbench.svelte b/plugins/workbench-resources/src/components/Workbench.svelte index 6ea12f296d6..69bf753edf3 100644 --- a/plugins/workbench-resources/src/components/Workbench.svelte +++ b/plugins/workbench-resources/src/components/Workbench.svelte @@ -91,13 +91,6 @@ parseLinkId, updateFocus } from '@hcengineering/view-resources' - import type { - Application, - NavigatorModel, - SpecialNavModel, - ViewConfiguration, - WorkbenchTab - } from '@hcengineering/workbench' import { getContext, onDestroy, onMount, tick } from 'svelte' import { subscribeMobile } from '../mobile' import workbench from '../plugin' @@ -115,6 +108,15 @@ import TopMenu from './icons/TopMenu.svelte' import WidgetsBar from './sidebar/Sidebar.svelte' import { sidebarStore, SidebarVariant, syncSidebarState } from '../sidebar' + import { get } from 'svelte/store' + import type { + Application, + NavigatorModel, + SpecialNavModel, + ViewConfiguration, + WorkbenchTab + } from '@hcengineering/workbench' + import { getTabDataByLocation, getTabLocation, @@ -122,9 +124,9 @@ selectTab, syncWorkbenchTab, tabIdStore, - tabsStore + tabsStore, + updateTabUiState } from '../workbench' - import { get } from 'svelte/store' const HIDE_NAVIGATOR = 720 const FLOAT_ASIDE = 1024 // lg @@ -254,6 +256,111 @@ } } + let currentTabId: Ref | undefined = $tabIdStore + + function saveScrollState () { + if (!currentTabId || !contentPanel) { + return + } + + const allScrollers = contentPanel.querySelectorAll('.scroll') + if (allScrollers.length === 0) { + return + } + + console.log(`[SAVE] Tab ${currentTabId}. Found a total of ${allScrollers.length} elements with class .scroll.`) + + const scrollableElements = Array.from(allScrollers).filter((el) => el.scrollHeight > el.clientHeight) + + console.log(`[SAVE] Of these, ${scrollableElements.length} are actually scrollable.`) + + if (scrollableElements.length > 0) { + const scrollPositions: Record = {} + scrollableElements.forEach((scroller, index) => { + const id = `index-${index}` + const scrollTop = scroller.scrollTop + scrollPositions[id] = scrollTop + console.log(`[SAVE] -> Saving for element #${id} ORIGINAL scrollTop value: ${scrollTop}`) + }) + + console.log('[SAVE] Final object to save:', scrollPositions) + updateTabUiState(currentTabId, { scrollPositions }) + } + } + + async function restoreScrollState () { + const tabId = $tabIdStore + if (!tabId || !contentPanel) { + return + } + + console.log('[RESTORE] Waiting for two ticks for components to render...') + await tick() + await tick() + + const tab = get(tabsStore).find((t) => t._id === tabId) + const savedScrollPositions = tab?.uiState?.scrollPositions + + console.log(`[RESTORE] Tab ${tabId}. Found saved positions:`, savedScrollPositions) + + if (!savedScrollPositions) { + console.log('[RESTORE] Positions object is undefined, exiting.') + return + } + + if (Object.keys(savedScrollPositions).length === 0) { + console.log('[RESTORE] No saved positions (object is empty), exiting.') + return + } + + const finalPositions = savedScrollPositions + const expectedCount = Object.keys(finalPositions).length + let attempts = 0 + const maxAttempts = 100 + + function findAndApplyScroll () { + const allScrollers = contentPanel.querySelectorAll('.scroll') + const scrollableElements = Array.from(allScrollers).filter((el) => el.scrollHeight > el.clientHeight) + + if (scrollableElements.length === expectedCount) { + console.log(`[RESTORE] Success! Found ${scrollableElements.length} scrollable elements.`) + + scrollableElements.forEach((scroller, index) => { + const id = `index-${index}` + const savedScrollTop = finalPositions[id] + + if (savedScrollTop !== undefined) { + console.log(`[RESTORE] -> Direct assignment of scroller.scrollTop = ${savedScrollTop} for element #${id}`) + scroller.scrollTop = savedScrollTop + } + }) + return + } + + if (attempts < maxAttempts) { + attempts++ + requestAnimationFrame(findAndApplyScroll) + } else { + console.error(`[RESTORE] FAILED to wait for content to load in .scroll after ${maxAttempts} attempts.`) + } + } + + findAndApplyScroll() + } + + $: { + if (currentTabId !== $tabIdStore) { + console.log( + `%c[SWITCH] Tab changed. Old: ${currentTabId}, New: ${$tabIdStore}`, + 'color: blue; font-weight: bold;' + ) + + saveScrollState() + void restoreScrollState() + currentTabId = $tabIdStore + } + } + onMount(() => { pushRootBarComponent('right', view.component.SearchSelector) pushRootBarComponent('left', workbench.component.WorkbenchTabs, 30) diff --git a/plugins/workbench-resources/src/workbench.ts b/plugins/workbench-resources/src/workbench.ts index db145d44520..2102c86db29 100644 --- a/plugins/workbench-resources/src/workbench.ts +++ b/plugins/workbench-resources/src/workbench.ts @@ -37,7 +37,7 @@ import { } from '@hcengineering/ui' import view from '@hcengineering/view' import { parseLinkId } from '@hcengineering/view-resources' -import { type Application, workbenchId, type WorkbenchTab } from '@hcengineering/workbench' +import { type Application, workbenchId, type WorkbenchTab, type TabUiState } from '@hcengineering/workbench' import { derived, get, writable } from 'svelte/store' import setting from '@hcengineering/setting' @@ -62,6 +62,19 @@ locationWorkspaceStore.subscribe((workspace) => { tabIdStore.set(getTabFromLocalStorage(workspace ?? '')) }) +export function updateTabUiState (tabId: Ref, state: Partial): void { + tabsStore.update((tabs) => { + const tab = tabs.find((t) => t._id === tabId) + if (tab != null) { + tab.uiState = { + ...(tab.uiState ?? {}), + ...state + } + } + return tabs + }) +} + const syncTabLoc = reduceCalls(async (): Promise => { const loc = getCurrentLocation() const workspace = get(locationWorkspaceStore) diff --git a/plugins/workbench/src/types.ts b/plugins/workbench/src/types.ts index 364f12fed2a..19194eb0e0d 100644 --- a/plugins/workbench/src/types.ts +++ b/plugins/workbench/src/types.ts @@ -99,12 +99,18 @@ export interface WidgetTab { readonly?: boolean } +/** @public */ +export interface TabUiState { + scrollPositions?: Record +} + /** @public */ export interface WorkbenchTab extends Preference { attachedTo: AccountUuid location: string isPinned: boolean name?: string + uiState?: TabUiState } /** @public */