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-file-viewer-bottombar #8965

Merged
merged 2 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
39 changes: 39 additions & 0 deletions client/src/components/viewers/FileViewerActionBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!-- Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS -->

<template>
<ms-action-bar class="file-viewer-action-bar">
<ms-action-bar-button
v-for="(action, key) in actions"
:key="key"
:icon="action.icon"
@click="action.handler"
:button-label="action.text"
/>
</ms-action-bar>
</template>

<script setup lang="ts">
import { MsActionBar, MsActionBarButton, Translatable } from 'megashark-lib';

// TODO: add a disabled state to the button (ex: max zoom level).
// TODO: extract this interface to a separate file.
interface FileViewerAction {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll probably want a disabled attribute to prevent clicking in some cases (max zoom level reached for example).
Also better to extract this interface to a separate file to be able to import it for typing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the action bar will change depending on the feature list we will make with Fabien on monday, I just add your suggestions as comment to keep it in mind in the next PR.

icon?: string;
text?: Translatable;
handler: () => void;
}

defineProps<{
actions: Array<FileViewerAction>;
}>();
</script>

<style scoped>
.file-viewer-action-bar {
padding: 0.5rem 1rem;
width: fit-content;
background: var(--parsec-color-light-primary-100);
border-radius: 5em;
box-shadow: var(--parsec-shadow-light);
}
</style>
5 changes: 5 additions & 0 deletions client/src/components/viewers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

import FileViewerActionBar from '@/components/viewers/FileViewerActionBar.vue';

export { FileViewerActionBar };
3 changes: 2 additions & 1 deletion client/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,7 @@
"date": "Preview date:"
},
"fileViewers": {
"openWithDefault": "Open with default application"
"openWithDefault": "Open with default application",
"openingFile": "Opening file..."
}
}
3 changes: 2 additions & 1 deletion client/src/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,7 @@
"date": "Date de prévisualisation :"
},
"fileViewers": {
"openWithDefault": "Ouvrir avec l'application par défaut"
"openWithDefault": "Ouvrir avec l'application par défaut",
"openingFile": "Ouverture du fichier..."
}
}
30 changes: 13 additions & 17 deletions client/src/theme/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -135,23 +135,19 @@ ion-title {
}

