Skip to content

Commit 629a170

Browse files
feat: implement separated filters
Signed-off-by: Rostislav Nagimov <[email protected]>
1 parent ee5c8d9 commit 629a170

File tree

6 files changed

+190
-22
lines changed

6 files changed

+190
-22
lines changed

plugins/tracker-resources/src/components/issues/IssuesView.svelte

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
import { FilterBar, SpaceHeader, ViewletContentView, ViewletSettingButton } from '@hcengineering/view-resources'
99
import tracker from '../../plugin'
1010
import CreateIssue from '../CreateIssue.svelte'
11+
import { get } from 'svelte/store'
12+
import { onMount, onDestroy, tick } from 'svelte'
13+
import type { WorkbenchTab, TabUiState } from '@hcengineering/workbench'
14+
import { tabIdStore, tabsStore, updateTabUiState } from '@hcengineering/workbench-resources/src/workbench'
1115
1216
export let space: Ref<Space> | undefined = undefined
1317
export let query: DocumentQuery<Issue> = {}
@@ -17,9 +21,12 @@
1721
export let modeSelectorProps: IModeSelector | undefined = undefined
1822
1923
let viewlet: WithLookup<Viewlet> | undefined = undefined
20-
const viewlets: WithLookup<Viewlet>[] | undefined = undefined
24+
let viewlets: Array<WithLookup<Viewlet>> | undefined = undefined;
2125
let viewOptions: ViewOptions | undefined
2226
27+
let currentTabId: Ref<WorkbenchTab> | undefined = get(tabIdStore)
28+
let indicatorElement: HTMLElement | null = null;
29+
2330
let search = ''
2431
let searchQuery: DocumentQuery<Issue> = { ...query }
2532
function updateSearchQuery (search: string): void {
@@ -28,6 +35,124 @@
2835
$: if (query) updateSearchQuery(search)
2936
let resultQuery: DocumentQuery<Issue> = { ...searchQuery }
3037
38+
function saveTrackerIssueState (): void {
39+
console.log('[DEBUG_STATE_ISSUES] Save State: Starting for tab', currentTabId)
40+
if (!currentTabId) {
41+
console.log('[DEBUG_STATE_ISSUES] Save State: No currentTabId, skipping.')
42+
return
43+
}
44+
updateTabUiState(currentTabId, {
45+
viewletId: viewlet?._id,
46+
filterBarState: resultQuery
47+
})
48+
console.log('[DEBUG_STATE_ISSUES] Save State: Saved. Viewlet ID:', viewlet?._id, 'Filter State:', resultQuery)
49+
}
50+
51+
async function restoreTrackerIssueState (): Promise<void> {
52+
console.log('[DEBUG_STATE_ISSUES] Restore State: Starting for tab', get(tabIdStore))
53+
const tabId = get(tabIdStore)
54+
if (!tabId) {
55+
console.log('[DEBUG_STATE_ISSUES] Restore State: No tabId, skipping.')
56+
return
57+
}
58+
console.log('[DEBUG_STATE_ISSUES] Restore State: Awaiting ticks for component render.')
59+
await tick()
60+
await tick()
61+
console.log('[DEBUG_STATE_ISSUES] Restore State: Ticks complete, searching tab state.')
62+
const tab = get(tabsStore).find((t) => t._id === tabId)
63+
if (tab?.uiState) {
64+
console.log('[DEBUG_STATE_ISSUES] Restore State: uiState found.', tab.uiState)
65+
const savedViewletId = tab.uiState.viewletId
66+
const savedFilterBarQuery = tab.uiState.filterBarState
67+
console.log(
68+
'[DEBUG_STATE_ISSUES] Restore State: Found saved Viewlet ID:',
69+
savedViewletId,
70+
'Saved Filter State:',
71+
savedFilterBarQuery
72+
)
73+
74+
if (savedViewletId && viewlets && viewlets.length > 0) {
75+
const foundViewlet = viewlets.find((v) => v._id === savedViewletId)
76+
if (foundViewlet) {
77+
viewlet = foundViewlet
78+
console.log('[DEBUG_STATE_ISSUES] Restore State: Viewlet restored to ID:', viewlet?._id)
79+
} else {
80+
viewlet = viewlets[0]
81+
console.log(
82+
'[DEBUG_STATE_ISSUES] Restore State: Saved Viewlet not found, set to first available:',
83+
viewlet?._id
84+
)
85+
}
86+
} else {
87+
console.log('[DEBUG_STATE_ISSUES] Restore State: No saved Viewlet ID or viewlets not ready/empty.')
88+
}
89+
if (savedFilterBarQuery) {
90+
resultQuery = { ...searchQuery, ...savedFilterBarQuery }
91+
console.log('[DEBUG_STATE_ISSUES] Restore State: FilterBar query restored:', resultQuery)
92+
} else {
93+
resultQuery = { ...searchQuery }
94+
console.log('[DEBUG_STATE_ISSUES] Restore State: No saved FilterBar query, set to initial:', resultQuery)
95+
}
96+
} else {
97+
resultQuery = { ...searchQuery }
98+
console.log('[DEBUG_STATE_ISSUES] Restore State: No uiState found, set FilterBar to initial:', resultQuery)
99+
}
100+
console.log('[DEBUG_STATE_ISSUES] Restore State: Finished.')
101+
}
102+
103+
$: {
104+
console.log('[DEBUG_STATE_ISSUES] Reactive: Tab ID change detection triggered.')
105+
const newTabId = get(tabIdStore)
106+
if (currentTabId !== newTabId) {
107+
console.log('[DEBUG_STATE_ISSUES] Reactive: Tab changed from', currentTabId, 'to', newTabId)
108+
if (currentTabId) {
109+
console.log('[DEBUG_STATE_ISSUES] Reactive: Saving state for previous tab', currentTabId)
110+
saveTrackerIssueState()
111+
}
112+
currentTabId = newTabId
113+
console.log('[DEBUG_STATE_ISSUES] Reactive: Restoring state for new tab', currentTabId)
114+
void restoreTrackerIssueState()
115+
} else {
116+
console.log('[DEBUG_STATE_ISSUES] Reactive: Tab ID unchanged, skip full tab switch logic.')
117+
}
118+
}
119+
120+
$: {
121+
console.log('[DEBUG_STATE_ISSUES] Reactive: Viewlet change detection triggered.')
122+
if (currentTabId && viewlet) {
123+
console.log(
124+
'[DEBUG_STATE_ISSUES] Reactive: Viewlet changed or initialized, saving state. Viewlet ID:',
125+
viewlet._id
126+
)
127+
saveTrackerIssueState()
128+
} else {
129+
console.log('[DEBUG_STATE_ISSUES] Reactive: Viewlet or currentTabId not ready for saving Viewlet state.')
130+
}
131+
}
132+
133+
$: {
134+
console.log('[DEBUG_STATE_ISSUES] Reactive: resultQuery change detection triggered.')
135+
if (currentTabId && resultQuery) {
136+
console.log(
137+
'[DEBUG_STATE_ISSUES] Reactive: resultQuery changed or initialized, saving state. Filters:',
138+
resultQuery
139+
)
140+
saveTrackerIssueState()
141+
} else {
142+
console.log('[DEBUG_STATE_ISSUES] Reactive: resultQuery or currentTabId not ready for saving FilterBar state.')
143+
}
144+
}
145+
146+
onMount(async () => {
147+
console.log('[DEBUG_STATE_ISSUES] Lifecycle: onMount. Restoring state.')
148+
void restoreTrackerIssueState()
149+
})
150+
151+
onDestroy(() => {
152+
console.log('[DEBUG_STATE_ISSUES] Lifecycle: onDestroy. Saving final state.')
153+
saveTrackerIssueState()
154+
})
155+
31156
$: if (title) {
32157
translateCB(title, {}, $themeStore.language, (res) => {
33158
label = res
@@ -46,6 +171,7 @@
46171
{label}
47172
{space}
48173
{modeSelectorProps}
174+
currentTabId={currentTabId}
49175
>
50176
<svelte:fragment slot="header-tools">
51177
<ViewletSettingButton bind:viewOptions bind:viewlet />

plugins/view-resources/src/components/SpaceHeader.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import { Viewlet } from '@hcengineering/view'
66
import ViewletSelector from './ViewletSelector.svelte'
77
import FilterButton from './filter/FilterButton.svelte'
8+
import { WorkbenchTab } from '../../../workbench/types'
89
910
export let space: Ref<Space> | undefined = undefined
1011
export let _class: Ref<Class<Doc>>
@@ -17,6 +18,7 @@
1718
export let showLabelSelector = false
1819
export let modeSelectorProps: IModeSelector | undefined = undefined
1920
export let adaptive: HeaderAdaptive = 'doubleRow'
21+
export let currentTabId: Ref<WorkbenchTab> | undefined = undefined;
2022
2123
let scroller: HTMLElement
2224
</script>
@@ -45,7 +47,7 @@
4547

4648
<svelte:fragment slot="search">
4749
<SearchInput bind:value={search} collapsed />
48-
<FilterButton {_class} {space} />
50+
<FilterButton {_class} {space} currentTabId={currentTabId}/>
4951
</svelte:fragment>
5052
<svelte:fragment slot="actions">
5153
<slot name="actions" />

plugins/view-resources/src/components/filter/FilterButton.svelte

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
import view from '../../plugin'
2222
import FilterTypePopup from './FilterTypePopup.svelte'
2323
import IconClose from '../icons/Close.svelte'
24-
import { onDestroy } from 'svelte'
24+
import { onDestroy, onMount } from 'svelte'
25+
import { WorkbenchTab } from '../../../../workbench/types'
2526
2627
export let _class: Ref<Class<Doc>> | undefined
2728
export let space: Ref<Space> | undefined = undefined
2829
export let viewOptions: ViewOptions | undefined = undefined
2930
export let adaptive: boolean = false
31+
export let currentTabId: Ref<WorkbenchTab> | undefined = undefined
3032
3133
const client = getClient()
3234
const hierarchy = client.getHierarchy()
@@ -37,18 +39,18 @@
3739
}
3840
})
3941
40-
function load (_class: Ref<Class<Doc>> | undefined): void {
41-
const key = getFilterKey(_class)
42-
const items = localStorage.getItem(key)
43-
if (items !== null) {
44-
filterStore.set(JSON.parse(items))
45-
}
42+
function load (_class: Ref<Class<Doc>> | undefined): void {
43+
const key = getFilterKey(_class, currentTabId);
44+
const items = localStorage.getItem(key);
45+
if (items !== null) {
46+
filterStore.set(JSON.parse(items));
4647
}
48+
}
4749
48-
function save (_class: Ref<Class<Doc>> | undefined, p: Filter[]): void {
49-
const key = getFilterKey(_class)
50-
localStorage.setItem(key, JSON.stringify(p))
51-
}
50+
function save (_class: Ref<Class<Doc>> | undefined, p: Filter[]): void {
51+
const key = getFilterKey(_class, currentTabId);
52+
localStorage.setItem(key, JSON.stringify(p));
53+
}
5254
5355
filterStore.subscribe((p) => {
5456
save(_class, p)
@@ -84,6 +86,13 @@
8486
visible = hierarchy.classHierarchyMixin(_class, view.mixin.ClassFilters) !== undefined
8587
} else visible = false
8688
}
89+
90+
onMount(() => {
91+
load(_class);
92+
});
93+
$: if (currentTabId) {
94+
load(_class);
95+
}
8796
</script>
8897

8998
{#if visible}

plugins/view-resources/src/filter.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { getCurrentResolvedLocation, locationToUrl, type AnyComponent } from '@h
1616
import { type Filter, type FilterMode, type FilteredView, type KeyFilter } from '@hcengineering/view'
1717
import { get, writable } from 'svelte/store'
1818
import view from './plugin'
19+
import { WorkbenchTab } from '../../workbench/types'
1920

2021
/**
2122
* @public
@@ -319,16 +320,25 @@ export function createFilter (_class: Ref<Class<Doc>>, key: string, value: any[]
319320
}
320321
}
321322

322-
export function getFilterKey (_class: Ref<Class<Doc>> | undefined): string {
323-
const loc = getCurrentResolvedLocation()
324-
loc.path.length = 3
325-
loc.fragment = undefined
326-
loc.query = undefined
327-
let res = 'filter' + locationToUrl(loc)
323+
export function getFilterKey (
324+
_class: Ref<Class<Doc>> | undefined,
325+
tabId?: Ref<WorkbenchTab>
326+
): string {
327+
const loc = getCurrentResolvedLocation();
328+
329+
const urlLoc = {
330+
path: loc.path.slice(0, 3),
331+
query: loc.query,
332+
fragment: loc.fragment
333+
};
334+
335+
const classIdString = _class ? (_class as any).id || _class.toString() : 'unknown_class';
336+
337+
let res = `filter_${tabId || 'global'}_${locationToUrl(urlLoc)}`;
328338
if (_class !== undefined) {
329-
res = res + _class
339+
res = res + classIdString;
330340
}
331-
return res
341+
return res;
332342
}
333343

334344
/**

plugins/workbench-resources/src/workbench.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
} from '@hcengineering/ui'
3838
import view from '@hcengineering/view'
3939
import { parseLinkId } from '@hcengineering/view-resources'
40-
import { type Application, workbenchId, type WorkbenchTab } from '@hcengineering/workbench'
40+
import { type Application, workbenchId, type WorkbenchTab, type TabUiState } from '@hcengineering/workbench'
4141
import { derived, get, writable } from 'svelte/store'
4242

4343
import setting from '@hcengineering/setting'
@@ -51,6 +51,19 @@ export const currentTabStore = derived([tabIdStore, tabsStore], ([tabId, tabs])
5151
return tabs.find((tab) => tab._id === tabId)
5252
})
5353

54+
export function updateTabUiState (tabId: Ref<WorkbenchTab>, state: Partial<TabUiState>): void {
55+
tabsStore.update((tabs) => {
56+
const tab = tabs.find((t) => t._id === tabId)
57+
if (tab != null) {
58+
tab.uiState = {
59+
...(tab.uiState ?? {}),
60+
...state
61+
}
62+
}
63+
return tabs
64+
})
65+
}
66+
5467
let prevTabId: Ref<WorkbenchTab> | undefined
5568
tabIdStore.subscribe((value) => {
5669
prevTabIdStore.set(prevTabId)

plugins/workbench/src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,20 @@ export interface WidgetTab {
9999
readonly?: boolean
100100
}
101101

102+
/** @public */
103+
export interface TabUiState {
104+
viewletId?: string;
105+
filterBarState?: Record<string, any>;
106+
}
107+
108+
102109
/** @public */
103110
export interface WorkbenchTab extends Preference {
104111
attachedTo: AccountUuid
105112
location: string
106113
isPinned: boolean
107114
name?: string
115+
uiState?: TabUiState;
108116
}
109117

110118
/** @public */

0 commit comments

Comments
 (0)