|
91 | 91 | parseLinkId,
|
92 | 92 | updateFocus
|
93 | 93 | } from '@hcengineering/view-resources'
|
94 |
| - import type { |
95 |
| - Application, |
96 |
| - NavigatorModel, |
97 |
| - SpecialNavModel, |
98 |
| - ViewConfiguration, |
99 |
| - WorkbenchTab |
100 |
| - } from '@hcengineering/workbench' |
101 | 94 | import { getContext, onDestroy, onMount, tick } from 'svelte'
|
102 | 95 | import { subscribeMobile } from '../mobile'
|
103 | 96 | import workbench from '../plugin'
|
|
115 | 108 | import TopMenu from './icons/TopMenu.svelte'
|
116 | 109 | import WidgetsBar from './sidebar/Sidebar.svelte'
|
117 | 110 | import { sidebarStore, SidebarVariant, syncSidebarState } from '../sidebar'
|
| 111 | + import { get } from 'svelte/store' |
| 112 | + import type { Application, NavigatorModel, SpecialNavModel, ViewConfiguration, WorkbenchTab} from '@hcengineering/workbench'; |
| 113 | +
|
118 | 114 | import {
|
119 | 115 | getTabDataByLocation,
|
120 | 116 | getTabLocation,
|
121 | 117 | prevTabIdStore,
|
122 | 118 | selectTab,
|
123 | 119 | syncWorkbenchTab,
|
124 | 120 | tabIdStore,
|
125 |
| - tabsStore |
126 |
| - } from '../workbench' |
127 |
| - import { get } from 'svelte/store' |
| 121 | + tabsStore, |
| 122 | + updateTabUiState |
| 123 | + } from '../workbench'; |
128 | 124 |
|
129 | 125 | const HIDE_NAVIGATOR = 720
|
130 | 126 | const FLOAT_ASIDE = 1024 // lg
|
|
254 | 250 | }
|
255 | 251 | }
|
256 | 252 |
|
| 253 | +let currentTabId: Ref<WorkbenchTab> | undefined = $tabIdStore; |
| 254 | +
|
| 255 | +function saveScrollState() { |
| 256 | + if (!currentTabId || !contentPanel) { |
| 257 | + return; |
| 258 | + } |
| 259 | +
|
| 260 | + const allScrollers = contentPanel.querySelectorAll<HTMLElement>('.scroll'); |
| 261 | + if (allScrollers.length === 0) { |
| 262 | + return; |
| 263 | + } |
| 264 | + |
| 265 | + console.log(`[SAVE] Tab ${currentTabId}. Found a total of ${allScrollers.length} elements with class .scroll.`); |
| 266 | +
|
| 267 | + const scrollableElements = Array.from(allScrollers).filter( |
| 268 | + (el) => el.scrollHeight > el.clientHeight |
| 269 | + ); |
| 270 | + |
| 271 | + console.log(`[SAVE] Of these, ${scrollableElements.length} are actually scrollable.`); |
| 272 | +
|
| 273 | + if (scrollableElements.length > 0) { |
| 274 | + const scrollPositions: Record<string, number> = {}; |
| 275 | + scrollableElements.forEach((scroller, index) => { |
| 276 | + const id = `index-${index}`; |
| 277 | + const scrollTop = scroller.scrollTop; |
| 278 | + scrollPositions[id] = scrollTop; |
| 279 | + console.log(`[SAVE] -> Saving for element #${id} ORIGINAL scrollTop value: ${scrollTop}`); |
| 280 | + }); |
| 281 | +
|
| 282 | + console.log('[SAVE] Final object to save:', scrollPositions); |
| 283 | + updateTabUiState(currentTabId, { scrollPositions }); |
| 284 | + } |
| 285 | +} |
| 286 | +
|
| 287 | +async function restoreScrollState() { |
| 288 | + const tabId = $tabIdStore; |
| 289 | + if (!tabId || !contentPanel) { |
| 290 | + return; |
| 291 | + } |
| 292 | +
|
| 293 | + console.log('[RESTORE] Waiting for two ticks for components to render...'); |
| 294 | + await tick(); |
| 295 | + await tick(); |
| 296 | +
|
| 297 | + const tab = get(tabsStore).find((t) => t._id === tabId); |
| 298 | + const savedScrollPositions = tab?.uiState?.scrollPositions; |
| 299 | +
|
| 300 | + console.log(`[RESTORE] Tab ${tabId}. Found saved positions:`, savedScrollPositions); |
| 301 | +
|
| 302 | + if (!savedScrollPositions) { |
| 303 | + console.log('[RESTORE] Positions object is undefined, exiting.'); |
| 304 | + return; |
| 305 | + } |
| 306 | +
|
| 307 | + if (Object.keys(savedScrollPositions).length === 0) { |
| 308 | + console.log('[RESTORE] No saved positions (object is empty), exiting.'); |
| 309 | + return; |
| 310 | + } |
| 311 | + |
| 312 | + const finalPositions = savedScrollPositions; |
| 313 | + const expectedCount = Object.keys(finalPositions).length; |
| 314 | + let attempts = 0; |
| 315 | + const maxAttempts = 100; |
| 316 | +
|
| 317 | + function findAndApplyScroll() { |
| 318 | + const allScrollers = contentPanel.querySelectorAll<HTMLElement>('.scroll'); |
| 319 | + const scrollableElements = Array.from(allScrollers).filter( |
| 320 | + (el) => el.scrollHeight > el.clientHeight |
| 321 | + ); |
| 322 | +
|
| 323 | + if (scrollableElements.length === expectedCount) { |
| 324 | + console.log(`[RESTORE] Success! Found ${scrollableElements.length} scrollable elements.`); |
| 325 | + |
| 326 | + scrollableElements.forEach((scroller, index) => { |
| 327 | + const id = `index-${index}`; |
| 328 | + const savedScrollTop = finalPositions[id]; |
| 329 | +
|
| 330 | + if (savedScrollTop !== undefined) { |
| 331 | + console.log(`[RESTORE] -> Direct assignment of scroller.scrollTop = ${savedScrollTop} for element #${id}`); |
| 332 | + scroller.scrollTop = savedScrollTop; |
| 333 | + } |
| 334 | + }); |
| 335 | + return; |
| 336 | + } |
| 337 | + |
| 338 | + if (attempts < maxAttempts) { |
| 339 | + attempts++; |
| 340 | + requestAnimationFrame(findAndApplyScroll); |
| 341 | + } else { |
| 342 | + console.error(`[RESTORE] FAILED to wait for content to load in .scroll after ${maxAttempts} attempts.`); |
| 343 | + } |
| 344 | + } |
| 345 | +
|
| 346 | + findAndApplyScroll(); |
| 347 | +} |
| 348 | +
|
| 349 | +$: { |
| 350 | + if (currentTabId !== $tabIdStore) { |
| 351 | + console.log(`%c[SWITCH] Tab changed. Old: ${currentTabId}, New: ${$tabIdStore}`, 'color: blue; font-weight: bold;'); |
| 352 | +
|
| 353 | + saveScrollState(); |
| 354 | + void restoreScrollState(); |
| 355 | + currentTabId = $tabIdStore; |
| 356 | + } |
| 357 | +} |
| 358 | +
|
257 | 359 | onMount(() => {
|
258 | 360 | pushRootBarComponent('right', view.component.SearchSelector)
|
259 | 361 | pushRootBarComponent('left', workbench.component.WorkbenchTabs, 30)
|
|
996 | 1098 | !(mobileAdaptive && $deviceInfo.isPortrait)}
|
997 | 1099 | data-id={'contentPanel'}
|
998 | 1100 | >
|
999 |
| - {#if currentApplication && currentApplication.component} |
1000 |
| - <Component |
1001 |
| - is={currentApplication.component} |
1002 |
| - props={{ |
1003 |
| - currentSpace, |
1004 |
| - workbenchWidth |
1005 |
| - }} |
1006 |
| - /> |
1007 |
| - {:else if specialComponent} |
1008 |
| - <Component |
1009 |
| - is={specialComponent.component} |
1010 |
| - props={{ |
1011 |
| - model: navigatorModel, |
1012 |
| - ...specialComponent.componentProps, |
1013 |
| - currentSpace, |
1014 |
| - space: currentSpace, |
1015 |
| - navigationModel: specialComponent?.navigationModel, |
1016 |
| - workbenchWidth, |
1017 |
| - queryBuilder: specialComponent?.queryBuilder |
1018 |
| - }} |
1019 |
| - on:action={(e) => { |
1020 |
| - if (e?.detail) { |
1021 |
| - const loc = getCurrentLocation() |
1022 |
| - loc.query = { ...loc.query, ...e.detail } |
1023 |
| - navigate(loc) |
1024 |
| - } |
1025 |
| - }} |
1026 |
| - /> |
1027 |
| - {:else if currentView?.component !== undefined} |
1028 |
| - <Component |
1029 |
| - is={currentView.component} |
| 1101 | + {#if currentApplication && currentApplication.component} |
| 1102 | + <Component |
| 1103 | + is={currentApplication.component} |
| 1104 | + props={{ |
| 1105 | + currentSpace, |
| 1106 | + workbenchWidth |
| 1107 | + }} |
| 1108 | + /> |
| 1109 | + {:else if specialComponent} |
| 1110 | + <Component |
| 1111 | + is={specialComponent.component} |
| 1112 | + props={{ |
| 1113 | + model: navigatorModel, |
| 1114 | + ...specialComponent.componentProps, |
| 1115 | + currentSpace, |
| 1116 | + space: currentSpace, |
| 1117 | + navigationModel: specialComponent?.navigationModel, |
| 1118 | + workbenchWidth, |
| 1119 | + queryBuilder: specialComponent?.queryBuilder |
| 1120 | + }} |
| 1121 | + on:action={(e) => { |
| 1122 | + if (e?.detail) { |
| 1123 | + const loc = getCurrentLocation() |
| 1124 | + loc.query = { ...loc.query, ...e.detail } |
| 1125 | + navigate(loc) |
| 1126 | + } |
| 1127 | + }} |
| 1128 | + /> |
| 1129 | + {:else if currentView?.component !== undefined} |
| 1130 | + <Component |
| 1131 | + is={currentView.component} |
1030 | 1132 | props={{ ...currentView.componentProps, currentSpace, currentView, workbenchWidth }}
|
1031 |
| - /> |
1032 |
| - {:else if $accessDeniedStore} |
1033 |
| - <div class="flex-center h-full"> |
1034 |
| - <h2><Label label={workbench.string.AccessDenied} /></h2> |
1035 |
| - </div> |
1036 |
| - {:else} |
1037 |
| - <SpaceView {currentSpace} {currentView} {createItemDialog} {createItemLabel} /> |
1038 |
| - {/if} |
| 1133 | + /> |
| 1134 | + {:else if $accessDeniedStore} |
| 1135 | + <div class="flex-center h-full"> |
| 1136 | + <h2><Label label={workbench.string.AccessDenied} /></h2> |
| 1137 | + </div> |
| 1138 | + {:else} |
| 1139 | + <SpaceView {currentSpace} {currentView} {createItemDialog} {createItemLabel} /> |
| 1140 | + {/if} |
1039 | 1141 | </div>
|
1040 | 1142 | </div>
|
1041 | 1143 | {#if $sidebarStore.variant === SidebarVariant.EXPANDED && !$sidebarStore.float}
|
|
0 commit comments