// specific style for the file viewers
.file-viewer {
&-bottombar {
.action-bar {
.ms-action-bar-button {
height: 3em;
width: 3em;
border-radius: 100%;
color: var(--parsec-color-light-primary-600);
opacity: 0.6;
scale: 1;
transition: all 0.2s ease-in-out;

&:hover {
scale: 1.1;
opacity: 1;
}
}
.file-viewer-action-bar {
.ms-action-bar-button {
height: 3em;
width: fit-content;
border-radius: 100%;
color: var(--parsec-color-light-primary-600);
opacity: 0.6;
scale: 1;
transition: all 0.2s ease-in-out;

&:hover {
scale: 1.1;
opacity: 1;
}
}
}
2 changes: 1 addition & 1 deletion client/src/views/files/FoldersPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1211,7 +1211,7 @@ async function openEntries(entries: EntryModel[]): Promise<void> {
return;
}

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

try {
Expand Down
64 changes: 59 additions & 5 deletions client/src/views/viewers/AudioViewer.vue
Original file line number Diff line number Diff line change
@@ -1,26 +1,80 @@
<!-- Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS -->

<template>
<audio
controls
v-if="src.length"
:src="src"
/>
<file-viewer-wrapper>
<template #viewer>
<audio
controls
v-if="src.length"
ref="audioElement"
@play="updateMediaData"
@playing="updateMediaData"
@canplay="updateMediaData"
@pause="updateMediaData"
@volumechange="updateMediaData"
:src="src"
/>
</template>
<!-- Disabled till we add an illustration in the viewer -->
<!-- <template #controls>
<file-viewer-action-bar
:actions="[
{ icon: paused ? play : pause, handler: togglePlayback },
{ icon: getVolumeIcon(), handler: toggleVolume },
]"
/>
</template> -->
</file-viewer-wrapper>
</template>

<script setup lang="ts">
// import { play, pause, volumeHigh, volumeLow, volumeMedium, volumeMute } from 'ionicons/icons';
import { onMounted, ref } from 'vue';
import { FileViewerWrapper } from '@/views/viewers';
import { FileContentInfo } from '@/views/viewers/utils';

const props = defineProps<{
contentInfo: FileContentInfo;
}>();

// const VOLUME_LEVELS = [0, 0.25, 0.5, 1];

const src = ref('');
const audioElement = ref();
const paused = ref(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need this? You can access the paused value on the element itself to get its state. Same with volume.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we need this part: audioElement as ref don't trigger its "change" state when using media controls.

const volume = ref(1);

onMounted(async () => {
src.value = URL.createObjectURL(new Blob([props.contentInfo.data], { type: props.contentInfo.mimeType }));
});

// function togglePlayback(): void {
// audioElement.value.paused ? audioElement.value.play() : audioElement.value.pause();
// }

// function toggleVolume(): void {
// audioElement.value.volume = VOLUME_LEVELS[(VOLUME_LEVELS.indexOf(audioElement.value.volume) + 1) % VOLUME_LEVELS.length];
// }

function updateMediaData(event: Event): void {
volume.value = (event.target as HTMLAudioElement).volume;
paused.value = (event.target as HTMLAudioElement).paused;
}

// function getVolumeIcon(): string {
// switch (volume.value) {
// case 0:
// return volumeMute;
// case 0.25:
// return volumeLow;
// case 0.5:
// return volumeMedium;
// case 1:
// return volumeHigh;
// default:
// return volumeMute;
// }
// }
</script>

<style scoped lang="scss"></style>
17 changes: 11 additions & 6 deletions client/src/views/viewers/DocumentViewer.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
<!-- Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS -->

<template>
<ms-spinner v-show="loading" />
<div
v-show="!loading"
class="document-content"
v-html="htmlContent"
/>
<file-viewer-wrapper>
<template #viewer>
<ms-spinner v-show="loading" />
<div
v-show="!loading"
class="document-content"
v-html="htmlContent"
/>
</template>
</file-viewer-wrapper>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { FileViewerWrapper } from '@/views/viewers';
import { FileContentInfo } from '@/views/viewers/utils';
import mammoth from 'mammoth';
import { MsSpinner } from 'megashark-lib';
Expand Down
84 changes: 8 additions & 76 deletions client/src/views/viewers/FileViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,42 +32,11 @@
</ion-buttons>
</div>

<!-- file-viewer wrapper -->
<div class="file-viewer-wrapper">
<component
:is="viewerComponent"
:content-info="contentInfo"
:config="viewerConfig"
/>
</div>

<!-- file-viewer bottombar -->
<div class="file-viewer-bottombar">
<!-- Here the idea is to put the specific file viewer bottom bar actions -->
<ms-action-bar
v-if="detectedFileType?.type === FileContentType.Image"
class="action-bar"
>
<ms-action-bar-button
id="button-image-zoom-out"
v-show="detectedFileType?.type === FileContentType.Image"
:icon="remove"
@click="zoomOut"
/>
<ms-action-bar-button
id="button-image-zoom-reset"
v-show="detectedFileType?.type === FileContentType.Image"
:icon="resize"
@click="resetZoom"
/>
<ms-action-bar-button
id="button-image-zoom-in"
v-show="detectedFileType?.type === FileContentType.Image"
:icon="add"
@click="zoomIn"
/>
</ms-action-bar>
</div>
<!-- file-viewer component -->
<component
:is="viewerComponent"
:content-info="contentInfo"
/>
</div>
</div>
</ion-content>
Expand All @@ -91,14 +60,14 @@ import {
closeHistoryFile,
} from '@/parsec';
import { IonPage, IonContent, IonButton, IonText, IonIcon, IonButtons } from '@ionic/vue';
import { add, open, remove, resize } from 'ionicons/icons';
import { Base64, MsSpinner, MsImage, MsActionBar, MsActionBarButton } from 'megashark-lib';
import { open } from 'ionicons/icons';
import { Base64, MsSpinner, MsImage } from 'megashark-lib';
import { ref, Ref, type Component, inject, onMounted, shallowRef } from 'vue';
import { ImageViewer, VideoViewer, SpreadsheetViewer, DocumentViewer, AudioViewer, TextViewer, PdfViewer } from '@/views/viewers';
import { Information, InformationLevel, InformationManager, InformationManagerKey, PresentationMode } from '@/services/informationManager';
import { getCurrentRouteQuery, getDocumentPath, getWorkspaceHandle, navigateTo, Routes } from '@/router';
import { DetectedFileType, FileContentType } from '@/common/fileTypes';
import { FileContentInfo, ViewerConfig, imageViewerUtils } from '@/views/viewers/utils';
import { FileContentInfo } from '@/views/viewers/utils';
import { Env } from '@/services/environment';
import { DateTime } from 'luxon';
import { getFileIcon } from '@/common/file';
Expand All @@ -110,9 +79,6 @@ const detectedFileType = ref<DetectedFileType | null>(null);
const loaded = ref(false);
const READ_CHUNK_SIZE = 512_000;
const atDateTime: Ref<DateTime | null> = ref(null);
const viewerConfig: Ref<ViewerConfig> = ref({
zoomLevel: 100,
});

onMounted(async () => {
loaded.value = false;
Expand Down Expand Up @@ -274,18 +240,6 @@ async function onClick(event: MouseEvent): Promise<void> {
}
}
}

function zoomOut(): void {
viewerConfig.value = imageViewerUtils.zoomOut(viewerConfig.value);
}

function resetZoom(): void {
viewerConfig.value = imageViewerUtils.resetZoom(viewerConfig.value);
}

function zoomIn(): void {
viewerConfig.value = imageViewerUtils.zoomIn(viewerConfig.value);
}
</script>

<style scoped lang="scss">
Expand Down Expand Up @@ -357,28 +311,6 @@ function zoomIn(): void {
}
}
}

&-wrapper {
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}

&-bottombar {
display: flex;
justify-content: center;
align-items: center;
gap: 1.5rem;

.action-bar {
padding: 0.5rem 1rem;
width: fit-content;
background: var(--parsec-color-light-primary-100);
border-radius: 5em;
box-shadow: var(--parsec-shadow-light);
}
}
}
}
</style>
35 changes: 35 additions & 0 deletions client/src/views/viewers/FileViewerWrapper.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!-- Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS -->

<template>
<!-- file-viewer wrapper -->
<div class="file-viewer-wrapper">
<slot name="viewer" />
</div>

<!-- file-viewer bottombar -->
<div class="file-viewer-bottombar">
<slot name="controls" />
</div>
</template>

<script setup lang="ts"></script>

<style scoped lang="scss">
.file-viewer-wrapper {
height: 100%;
height: -webkit-fill-available;
height: -moz-available;
height: stretch;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}

.file-viewer-bottombar {
display: flex;
justify-content: center;
align-items: center;
gap: 1.5rem;
}
</style>
Loading
Loading