diff --git a/client/src/components/sidebar/SidebarWorkspaceItem.vue b/client/src/components/sidebar/SidebarWorkspaceItem.vue index 574e45817f0..07cdd3f3238 100644 --- a/client/src/components/sidebar/SidebarWorkspaceItem.vue +++ b/client/src/components/sidebar/SidebarWorkspaceItem.vue @@ -4,7 +4,7 @@ import { IonItem, IonText, IonIcon } from '@ionic/vue'; -import { WorkspaceHandle, WorkspaceInfo } from '@/parsec'; +import { WorkspaceInfo } from '@/parsec'; import { ellipsisHorizontal } from 'ionicons/icons'; import { currentRouteIsWorkspaceRoute } from '@/router'; import { onMounted, onBeforeUnmount, ref } from 'vue'; @@ -33,7 +33,7 @@ defineProps<{ }>(); const emits = defineEmits<{ - (e: 'workspaceClicked', handle: WorkspaceHandle): void; + (e: 'workspaceClicked', workspace: WorkspaceInfo): void; (e: 'contextMenuRequested', event: Event): void; }>(); diff --git a/client/src/components/users/types.ts b/client/src/components/users/types.ts index 84adca0573c..4012813d859 100644 --- a/client/src/components/users/types.ts +++ b/client/src/components/users/types.ts @@ -72,6 +72,7 @@ export class UserCollection { setFilters(filters: UserFilterLabels): void { this.filters.statusActive = filters.statusActive; this.filters.statusRevoked = filters.statusRevoked; + this.filters.statusFrozen = filters.statusFrozen; this.filters.profileAdmin = filters.profileAdmin; this.filters.profileStandard = filters.profileStandard; this.filters.profileOutsider = filters.profileOutsider; diff --git a/client/src/components/workspaces/utils.ts b/client/src/components/workspaces/utils.ts index 363b540fd3b..1ffda857249 100644 --- a/client/src/components/workspaces/utils.ts +++ b/client/src/components/workspaces/utils.ts @@ -21,6 +21,7 @@ import WorkspaceContextMenu, { WorkspaceAction } from '@/views/workspaces/Worksp import WorkspaceSharingModal from '@/views/workspaces/WorkspaceSharingModal.vue'; import { modalController, popoverController } from '@ionic/vue'; import { Clipboard, DisplayState, Translatable, getTextFromUser } from 'megashark-lib'; +import { toRaw } from 'vue'; export const WORKSPACES_PAGE_DATA_KEY = 'WorkspacesPage'; @@ -102,7 +103,7 @@ export async function toggleFavorite( } await storageManager.updateComponentData( WORKSPACES_PAGE_DATA_KEY, - { favoriteList: favorites }, + { favoriteList: toRaw(favorites) }, WorkspaceDefaultData, ); eventDistributor.dispatchEvent(Events.WorkspaceFavorite); diff --git a/client/src/services/recentDocuments.ts b/client/src/services/recentDocuments.ts index e1c5817469c..d7387bf5f0f 100644 --- a/client/src/services/recentDocuments.ts +++ b/client/src/services/recentDocuments.ts @@ -62,9 +62,11 @@ class RecentDocumentManager { window.electronAPI.log('error', `Failed to load recent workspaces: ${JSON.stringify(workspacesResult.error)}`); return; } - for (const workspace of workspacesResult.value) { - if (storedData.workspaces.includes(workspace.id)) { - this.addWorkspace(workspace); + + for (const workspaceId of storedData.workspaces.toReversed()) { + const wk = workspacesResult.value.find((w) => w.id === workspaceId); + if (wk) { + this.addWorkspace(wk); } } console.log('Loaded', this.workspaces.value); @@ -82,22 +84,25 @@ class RecentDocumentManager { }); } + private _arrayMove(array: Array, from: number, to: number): void { + array.splice(to, 0, array.splice(from, 1)[0]); + } + addFile(file: RecentFile): void { - const exists = this.files.value.find((item) => item.entryId === file.entryId) !== undefined; - if (exists) { - return; - } - if (this.files.value.unshift(file) > FILE_HISTORY_SIZE) { + const index = this.files.value.findIndex((item) => item.entryId === file.entryId); + if (index !== -1) { + this._arrayMove(this.files.value, index, 0); + } else if (this.files.value.unshift(file) > FILE_HISTORY_SIZE) { this.files.value.pop(); } } addWorkspace(workspace: RecentWorkspace): void { - const exists = this.workspaces.value.find((item) => item.id === workspace.id) !== undefined; - if (exists) { - return; - } - if (this.workspaces.value.unshift(workspace) > WORKSPACE_HISTORY_SIZE) { + const index = this.workspaces.value.findIndex((item) => item.id === workspace.id); + if (index !== -1) { + console.log(index); + this._arrayMove(this.workspaces.value, index, 0); + } else if (this.workspaces.value.unshift(workspace) > WORKSPACE_HISTORY_SIZE) { this.workspaces.value.pop(); } } diff --git a/client/src/services/storageManager.ts b/client/src/services/storageManager.ts index 9dd67a2e6d6..ba03ae8a392 100644 --- a/client/src/services/storageManager.ts +++ b/client/src/services/storageManager.ts @@ -99,7 +99,7 @@ export class StorageManager { async storeComponentData(componentKey: string, data: Type): Promise { const key = `${StorageManager.STORED_COMPONENT_PREFIX}_${componentKey}`; - await this.internalStore.set(key, JSON.stringify(data)); + await this.internalStore.set(key, data); } async updateComponentData(componentKey: string, newData: Type, defaultValues: Required): Promise { @@ -114,7 +114,7 @@ export class StorageManager { } } try { - await this.internalStore.set(key, JSON.stringify(data)); + await this.internalStore.set(key, data); } catch (error) { console.log(`Failed to serialize ${componentKey}: ${error}`); } @@ -125,7 +125,7 @@ export class StorageManager { const data = await this.internalStore.get(key); try { - const parsedData = JSON.parse(data) || {}; + const parsedData = data || {}; for (const element in defaultValues) { if (!(element in parsedData)) { parsedData[element] = defaultValues[element]; @@ -134,6 +134,7 @@ export class StorageManager { return parsedData; } catch (error) { console.log(`Failed to deserialize ${componentKey}: ${error}`); + await this.internalStore.set(key, {}); } return defaultValues; } diff --git a/client/src/views/sidebar-menu/SidebarMenuPage.vue b/client/src/views/sidebar-menu/SidebarMenuPage.vue index e50111dc759..a305b6aca10 100644 --- a/client/src/views/sidebar-menu/SidebarMenuPage.vue +++ b/client/src/views/sidebar-menu/SidebarMenuPage.vue @@ -199,7 +199,7 @@ v-for="workspace in recentDocumentManager.getWorkspaces()" :workspace="workspace" :key="workspace.id" - @workspace-clicked="goToWorkspace" + @workspace-clicked="goToWorkspace($event, true)" @context-menu-requested=" openWorkspaceContextMenu($event, workspace, favorites, eventDistributor, informationManager, storageManager, true) " @@ -322,7 +322,6 @@ import { AvailableDevice, ClientInfo, UserProfile, - WorkspaceHandle, WorkspaceID, WorkspaceInfo, getCurrentAvailableDevice, @@ -433,8 +432,13 @@ const watchSidebarWidthCancel = watch(computedWidth, async (value: number) => { }, 2000); }); -async function goToWorkspace(workspaceHandle: WorkspaceHandle): Promise { - await navigateToWorkspace(workspaceHandle); +async function goToWorkspace(workspace: WorkspaceInfo, skipRecent = false): Promise { + if (!skipRecent) { + recentDocumentManager.addWorkspace(workspace); + await recentDocumentManager.saveToStorage(storageManager); + } + + await navigateToWorkspace(workspace.handle); await menuController.close(); } diff --git a/client/src/views/users/UsersPage.vue b/client/src/views/users/UsersPage.vue index 83df6323f99..c7ebd74c9ff 100644 --- a/client/src/views/users/UsersPage.vue +++ b/client/src/views/users/UsersPage.vue @@ -156,7 +156,7 @@ import UserGridDisplay from '@/views/users/UserGridDisplay.vue'; import UserListDisplay from '@/views/users/UserListDisplay.vue'; import { IonContent, IonPage, IonText, modalController, popoverController } from '@ionic/vue'; import { informationCircle, personAdd, personRemove } from 'ionicons/icons'; -import { Ref, inject, onMounted, onUnmounted, ref } from 'vue'; +import { Ref, inject, onMounted, onUnmounted, ref, toRaw } from 'vue'; import BulkRoleAssignmentModal from '@/views/users/BulkRoleAssignmentModal.vue'; import { EventData, EventDistributor, EventDistributorKey, Events, InvitationUpdatedData } from '@/services/eventDistributor'; @@ -186,7 +186,7 @@ interface UsersPageSavedData { async function storeComponentData(): Promise { await storageManager.storeComponentData(USERS_PAGE_DATA_KEY, { displayState: displayView.value, - filters: users.value.getFilters(), + filters: toRaw(users.value.getFilters()), sortProperty: currentSortProperty.value, sortAscending: currentSortOrder.value, }); @@ -198,6 +198,7 @@ async function restoreComponentData(): Promise { filters: { statusActive: true, statusRevoked: true, + statusFrozen: true, profileAdmin: true, profileStandard: true, profileOutsider: true,