@@ -21,10 +21,12 @@ import { useDockerViewPreferences } from '@/composables/useDockerColumnVisibilit
2121import { useDockerEditNavigation } from ' @/composables/useDockerEditNavigation' ;
2222import { useFolderOperations } from ' @/composables/useFolderOperations' ;
2323import { useFolderTree } from ' @/composables/useFolderTree' ;
24+ import { usePersistentColumnVisibility } from ' @/composables/usePersistentColumnVisibility' ;
2425import { useTreeData } from ' @/composables/useTreeData' ;
2526
2627import type { DockerContainer , FlatOrganizerEntry } from ' @/composables/gql/graphql' ;
2728import type { DropEvent } from ' @/composables/useDragDrop' ;
29+ import type { ColumnVisibilityTableInstance } from ' @/composables/usePersistentColumnVisibility' ;
2830import type { TreeRow } from ' @/composables/useTreeData' ;
2931import type { TableColumn } from ' @nuxt/ui' ;
3032import type { Component } from ' vue' ;
@@ -196,27 +198,7 @@ const containersRef = computed(() => props.containers);
196198
197199const rootFolderId = computed <string >(() => props .rootFolderId || ' root' );
198200
199- interface BaseTableInstance {
200- columnVisibility? : { value: Record <string , boolean > };
201- tableApi? : {
202- getAllColumns: () => Array <{
203- id: string ;
204- getCanHide: () => boolean ;
205- getIsVisible: () => boolean ;
206- toggleVisibility: (visible : boolean ) => void ;
207- }>;
208- getColumn: (id : string ) =>
209- | {
210- toggleVisibility: (visible : boolean ) => void ;
211- }
212- | undefined ;
213- setColumnVisibility? : (
214- updater : Record <string , boolean > | ((prev : Record <string , boolean >) => Record <string , boolean >)
215- ) => void ;
216- };
217- }
218-
219- const baseTableRef = ref <BaseTableInstance | null >(null );
201+ const baseTableRef = ref <ColumnVisibilityTableInstance | null >(null );
220202
221203const searchableKeys = [
222204 ' name' ,
@@ -505,125 +487,14 @@ const resolvedColumnVisibility = computed<Record<string, boolean>>(() => ({
505487 ... (columnVisibilityRef .value ?? {}),
506488}));
507489
508- function getEffectiveVisibility(
509- visibility : Record <string , boolean > | undefined | null ,
510- columnId : string
511- ): boolean {
512- if (! visibility ) return true ;
513- if (Object .prototype .hasOwnProperty .call (visibility , columnId )) {
514- return visibility [columnId ];
515- }
516- return true ;
517- }
518-
519- function visibilityStatesMatch(
520- current : Record <string , boolean > | null | undefined ,
521- target : Record <string , boolean > | null | undefined ,
522- columnIds : string [] | undefined
523- ): boolean {
524- if (! current || ! target ) return false ;
525- const keys =
526- columnIds && columnIds .length > 0
527- ? new Set (columnIds )
528- : new Set ([... Object .keys (current ), ... Object .keys (target )]);
529- for (const key of keys ) {
530- if (getEffectiveVisibility (current , key ) !== getEffectiveVisibility (target , key )) {
531- return false ;
532- }
533- }
534- return true ;
535- }
536-
537- function applyColumnVisibility(target : Record <string , boolean >) {
538- const tableInstance = baseTableRef .value ;
539- if (! tableInstance ?.columnVisibility ) return ;
540-
541- const visibilityRef = tableInstance .columnVisibility ;
542- const tableApi = tableInstance .tableApi ;
543- const current = visibilityRef .value || {};
544- const columnIds = tableApi
545- ? tableApi
546- .getAllColumns ()
547- .filter ((column ) => column .getCanHide ())
548- .map ((column ) => column .id )
549- : [];
550-
551- if (visibilityStatesMatch (current , target , columnIds )) {
552- return ;
553- }
554-
555- if (tableApi ?.setColumnVisibility ) {
556- tableApi .setColumnVisibility (() => ({ ... target }));
557- } else {
558- visibilityRef .value = { ... target };
559- }
560- }
561-
562- watch (
563- () => resolvedColumnVisibility .value ,
564- (target ) => {
565- applyColumnVisibility (target );
566- },
567- { immediate: true , deep: true }
568- );
569-
570- watch (
571- baseTableRef ,
572- () => {
573- applyColumnVisibility (resolvedColumnVisibility .value );
574- },
575- { immediate: true , flush: ' post' }
576- );
577-
578- const lastSavedColumnVisibility = ref <Record <string , boolean > | null >(null );
579-
580- function getHideableColumnIds(): string [] {
581- const tableApi = baseTableRef .value ?.tableApi ;
582- if (tableApi ) {
583- return tableApi
584- .getAllColumns ()
585- .filter ((column ) => column .getCanHide ())
586- .map ((column ) => column .id );
587- }
588- return Object .keys (defaultColumnVisibility .value );
589- }
590-
591- function normalizeColumnVisibilityState(
592- raw : Record <string , boolean > | null | undefined
593- ): Record <string , boolean > {
594- const ids = getHideableColumnIds ();
595- const normalized: Record <string , boolean > = {};
596- for (const id of ids ) {
597- normalized [id ] = getEffectiveVisibility (raw , id );
598- }
599- return normalized ;
600- }
601-
602- function readCurrentColumnVisibility(): Record <string , boolean > | null {
603- const tableApi = baseTableRef .value ?.tableApi ;
604- if (! tableApi ) return null ;
605- const record: Record <string , boolean > = {};
606- for (const column of tableApi .getAllColumns ()) {
607- if (! column .getCanHide ()) continue ;
608- record [column .id ] = column .getIsVisible ();
609- }
610- return record ;
611- }
612-
613- async function persistCurrentColumnVisibility() {
614- await nextTick ();
615- const current = readCurrentColumnVisibility ();
616- if (! current ) return ;
617- const normalized = normalizeColumnVisibilityState (current );
618- if (
619- lastSavedColumnVisibility .value &&
620- visibilityStatesMatch (normalized , lastSavedColumnVisibility .value , Object .keys (normalized ))
621- ) {
622- return ;
623- }
624- lastSavedColumnVisibility .value = { ... normalized };
625- saveColumnVisibility ({ ... normalized });
626- }
490+ // Keep table visibility in sync with saved preferences and persist optimistic user toggles.
491+ const { persistCurrentColumnVisibility } = usePersistentColumnVisibility ({
492+ tableRef: baseTableRef ,
493+ resolvedVisibility: resolvedColumnVisibility ,
494+ fallbackVisibility: defaultColumnVisibility ,
495+ onPersist : (visibility ) => saveColumnVisibility ({ ... visibility }),
496+ isPersistenceEnabled : () => ! props .compact ,
497+ });
627498
628499watch (
629500 () => props .viewPrefs ,
@@ -635,34 +506,6 @@ watch(
635506 { immediate: true }
636507);
637508
638- watch (
639- () => baseTableRef .value ?.columnVisibility ?.value ,
640- (columnVisibility ) => {
641- if (! columnVisibility || props .compact ) {
642- return ;
643- }
644-
645- const columnIds = getHideableColumnIds ();
646- const normalizedCurrent = normalizeColumnVisibilityState (columnVisibility );
647-
648- if (visibilityStatesMatch (normalizedCurrent , resolvedColumnVisibility .value , columnIds )) {
649- lastSavedColumnVisibility .value = { ... normalizedCurrent };
650- return ;
651- }
652-
653- if (
654- lastSavedColumnVisibility .value &&
655- visibilityStatesMatch (normalizedCurrent , lastSavedColumnVisibility .value , columnIds )
656- ) {
657- return ;
658- }
659-
660- lastSavedColumnVisibility .value = { ... normalizedCurrent };
661- saveColumnVisibility ({ ... normalizedCurrent });
662- },
663- { deep: true }
664- );
665-
666509type ActionDropdownItem = { label: string ; icon? : string ; onSelect? : (e ? : Event ) => void ; as? : string };
667510type DropdownMenuItems = ActionDropdownItem [][];
668511
@@ -699,7 +542,7 @@ const columnsMenuItems = computed<DropdownMenuItems>(() => {
699542 type: ' checkbox' as const ,
700543 checked: column .getIsVisible (),
701544 onUpdateChecked(checked : boolean ) {
702- baseTableRef .value ?.tableApi ?.getColumn (column .id )?.toggleVisibility (!! checked );
545+ baseTableRef .value ?.tableApi ?.getColumn ?. (column .id )?.toggleVisibility (!! checked );
703546 void persistCurrentColumnVisibility ();
704547 },
705548 onSelect(e : Event ) {
0 commit comments