Skip to content

Commit

Permalink
[MS] Show last opened files in sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
NicoTuxx committed Nov 26, 2024
1 parent 5840cd7 commit 9ae990d
Show file tree
Hide file tree
Showing 11 changed files with 361 additions and 144 deletions.
4 changes: 4 additions & 0 deletions client/src/common/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ class FIFO<T> {
length(): number {
return this.entries.length;
}

has(findFunction: (val: T) => boolean): boolean {
return this.entries.find(findFunction) !== undefined;
}
}

export { FIFO };
81 changes: 81 additions & 0 deletions client/src/components/sidebar/SidebarRecentFileItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<!-- Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS -->

<template>
<ion-item
lines="none"
button
@click="$emit('fileClicked', file)"
class="sidebar-item menu-default"
ref="itemRef"
>
<ion-text class="sidebar-item-file-label">{{ file.name }}</ion-text>
<div
class="file-option"
@click.stop="$emit('removeClicked', file)"
>
<ion-icon :icon="close" />
</div>
</ion-item>
</template>

<script setup lang="ts">
import { IonItem, IonText, IonIcon } from '@ionic/vue';
import { close } from 'ionicons/icons';
import { RecentFile } from '@/services/recentFiles';

defineProps<{
file: RecentFile;
}>();

defineEmits<{
(e: 'fileClicked', file: RecentFile): void;
(e: 'removeClicked', file: RecentFile): void;
}>();
</script>

<style scoped lang="scss">
.sidebar-item {
--background: none;
border-radius: var(--parsec-radius-8);
border: solid 1px var(--parsec-color-light-primary-800);
--padding-start: 1rem;
--padding-end: 1rem;
--padding-top: 0.5rem;
--padding-bottom: 0.5rem;

.file-option {
color: var(--parsec-color-light-secondary-grey);
font-size: 1.2rem;
opacity: 1;
display: flex;
align-items: center;

&:hover {
color: var(--parsec-color-light-primary-30);
}
}

&:hover {
border: solid 1px var(--parsec-color-light-primary-30-opacity15);
cursor: pointer;

.file-option {
opacity: 1;
}
}

&:active,
&.item-selected {
--background: var(--parsec-color-light-primary-30-opacity15);
}

&-file-label {
position: relative;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
color: var(--parsec-color-light-primary-100);
width: 100%;
}
}
</style>
3 changes: 2 additions & 1 deletion client/src/components/sidebar/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

import SidebarRecentFileItem from '@/components/sidebar/SidebarRecentFileItem.vue';
import SidebarWorkspaceItem from '@/components/sidebar/SidebarWorkspaceItem.vue';

export { SidebarWorkspaceItem };
export { SidebarRecentFileItem, SidebarWorkspaceItem };
4 changes: 3 additions & 1 deletion client/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@
"manageOrganization": "Manage my organization",
"organizationInfo": "Information",
"goToHome": "My workspaces",
"recentDocuments": "Recent documents",
"back": "Back",
"trial": {
"tag": "Trial version",
Expand Down Expand Up @@ -1695,6 +1696,7 @@
},
"fileViewers": {
"openWithDefault": "Open with default application",
"openingFile": "Opening file..."
"openingFile": "Opening file...",
"fileTooBig": "File is too big to be opened using a viewer."
}
}
4 changes: 3 additions & 1 deletion client/src/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@
"manageOrganization": "Gérer mon organisation",
"organizationInfo": "Informations",
"goToHome": "Mes espaces",
"recentDocuments": "Documents récents",
"back": "Retour",
"trial": {
"tag": "Version d'essai",
Expand Down Expand Up @@ -1695,6 +1696,7 @@
},
"fileViewers": {
"openWithDefault": "Ouvrir avec l'application par défaut",
"openingFile": "Ouverture du fichier..."
"openingFile": "Ouverture du fichier...",
"fileTooBig": "Le fichier est trop large pour être ouvert avec un visualiseur."
}
}
117 changes: 117 additions & 0 deletions client/src/services/fileOpener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

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 { DateTime } from 'luxon';
import { Base64, openSpinnerModal } from 'megashark-lib';

interface OpenPathOptions {
skipViewers?: boolean;
atTime?: DateTime;
}

const OPEN_FILE_SIZE_LIMIT = 15_000_000;

async function openWithSystem(
workspaceHandle: WorkspaceHandle,
entry: EntryStat | WorkspaceHistoryEntryStat,
informationManager: InformationManager,
): Promise<void> {
if (entry.isFile()) {
recentFileManager.addFile({
entryId: entry.id,
path: entry.path,
workspaceHandle: workspaceHandle,
name: entry.name,
});
}

const result = await getSystemPath(workspaceHandle, entry.path);

if (!result.ok) {
await informationManager.present(
new Information({
message: entry.isFile() ? 'FoldersPage.open.fileFailed' : 'FoldersPage.open.folderFailed',
level: InformationLevel.Error,
}),
PresentationMode.Modal,
);
} else {
window.electronAPI.openFile(result.value);
}
}

async function openPath(
workspaceHandle: WorkspaceHandle,
path: FsPath,
informationManager: InformationManager,
options: OpenPathOptions,
): Promise<void> {
let statsResult;
if (options.atTime) {
statsResult = await entryStatAt(workspaceHandle, path, options.atTime);
} else {
statsResult = await entryStat(workspaceHandle, path);
}
if (!statsResult.ok) {
await informationManager.present(
new Information({
message: 'FoldersPage.open.fileFailed',
level: InformationLevel.Error,
}),
PresentationMode.Modal,
);
return;
}

const entry = statsResult.value;

if (!entry.isFile()) {
await openWithSystem(workspaceHandle, entry, informationManager);
return;
}
if (isDesktop() && options.skipViewers) {
await openWithSystem(workspaceHandle, entry, informationManager);
return;
}

const modal = await openSpinnerModal('fileViewers.openingFile');
const contentType = await detectFileContentType(workspaceHandle, entry.path, options.atTime);

try {
if (!contentType || contentType.type === FileContentType.Unknown) {
await openWithSystem(workspaceHandle, entry, informationManager);
} else {
if ((entry as any).size > OPEN_FILE_SIZE_LIMIT) {
informationManager.present(
new Information({
message: 'fileViewers.fileTooBig',
level: InformationLevel.Warning,
}),
PresentationMode.Toast,
);
await openWithSystem(workspaceHandle, entry, informationManager);
return;
}

recentFileManager.addFile({
entryId: entry.id,
path: entry.path,
workspaceHandle: workspaceHandle,
name: entry.name,
contentType: contentType,
});

await navigateTo(Routes.Viewer, {
query: { workspaceHandle: workspaceHandle, documentPath: entry.path, fileTypeInfo: Base64.fromObject(contentType) },
});
}
} finally {
await modal.dismiss();
}
}

export { openPath };
52 changes: 52 additions & 0 deletions client/src/services/recentFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

import { DetectedFileType } from '@/common/fileTypes';
import { EntryID, EntryName, FsPath, WorkspaceHandle } from '@/parsec';
import { Ref, ref } from 'vue';

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

const HISTORY_SIZE = 5;

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

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

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) > HISTORY_SIZE) {
this.files.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);
}
}

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

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

const recentFileManager = new RecentFileManager();

export { RecentFile, recentFileManager };
Loading

0 comments on commit 9ae990d

Please sign in to comment.