diff --git a/client/src/components/sidebar/SidebarMenuList.vue b/client/src/components/sidebar/SidebarMenuList.vue index 1764f7c8285..269dfc069a7 100644 --- a/client/src/components/sidebar/SidebarMenuList.vue +++ b/client/src/components/sidebar/SidebarMenuList.vue @@ -69,6 +69,7 @@ const emits = defineEmits<{ padding: 0.25rem 0.5rem; opacity: 0.8; cursor: pointer; + width: 100%; &:hover { opacity: 1; @@ -83,16 +84,20 @@ const emits = defineEmits<{ color: var(--parsec-color-light-secondary-inversed-contrast); display: flex; align-items: center; + overflow: hidden; &__icon { - font-size: 1rem; margin-right: 0.5rem; + flex-shrink: 0; + padding: 0.125rem; + font-size: 1rem; } &__text { - margin-right: 0.5rem; - line-height: auto; user-select: none; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; } } } @@ -103,6 +108,7 @@ const emits = defineEmits<{ font-size: 1rem; border-radius: var(--parsec-radius-6); color: var(--parsec-color-light-secondary-inversed-contrast); + flex-shrink: 0; } &-content { diff --git a/client/src/locales/en-US.json b/client/src/locales/en-US.json index b5ced24c696..e317269812d 100644 --- a/client/src/locales/en-US.json +++ b/client/src/locales/en-US.json @@ -736,6 +736,19 @@ "goToHome": "My workspaces", "recentDocuments": "Recent documents", "back": "Back", + "tabbar": { + "workspaces": "Workspaces", + "files": "Files", + "organization": "Organization", + "myProfile": "My profile", + "newWorkspace": "New workspace", + "newFolder": "New folder", + "importFile": "Import file", + "importFolder": "Import folder", + "scanDocument": "Scan document", + "importPhoto": "Import photo", + "addUser": "Add user" + }, "trial": { "tag": "Trial version", "description": "You are currently on the free trial version.", diff --git a/client/src/locales/fr-FR.json b/client/src/locales/fr-FR.json index b2e5f7a912f..d5ce07fac4a 100644 --- a/client/src/locales/fr-FR.json +++ b/client/src/locales/fr-FR.json @@ -736,6 +736,19 @@ "goToHome": "Mes espaces", "recentDocuments": "Documents récents", "back": "Retour", + "tabbar": { + "workspaces": "Espaces de travail", + "files": "Fichiers", + "organization": "Organisation", + "myProfile": "Mon profil", + "newWorkspace": "Créer un espace de travail", + "newFolder": "Nouveau dossier", + "importFile": "Importer un fichier", + "importFolder": "Importer un dossier", + "scanDocument": "Scanner un document", + "importPhoto": "Importer une photo", + "addUser": "Ajouter un utilisateur" + }, "trial": { "tag": "Version d'essai", "description": "Vous êtes actuellement sur la version d'essai gratuite.", diff --git a/client/src/router/checks.ts b/client/src/router/checks.ts index 9f55af9e3ba..59dfd571786 100644 --- a/client/src/router/checks.ts +++ b/client/src/router/checks.ts @@ -2,7 +2,7 @@ import { WorkspaceHandle } from '@/parsec'; import { getConnectionHandle, getWorkspaceHandle } from '@/router/params'; -import { Routes, getRouter } from '@/router/types'; +import { RouteBackup, Routes, getRouter, getVisitedLastHistory } from '@/router/types'; export function isLoggedIn(): boolean { return getConnectionHandle() !== null; @@ -56,6 +56,16 @@ export function currentRouteIsFileRoute(): boolean { return currentRouteIsOneOf([Routes.Documents, Routes.Workspaces]); } +export function getLastVisited(route: Routes): RouteBackup | undefined { + const history = getVisitedLastHistory(); + + return history.get(route); +} + +export function hasVisited(route: Routes): boolean { + return getLastVisited(route) !== undefined; +} + export function currentRouteIsLoggedRoute(): boolean { return currentRouteIsOneOf([ Routes.Workspaces, diff --git a/client/src/router/types.ts b/client/src/router/types.ts index 7c0be61244d..2471b18422f 100644 --- a/client/src/router/types.ts +++ b/client/src/router/types.ts @@ -57,7 +57,7 @@ const routes: Array = [ children: [ { path: '/sidebar', - component: () => import('@/views/sidebar-menu/SidebarMenuPage.vue'), + component: () => import('@/views/menu/MenuPage.vue'), children: [ { path: '/header', @@ -139,10 +139,41 @@ const router: Router = createRouter({ routes, }); +const visitedLastHistory = new Map(); + +router.beforeEach((to, from, next) => { + // Clearing history if we come from a page that has no handle. + // Not taking any risk. + if (!to.params.handle || !from.params.handle) { + visitedLastHistory.clear(); + } + if (!to.name || !to.params.handle) { + next(); + return; + } + const routeName = to.name as Routes; + if (visitedLastHistory.has(to.name as Routes)) { + visitedLastHistory.delete(routeName); + } + visitedLastHistory.set(routeName, { + handle: Number(to.params.handle), + data: { + route: routeName, + params: to.params, + query: to.query, + }, + }); + next(); +}); + export function getRouter(): Router { return router; } +export function getVisitedLastHistory(): Map { + return visitedLastHistory; +} + export function getCurrentRoute(): Ref { return (router as Router).currentRoute; } diff --git a/client/src/services/sidebarMenu.ts b/client/src/services/sidebarMenu.ts index 8221010d057..39954c8739a 100644 --- a/client/src/services/sidebarMenu.ts +++ b/client/src/services/sidebarMenu.ts @@ -3,7 +3,7 @@ import { ref } from 'vue'; const defaultWidth = 300; -const hiddenWidth = 2; +const hiddenWidth = 0; const computedWidth = ref(defaultWidth); const storedWidth = ref(defaultWidth); diff --git a/client/src/theme/components/modals.scss b/client/src/theme/components/modals.scss index a1cc83b228f..fba0fb91fa9 100644 --- a/client/src/theme/components/modals.scss +++ b/client/src/theme/components/modals.scss @@ -1,5 +1,15 @@ /* Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS */ +@import '@/theme/responsive-mixin'; + +/* -- modal-global -- */ +@include breakpoint('sm') { + ion-modal::part(content) { + width: 100%; + min-width: 100%; + } +} + /* -- modal-stepper -- */ .modal-stepper { // global diff --git a/client/src/theme/global.scss b/client/src/theme/global.scss index f8dce998d21..218567ec73b 100644 --- a/client/src/theme/global.scss +++ b/client/src/theme/global.scss @@ -10,6 +10,7 @@ @import 'components/notifications'; @import 'components/popovers'; @import 'components/tags'; +@import '@/theme/responsive-mixin'; @import 'global-client'; /**** Global CSS overloads ****/ diff --git a/client/src/theme/responsive-mixin.scss b/client/src/theme/responsive-mixin.scss new file mode 100644 index 00000000000..66cc7b28282 --- /dev/null +++ b/client/src/theme/responsive-mixin.scss @@ -0,0 +1,37 @@ +/* Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS */ + +$breakpoints: ( + 'xs': ( + max-width: 576px, + ), + 'sm': ( + max-width: 768px, + ), + 'md': ( + max-width: 992px, + ), + 'lg': ( + max-width: 1200px, + ), + 'wide': ( + max-width: 1500px, + ), + 'ultra-wide': ( + max-width: 1921px, + ), +) !default; + +@mixin breakpoint($breakpoint) { + // If the key exists in the map + @if map-has-key($breakpoints, $breakpoint) { + // Prints a media query based on the value + @media #{inspect(map-get($breakpoints, $breakpoint))} { + @content; + } + } + // If the key doesn't exist in the map + @else { + @warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. " + + "Available breakpoints are: #{map-keys($breakpoints)}."; + } +} diff --git a/client/src/views/client-area/ClientAreaPage.vue b/client/src/views/client-area/ClientAreaPage.vue index af43bb3ad30..141d76e55e3 100644 --- a/client/src/views/client-area/ClientAreaPage.vue +++ b/client/src/views/client-area/ClientAreaPage.vue @@ -300,13 +300,6 @@ function getTitleByPage(): Translatable { height: 100%; } -// -------- sidebar ------------ -ion-split-pane { - --side-min-width: var(--parsec-sidebar-menu-min-width); - --side-max-width: var(--parsec-sidebar-menu-max-width); - --side-width: v-bind(sidebarWidthProperty); -} - .resize-divider { width: 0.25rem; height: 100%; @@ -332,6 +325,9 @@ ion-split-pane { } .sidebar { + --side-min-width: var(--parsec-sidebar-menu-min-width); + --side-max-width: var(--parsec-sidebar-menu-max-width); + --side-width: v-bind(sidebarWidthProperty); --background: var(--parsec-color-light-secondary-background); border-right: 1px solid var(--parsec-color-light-secondary-disabled); user-select: none; diff --git a/client/src/views/menu/MenuPage.vue b/client/src/views/menu/MenuPage.vue new file mode 100644 index 00000000000..97847fdb572 --- /dev/null +++ b/client/src/views/menu/MenuPage.vue @@ -0,0 +1,78 @@ + + + + + + + diff --git a/client/src/views/sidebar-menu/SidebarMenuPage.vue b/client/src/views/menu/SidebarMenu.vue similarity index 55% rename from client/src/views/sidebar-menu/SidebarMenuPage.vue rename to client/src/views/menu/SidebarMenu.vue index 7f13e5746ca..e36cb02db4b 100644 --- a/client/src/views/sidebar-menu/SidebarMenuPage.vue +++ b/client/src/views/menu/SidebarMenu.vue @@ -1,367 +1,335 @@ diff --git a/client/src/views/menu/TabMenuModal.vue b/client/src/views/menu/TabMenuModal.vue new file mode 100644 index 00000000000..a627b76b765 --- /dev/null +++ b/client/src/views/menu/TabMenuModal.vue @@ -0,0 +1,131 @@ + + + + + + + + + diff --git a/client/src/views/sidebar-menu/index.ts b/client/src/views/menu/index.ts similarity index 65% rename from client/src/views/sidebar-menu/index.ts rename to client/src/views/menu/index.ts index d886e577ef1..e2e1d250eaa 100644 --- a/client/src/views/sidebar-menu/index.ts +++ b/client/src/views/menu/index.ts @@ -1,5 +1,5 @@ // Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS -import { SidebarSavedData } from '@/views/sidebar-menu/utils'; +import { SidebarSavedData } from '@/views/menu/utils'; export { SidebarSavedData }; diff --git a/client/src/views/sidebar-menu/utils.ts b/client/src/views/menu/utils.ts similarity index 100% rename from client/src/views/sidebar-menu/utils.ts rename to client/src/views/menu/utils.ts diff --git a/client/src/views/workspaces/WorkspacesPage.vue b/client/src/views/workspaces/WorkspacesPage.vue index 28644998191..85cfe70b4d5 100644 --- a/client/src/views/workspaces/WorkspacesPage.vue +++ b/client/src/views/workspaces/WorkspacesPage.vue @@ -136,16 +136,6 @@ /> - - - - - @@ -198,19 +188,7 @@ import { EventData, EventDistributor, EventDistributorKey, Events } from '@/serv import { HotkeyGroup, HotkeyManager, HotkeyManagerKey, Modifiers, Platforms } from '@/services/hotkeyManager'; import { Information, InformationLevel, InformationManager, InformationManagerKey, PresentationMode } from '@/services/informationManager'; import { StorageManager, StorageManagerKey } from '@/services/storageManager'; -import { - IonButton, - IonContent, - IonFab, - IonFabButton, - IonIcon, - IonLabel, - IonList, - IonListHeader, - IonPage, - IonText, - isPlatform, -} from '@ionic/vue'; +import { IonButton, IonContent, IonIcon, IonLabel, IonList, IonListHeader, IonPage, IonText } from '@ionic/vue'; import { addCircle } from 'ionicons/icons'; import { Ref, computed, inject, onMounted, onUnmounted, ref } from 'vue'; import { recentDocumentManager } from '@/services/recentDocuments'; diff --git a/newsfragments/9805.feature.rst b/newsfragments/9805.feature.rst new file mode 100644 index 00000000000..377c1a61d20 --- /dev/null +++ b/newsfragments/9805.feature.rst @@ -0,0 +1 @@ +Switch sidebar to tab menu on mobile