Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MS] Replaced workspaces by recent workspaces in sidebar #9345

Merged
merged 2 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/src/components/sidebar/SidebarRecentFileItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<script setup lang="ts">
import { IonItem, IonText, IonIcon } from '@ionic/vue';
import { close } from 'ionicons/icons';
import { RecentFile } from '@/services/recentFiles';
import { RecentFile } from '@/services/recentDocuments';

defineProps<{
file: RecentFile;
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/sidebar/SidebarWorkspaceItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<ion-item
lines="none"
button
@click="$emit('workspaceClicked', workspace.handle)"
@click="$emit('workspaceClicked', workspace)"
:class="currentRouteIsWorkspaceRoute(workspace.handle) ? 'item-selected' : 'item-not-selected'"
class="sidebar-item button-medium ion-no-padding"
ref="itemRef"
Expand All @@ -23,7 +23,7 @@

<script setup lang="ts">
import { IonItem, IonText, IonIcon } from '@ionic/vue';
import { WorkspaceInfo, WorkspaceHandle } from '@/parsec';
import { WorkspaceInfo } from '@/parsec';
import { ellipsisHorizontal } from 'ionicons/icons';
import { currentRouteIsWorkspaceRoute } from '@/router';
import { onMounted, onBeforeUnmount, ref } from 'vue';
Expand All @@ -33,7 +33,7 @@ defineProps<{
}>();

const emits = defineEmits<{
(e: 'workspaceClicked', handle: WorkspaceHandle): void;
(e: 'workspaceClicked', workspace: WorkspaceInfo): void;
(e: 'contextMenuRequested', event: Event): void;
}>();

Expand Down
1 change: 1 addition & 0 deletions client/src/components/users/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/workspaces/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -102,7 +103,7 @@ export async function toggleFavorite(
}
await storageManager.updateComponentData<WorkspacesPageSavedData>(
WORKSPACES_PAGE_DATA_KEY,
{ favoriteList: favorites },
{ favoriteList: toRaw(favorites) },
WorkspaceDefaultData,
);
eventDistributor.dispatchEvent(Events.WorkspaceFavorite);
Expand Down
7 changes: 4 additions & 3 deletions client/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -417,8 +417,8 @@
"actionShare": "Sharing and roles",
"actionDetails": "Details",
"actionCopyLink": "Copy link",
"actionAddFavorite": "Add to favorites",
"actionRemoveFavorite": "Remove from favorites"
"actionAddFavorite": "Pin",
"actionRemoveFavorite": "Unpin"
},
"CreateWorkspaceModal": {
"pageTitle": "Create a new workspace",
Expand Down Expand Up @@ -698,7 +698,8 @@
},
"SideMenu": {
"workspaces": "Workspaces",
"favorites": "Favorites",
"recentWorkspaces": "Recent workspaces",
"favorites": "Pinned",
"noWorkspace": "No workspaces",
"users": "Users",
"storage": "Storage",
Expand Down
7 changes: 4 additions & 3 deletions client/src/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -417,8 +417,8 @@
"actionShare": "Partage et rôles",
"actionDetails": "Détails",
"actionCopyLink": "Copier le lien",
"actionAddFavorite": "Ajouter aux favoris",
"actionRemoveFavorite": "Retirer des favoris"
"actionAddFavorite": "Épingler",
"actionRemoveFavorite": "Désépingler"
},
"CreateWorkspaceModal": {
"pageTitle": "Créer un nouvel espace de travail",
Expand Down Expand Up @@ -698,7 +698,8 @@
},
"SideMenu": {
"workspaces": "Espaces de travail",
"favorites": "Favoris",
"recentWorkspaces": "Espaces récents",
"favorites": "Épinglés",
"noWorkspace": "Aucun espace de travail",
"users": "Utilisateurs",
"storage": "Stockage",
Expand Down
8 changes: 6 additions & 2 deletions client/src/parsec/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,12 @@ export async function initializeWorkspace(
return startedWorkspaceResult;
}

export async function listWorkspaces(): Promise<Result<Array<WorkspaceInfo>, ClientListWorkspacesError>> {
const handle = getParsecHandle();
export async function listWorkspaces(
handle: ConnectionHandle | null = null,
): Promise<Result<Array<WorkspaceInfo>, ClientListWorkspacesError>> {
if (!handle) {
handle = getParsecHandle();
}

if (handle !== null && !needsMocks()) {
const result = await libparsec.clientListWorkspaces(handle);
Expand Down
6 changes: 3 additions & 3 deletions client/src/services/fileOpener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { detectFileContentType, FileContentType } from '@/common/fileTypes';
import { entryStat, EntryStat, entryStatAt, FsPath, getSystemPath, isDesktop, WorkspaceHandle, WorkspaceHistoryEntryStat } from '@/parsec';
import { navigateTo, Routes } from '@/router';
import { Information, InformationLevel, InformationManager, PresentationMode } from '@/services/informationManager';
import { recentFileManager } from '@/services/recentFiles';
import { recentDocumentManager } from '@/services/recentDocuments';
import { DateTime } from 'luxon';
import { Base64, openSpinnerModal } from 'megashark-lib';

Expand All @@ -21,7 +21,7 @@ async function openWithSystem(
informationManager: InformationManager,
): Promise<void> {
if (entry.isFile()) {
recentFileManager.addFile({
recentDocumentManager.addFile({
entryId: entry.id,
path: entry.path,
workspaceHandle: workspaceHandle,
Expand Down Expand Up @@ -113,7 +113,7 @@ async function openPath(
return;
}

recentFileManager.addFile({
recentDocumentManager.addFile({
entryId: entry.id,
path: entry.path,
workspaceHandle: workspaceHandle,
Expand Down
138 changes: 138 additions & 0 deletions client/src/services/recentDocuments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

import { DetectedFileType } from '@/common/fileTypes';
import {
ConnectionHandle,
EntryID,
EntryName,
FsPath,
getClientInfo,
listWorkspaces,
UserID,
WorkspaceHandle,
WorkspaceID,
WorkspaceInfo,
} from '@/parsec';
import { StorageManager } from '@/services/storageManager';
import { Ref, ref } from 'vue';

interface RecentFile {
entryId: EntryID;
workspaceHandle: WorkspaceHandle;
path: FsPath;
name: EntryName;
contentType?: DetectedFileType;
}

type RecentWorkspace = WorkspaceInfo;

const FILE_HISTORY_SIZE = 5;
const WORKSPACE_HISTORY_SIZE = 5;

interface RecentDocumentStorageData {
workspaces: Array<WorkspaceID>;
}

class RecentDocumentManager {
files: Ref<Array<RecentFile>> = ref([]);
workspaces: Ref<Array<RecentWorkspace>> = ref([]);

constructor() {
this.files.value = new Array<RecentFile>();
this.workspaces.value = new Array<RecentWorkspace>();
}

private _getStorageDataKey(userId: UserID): string {
return `recentDocuments_${userId}`;
}

async loadFromStorage(storage: StorageManager, handle: ConnectionHandle | null = null): Promise<void> {
const clientInfoResult = await getClientInfo(handle);
if (!clientInfoResult.ok) {
window.electronAPI.log('error', `Failed to load recent workspaces: ${JSON.stringify(clientInfoResult.error)}`);
return;
}
const dataKey = this._getStorageDataKey(clientInfoResult.value.userId);
const storedData = await storage.retrieveComponentData<RecentDocumentStorageData>(dataKey, { workspaces: [] });
if (!storedData || !storedData.workspaces) {
return;
}
const workspacesResult = await listWorkspaces(handle);
if (!workspacesResult.ok) {
window.electronAPI.log('error', `Failed to load recent workspaces: ${JSON.stringify(workspacesResult.error)}`);
return;
}

for (const workspaceId of storedData.workspaces.toReversed()) {
const wk = workspacesResult.value.find((w) => w.id === workspaceId);
if (wk) {
this.addWorkspace(wk);
}
}
}

async saveToStorage(storage: StorageManager): Promise<void> {
const clientInfoResult = await getClientInfo();
if (!clientInfoResult.ok) {
window.electronAPI.log('error', `Failed to save recent workspaces: ${JSON.stringify(clientInfoResult.error)}`);
return;
}
const dataKey = this._getStorageDataKey(clientInfoResult.value.userId);
await storage.storeComponentData<RecentDocumentStorageData>(dataKey, {
workspaces: this.workspaces.value.map((workspace) => workspace.id),
});
}

private _arrayMove(array: Array<any>, from: number, to: number): void {
array.splice(to, 0, array.splice(from, 1)[0]);
}

addFile(file: RecentFile): void {
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 index = this.workspaces.value.findIndex((item) => item.id === workspace.id);
if (index !== -1) {
this._arrayMove(this.workspaces.value, index, 0);
} else if (this.workspaces.value.unshift(workspace) > WORKSPACE_HISTORY_SIZE) {
this.workspaces.value.pop();
}
}

removeFile(file: RecentFile): void {
const index = this.files.value.findIndex((item) => item.entryId === file.entryId);
if (index !== -1) {
this.files.value.splice(index, 1);
}
}

removeWorkspace(workspace: RecentWorkspace): void {
const index = this.workspaces.value.findIndex((item) => item.id === workspace.id);
if (index !== -1) {
this.workspaces.value.splice(index, 1);
}
}

resetHistory(): void {
this.files.value = new Array<RecentFile>();
this.workspaces.value = new Array<RecentWorkspace>();
}

getFiles(): Array<RecentFile> {
return this.files.value;
}

getWorkspaces(): Array<RecentWorkspace> {
return this.workspaces.value;
}
}

const recentDocumentManager = new RecentDocumentManager();

export { recentDocumentManager, RecentFile, RecentWorkspace };
52 changes: 0 additions & 52 deletions client/src/services/recentFiles.ts

This file was deleted.

7 changes: 4 additions & 3 deletions client/src/services/storageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class StorageManager {

async storeComponentData<Type>(componentKey: string, data: Type): Promise<void> {
const key = `${StorageManager.STORED_COMPONENT_PREFIX}_${componentKey}`;
await this.internalStore.set(key, JSON.stringify(data));
await this.internalStore.set(key, data);
}

async updateComponentData<Type>(componentKey: string, newData: Type, defaultValues: Required<Type>): Promise<void> {
Expand All @@ -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}`);
}
Expand All @@ -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];
Expand All @@ -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;
}
Expand Down
Loading
Loading