diff --git a/tdrive/frontend/public/locales/en.json b/tdrive/frontend/public/locales/en.json
index 6b24cb83e..d1a7d9557 100644
--- a/tdrive/frontend/public/locales/en.json
+++ b/tdrive/frontend/public/locales/en.json
@@ -35,7 +35,7 @@
"components.add_mails_workspace.text_area_placeholder": "Enter emails of your users*",
"components.alert.confirm": "Confirm your action",
"components.alert.confirm_click": "Confirm your action by clicking on OK.",
- "components.create_folder_modal.hint": "Choose a name for the new folder.",
+ "components.create_folder_modal.hint": "Create a folder",
"components.create_folder_modal.placeholder": "Folder name",
"components.create_link_modal.button": "Create link",
"components.create_link_modal.hint": "Link name",
@@ -48,6 +48,7 @@
"components.disk_usage.in_trash": "in trash",
"components.disk_usage.of": "of",
"components.disk_usage.used": "used",
+ "components.disk_usage.title": "Storage",
"components.dragndrop_info_move_to": "move to",
"components.drive_dropzone.uploading": "Uploading...",
"components.header_path.my_trash": "My Trash",
diff --git a/tdrive/frontend/public/locales/fr.json b/tdrive/frontend/public/locales/fr.json
index a10a2ecc1..ab5d89581 100644
--- a/tdrive/frontend/public/locales/fr.json
+++ b/tdrive/frontend/public/locales/fr.json
@@ -48,6 +48,7 @@
"components.disk_usage.in_trash": "dans la corbeille",
"components.disk_usage.of": "sur",
"components.disk_usage.used": "utilisé(s)",
+ "components.disk_usage.title": "Stockage",
"components.dragndrop_info_move_to": "déplacé vers",
"components.drive_dropzone.uploading": "Téléchargement...",
"components.header_path.my_trash": "Ma corbeille",
diff --git a/tdrive/frontend/public/locales/ru.json b/tdrive/frontend/public/locales/ru.json
index fd84e29bd..14ae78570 100644
--- a/tdrive/frontend/public/locales/ru.json
+++ b/tdrive/frontend/public/locales/ru.json
@@ -48,6 +48,7 @@
"components.disk_usage.in_trash": "в корзине",
"components.disk_usage.of": "из",
"components.disk_usage.used": "использовано",
+ "components.disk_usage.title": "хранилище",
"components.dragndrop_info_move_to": "move to",
"components.drive_dropzone.uploading": "Загрузка...",
"components.header_path.my_trash": "Корзина \"Моего диска\"",
diff --git a/tdrive/frontend/public/locales/vi.json b/tdrive/frontend/public/locales/vi.json
index 4cbf011a5..d56444b3b 100644
--- a/tdrive/frontend/public/locales/vi.json
+++ b/tdrive/frontend/public/locales/vi.json
@@ -35,7 +35,7 @@
"components.add_mails_workspace.text_area_placeholder": "Nhập email của người dùng của bạn*",
"components.alert.confirm": "Xác nhận hành động của bạn",
"components.alert.confirm_click": "Xác nhận hành động của bạn bằng cách nhấp vào OK.",
- "components.create_folder_modal.hint": "Chọn tên cho thư mục mới.",
+ "components.create_folder_modal.hint": "Tạo folder",
"components.create_folder_modal.placeholder": "Tên thư mục",
"components.create_link_modal.button": "Tạo liên kết",
"components.create_link_modal.hint": "Tên liên kết",
@@ -46,6 +46,7 @@
"components.create_modal.upload_folders": "Tải lên thư mục từ thiết bị",
"components.disk_usage.in_trash": "trong thùng rác",
"components.disk_usage.used": "đã sử dụng",
+ "components.disk_usage.title": "Lưu trữ",
"components.dragndrop_info_move_to": "di chuyển đến",
"components.drive_dropzone.uploading": "Đang tải lên...",
"components.header_path.my_trash": "Thùng rác của tôi",
diff --git a/tdrive/frontend/src/app/components/menus/menu.jsx b/tdrive/frontend/src/app/components/menus/menu.jsx
index 1200adec3..26715fb10 100755
--- a/tdrive/frontend/src/app/components/menus/menu.jsx
+++ b/tdrive/frontend/src/app/components/menus/menu.jsx
@@ -64,7 +64,8 @@ export default class Menu extends React.Component {
elementRect,
this.props.position,
undefined,
- this.props.testClassId
+ this.props.testClassId,
+ this.props.enableMobileMenu,
);
this.setState({ isMenuOpen: true }, () => this.open = true);
this.open = true;
diff --git a/tdrive/frontend/src/app/components/menus/menus-body-layer.jsx b/tdrive/frontend/src/app/components/menus/menus-body-layer.jsx
index 32bd8018b..619d76711 100755
--- a/tdrive/frontend/src/app/components/menus/menus-body-layer.jsx
+++ b/tdrive/frontend/src/app/components/menus/menus-body-layer.jsx
@@ -1,9 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import MenusManager from '@components/menus/menus-manager.jsx';
-import MenuComponent from './menu-component.jsx';
+import MenusManager from '@components/menus/menus-manager';
+import MenuComponent from './menu-component';
import OutsideClickHandler from 'react-outside-click-handler';
+import MobileMenu from './mobile-menu';
/*
Where the menu will be displayed, this component should be in app.js (menus should be over all elements of the page)
@@ -194,24 +195,45 @@ export default class MenusBodyLayer extends React.Component {
marginLeft: item.position.marginLeft,
}}
>
-
+ {item.enableMobileMenu ? (
+
+ ) : (
+
+ )}
);
diff --git a/tdrive/frontend/src/app/components/menus/menus-manager.jsx b/tdrive/frontend/src/app/components/menus/menus-manager.jsx
index 744b50e66..5249fe24b 100755
--- a/tdrive/frontend/src/app/components/menus/menus-manager.jsx
+++ b/tdrive/frontend/src/app/components/menus/menus-manager.jsx
@@ -57,7 +57,7 @@ class MenusManager extends Observable {
this.notify();
}
- async openMenu(menu, domRect, positionType, options, menuTestClassId) {
+ async openMenu(menu, domRect, positionType, options, menuTestClassId, enableMobileMenu) {
this.isOpen = 1;
if(typeof menu === 'function') {
menu = await menu();
@@ -86,6 +86,7 @@ class MenusManager extends Observable {
id: Number.unid(),
allowClickOut: options.allowClickOut !== undefined ? options.allowClickOut : true,
menuTestClassId,
+ enableMobileMenu,
});
this.last_opened_id = Number.unid();
this.notify();
diff --git a/tdrive/frontend/src/app/components/menus/mobile-menu.jsx b/tdrive/frontend/src/app/components/menus/mobile-menu.jsx
new file mode 100644
index 000000000..69b372c69
--- /dev/null
+++ b/tdrive/frontend/src/app/components/menus/mobile-menu.jsx
@@ -0,0 +1,161 @@
+import React, { Component } from 'react';
+
+import Icon from '@components/icon/icon.jsx';
+import { Modal, ModalContent } from '@atoms/modal';
+import Emojione from 'components/emojione/emojione';
+import MenusManager from '@components/menus/menus-manager.jsx';
+import './menu.scss';
+
+/*
+ One menu
+*/
+export default class MobileMenu extends React.Component {
+ constructor(props) {
+ super();
+ this.state = {
+ menus_manager: MenusManager,
+ };
+ MenusManager.addListener(this);
+ }
+ componentWillUnmount() {
+ MenusManager.removeListener(this);
+ }
+ openSubMenu(dom_element, menu, level) {
+ var elementRect = window.getBoundingClientRect(dom_element);
+ elementRect.x = elementRect.x || elementRect.left;
+ elementRect.y = elementRect.y || elementRect.top;
+ MenusManager.openSubMenu(menu, elementRect, level);
+ }
+ closeSubMenu(level) {
+ MenusManager.closeSubMenu(level);
+ }
+ hoverMenu(dom_element, item) {
+ if (item.submenu && !item.submenu_replace) {
+ this.last_hovered = item;
+ this.openSubMenu(dom_element, item.submenu, this.props.level || 0);
+ } else {
+ this.closeSubMenu(this.props.level || 0);
+ }
+ }
+ clickMenu(dom_element, item, evt) {
+ if(Date.now() - this.props.openAt < 200 ){
+ // When a menu is open and another one opens above it, you have to block the buttons for a while. Otherwise, the hovered option of the new menu will be clicked
+ return;
+ }
+ if (item.submenu_replace) {
+ this.state.menus_manager.openMenu(item.submenu, { x: evt.clientX, y: evt.clientY }, 'center');
+ return;
+ }
+ if (item.onClick) {
+ var res = item.onClick(evt);
+ if (res !== false) {
+ this.state.menus_manager.closeMenu();
+ }
+ }
+ }
+ render() {
+ return (
+ this.state.menus_manager.closeMenu()}
+ className={`md:!max-w-sm testid:${this.props.testClassId}`}
+ disableCountVisibleModals={true}
+ >
+
+ (this.original_menu = node)}
+ className={
+ 'menu-list sm:py-0 ' + (this.props.withFrame ? 'text-black bg-white dark:bg-zinc-900 dark:text-white rounded-lg ' : '') + this.props.animationClass
+ }>
+ {(this.props.menu || [])
+ .filter(item => item && !item.hide)
+ .map((item, index) => {
+ if (item.type == 'separator') {
+ return
;
+ } else if (item.type == 'title') {
+ return (
+
+ {item.text}
+
+ );
+ } else if (item.type == 'text') {
+ return (
+
(item.ref = node)}
+ className={'menu-text ' + item.className + ' testid:menu-item'}
+ onMouseEnter={evt => {
+ this.hoverMenu(item.ref, item);
+ }}
+ >
+ {item.icon && (
+
+ {typeof item.icon === 'string' ? : item.icon}
+
+ )}
+
{item.text}
+
+ );
+ } else if (item.type == 'react-element') {
+ return (
+
+ {typeof item.reactElement == 'function'
+ ? item.reactElement(this.props.level)
+ : item.reactElement}
+
+ );
+ } else {
+ return (
+
(item.ref = node)}
+ className={
+ 'menu ' +
+ item.className +
+ ' ' +
+ (this.state.menus_manager.max_level > this.props.level &&
+ this.last_hovered == item
+ ? 'hovered '
+ : '') +
+ (item.selected ? 'selected ' : '') +
+ ' testid:menu-item'
+ }
+ onMouseEnter={evt => {
+ this.hoverMenu(item.ref, item);
+ }}
+ onClick={evt => {
+ this.clickMenu(item.ref, item, evt);
+ }}
+ >
+ {item.icon && (
+
+ {typeof item.icon === 'string' ? : item.icon}
+
+ )}
+ {item.emoji && (
+
+
+
+ )}
+
{item.text}
+
+ {item.rightIcon && }
+ {item.submenu && !item.submenu_replace && }
+
+
+ );
+ }
+ })
+ }
+
+
+
+
+ );
+ }
+}
diff --git a/tdrive/frontend/src/app/views/client/body/drive/browser.tsx b/tdrive/frontend/src/app/views/client/body/drive/browser.tsx
index f9d8f215c..2cb75b6a4 100644
--- a/tdrive/frontend/src/app/views/client/body/drive/browser.tsx
+++ b/tdrive/frontend/src/app/views/client/body/drive/browser.tsx
@@ -224,7 +224,8 @@ export default memo(
key: index,
className:
(index === 0 ? 'rounded-t-md ' : '-mt-px ') +
- (index === items.length - 1 ? 'rounded-b-md ' : ''),
+ (index === items.length - 1 ? 'rounded-b-md ' : '') +
+ 'border-0 md:border',
item: child,
checked: checked[child.id] || false,
onCheck: (v: boolean) => setChecked(_.pickBy({ ...checked, [child.id]: v }, _.identity)),
@@ -358,7 +359,7 @@ export default memo(
(loading && (!items?.length || loadingParentChange) ? 'opacity-50 ' : '')
}
>
-
+
{sharedWithMe ? (
@@ -445,14 +446,14 @@ export default memo(
{access !== 'read' && (
-
+
{formatBytes(item?.size || 0)} {Languages.t('scenes.app.drive.used')}
)}
-
-
{item.name}
+
+ {item.name}
- {hasAnyPublicLinkAccess(item) &&
}
+ {hasAnyPublicLinkAccess(item) &&
}
-
+
{formatDateShort(item?.last_version_cache?.date_added)}
- {formatBytes(item.size)}
+ {formatBytes(item.size)}
{FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_AV_ENABLED) && (
@@ -102,11 +104,11 @@ export const DocumentRow = ({
)}
-
- {item.name}
+ {item.name}
{hasAnyPublicLinkAccess(item) && (
-
+
)}
- {formatBytes(item.size)}
+ {formatBytes(item.size)}
-
+
diff --git a/tdrive/frontend/src/app/views/client/body/drive/header-path.tsx b/tdrive/frontend/src/app/views/client/body/drive/header-path.tsx
index 39d802cca..cb3437171 100644
--- a/tdrive/frontend/src/app/views/client/body/drive/header-path.tsx
+++ b/tdrive/frontend/src/app/views/client/body/drive/header-path.tsx
@@ -1,6 +1,6 @@
import { Title } from '@atoms/text';
import { DriveItem } from '@features/drive/types';
-import { ChevronDownIcon } from '@heroicons/react/solid';
+import { ChevronDownIcon, ChevronLeftIcon } from '@heroicons/react/solid';
import { useEffect, useState } from 'react';
import { PublicIcon } from './components/public-icon';
import MenusManager from '@components/menus/menus-manager.jsx';
@@ -74,46 +74,65 @@ export const PathRender = ({
const pathLength = (pathToRender || []).reduce((acc, curr) => acc + curr.name.length, 0);
return (
-
+ <>
+
+
+ >
);
};
@@ -121,11 +140,13 @@ const PathItem = ({
item,
first,
last,
+ isPreviousItemInMobile,
onClick,
}: {
item: Partial;
last?: boolean;
first?: boolean;
+ isPreviousItemInMobile?: boolean;
onClick: (viewId: string, dirId: string) => void;
}) => {
const { user } = useCurrentUser();
@@ -181,11 +202,15 @@ const PathItem = ({
}
}}
>
-
+
{(() => {
const isTrash = viewId?.includes('trash_') || viewId === 'trash';
const fileName = cutFileName(item?.name) || '';
+ if (isPreviousItemInMobile && fileName) {
+ return
+ }
+
if (first) {
if (isTrash) {
return viewId?.includes('trash_')
@@ -202,15 +227,15 @@ const PathItem = ({
})()}
- {hasAnyPublicLinkAccess(item) && (
+ {hasAnyPublicLinkAccess(item) && !isPreviousItemInMobile && (
)}
- {first && !!user?.id && viewId?.includes('trash') && (
+ {first && !!user?.id && viewId?.includes('trash') && !isPreviousItemInMobile && (
)}
- {!last && (
+ {!last && !isPreviousItemInMobile && (