From 579b64972e5daa74abf3d60ceb72d314b63b84fe Mon Sep 17 00:00:00 2001 From: Eric Doughty-Papassideris Date: Sat, 6 Apr 2024 23:21:18 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=84=20Applied=20sharing=20dialog=20cha?= =?UTF-8?q?nges=20from=20figme=20(#50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://www.figma.com/proto/CWz4DatnJ7Qhl3JycJhbFY/TDrive---MUI3?type=design&node-id=1355-9893&t=7v0ocJCf6knnPDan-0&scaling=min-zoom&page-id=1189%3A23493&starting-point-node-id=1347%3A8530 --- tdrive/frontend/public/locales/en.json | 24 +- tdrive/frontend/public/locales/es.json | 8 +- tdrive/frontend/public/locales/fr.json | 12 +- tdrive/frontend/public/locales/ru.json | 15 +- tdrive/frontend/public/locales/vn.json | 13 +- .../frontend/src/app/atoms/button/button.tsx | 6 +- .../src/app/atoms/input/input-checkbox.tsx | 14 +- .../src/app/atoms/input/input-select.tsx | 4 +- .../src/app/atoms/input/input-text.tsx | 25 +- tdrive/frontend/src/app/atoms/modal/index.tsx | 3 +- .../body/drive/components/select-users.tsx | 4 +- .../drive/modals/update-access/common.tsx | 16 +- .../cute-depiction-of-folder-hierarchy.tsx | 29 ++ .../body/drive/modals/update-access/index.tsx | 124 ++++++++- .../update-access/inherit-access-options.tsx | 137 ++++++++++ .../modals/update-access/internal-access.tsx | 125 +-------- .../public-link-access-options.tsx | 120 +++++++++ .../update-access/public-link-access.tsx | 255 ++++++------------ 18 files changed, 564 insertions(+), 370 deletions(-) create mode 100644 tdrive/frontend/src/app/views/client/body/drive/modals/update-access/cute-depiction-of-folder-hierarchy.tsx create mode 100644 tdrive/frontend/src/app/views/client/body/drive/modals/update-access/inherit-access-options.tsx create mode 100644 tdrive/frontend/src/app/views/client/body/drive/modals/update-access/public-link-access-options.tsx diff --git a/tdrive/frontend/public/locales/en.json b/tdrive/frontend/public/locales/en.json index 06e74e41..e8942fdb 100644 --- a/tdrive/frontend/public/locales/en.json +++ b/tdrive/frontend/public/locales/en.json @@ -196,16 +196,19 @@ "components.locked_features.locked_only_office_popup.text": "Whoops!! Seems like you want to edit the file from Tdrive. You might want to consider upgrading your plan if you want to collaborate around office files. Explore our pricing options and select the best option for your team collaboration needs.", "components.locked_features.locked_drive_popup.title": "You have just reached the drive limit", "components.locked_features.locked_drive_popup.subtitle": "In the free version, you can store only 6GB.", - "components.public-link-acess.public_link_acess": "Public link acess", - "components.public-link-acess.info_acess_true": "Anyone with this link will have access to this item.", - "components.public-link-acess.info_acess_false": "This item is not available by public link.", + "components.public-link-acess.public-link-placeholder": "https://tdrive.com/your-simple-link", + "components.public-link-acess.public_link_access": "Share via link", "components.public-link-acess.info_acess_expired": "Expired", - "components.public-link-copied-info" : "Public link copied to clipboard", - "components.public-link-copy" : "Copy public link to clip board", + "components.public-link-copied-info" : "Copied", + "components.public-link-get" : "Create link", + "components.public-link-copy" : "Copy link", + "components.public-link-access-level-create" : "Anyone with the link will have access to", + "components.public-link-access-level-update" : "Anyone with the link has access to", + "components.public-link-access-level-delete" : "Delete link", "components.public-link-security": "Public link security", - "components.public-link-security_password": "Password", - "components.public-link-security_password_copied": "Password copied to clipboard", - "components.public-link-security_expired": "Expiration", + "components.public-link-security_password": "Password", + "components.public-link-security_password_copied": "Password copied to clipboard", + "components.public-link-security_expired": "Expiration", "components.internal-access_access_manage":"General access management", "components.internal-access_inherit_parent":"Inherit parent folder", "components.internal-access_inherit_parent_perm":"Choose to inherit or not the parent folder permissions.", @@ -304,7 +307,8 @@ "components.item_context_menu.download": "Download", "components.item_context_menu.rename": "Rename", "components.item_context_menu.manage_access": "Manage access", - "components.item_context_menu.manage_access_to": "Manage access to", + "components.item_context_menu.manage_access_to": "Share", + "components.item_context_menu.manage_access_advanced_to": "Manage access to", "components.item_context_menu.share": "Share", "components.item_context_menu.copy_link": "Copy link", "components.item_context_menu.copy_link.success": "Link copied to clipboard", @@ -342,5 +346,5 @@ "scenes.app.shared_with_me.edit": "Edit", "ONLYOFFICE Word Document": "Create a Document", "ONLYOFFICE Excel Document": "Create a Spreadsheet", - "ONLYOFFICE PowerPoint Document": "Create a Presentation" + "ONLYOFFICE PowerPoint Document": "Create a Presentation" } \ No newline at end of file diff --git a/tdrive/frontend/public/locales/es.json b/tdrive/frontend/public/locales/es.json index ceb9e35e..4053534c 100644 --- a/tdrive/frontend/public/locales/es.json +++ b/tdrive/frontend/public/locales/es.json @@ -123,15 +123,13 @@ "components.locked_features.locked_only_office_popup.text": "¡Ups! Parece que desea editar el archivo de Tdrive. Es posible que desee considerar la posibilidad de actualizar su plan si desea colaborar en torno a los archivos de Office. Explore nuestras opciones de precios y seleccione la mejor opción para las necesidades de colaboración de su equipo.", "components.locked_features.locked_drive_popup.title": "No puedes subir mas archivos", "components.locked_features.locked_drive_popup.subtitle": "En la versión gratuita, puede almacenar solo 6 GB.", - "components.public-link-acess.public_link_acess": "Accesibilidad del enlace público", - "components.public-link-acess.info_acess_true": "Cualquier persona con a este enlace tiene acceso al archivo.", - "components.public-link-acess.info_acess_false": "Este archivo no está disponible por enlace público.", + "components.public-link-acess.public_link_access": "Accesibilidad del enlace público", "components.public-link-acess.info_acess_expired": "Expirado", "components.public-link-copied-info" : "Enlace público copiado en el portapapeles", "components.public-link-copy" : "Copiar enlace en el portapapeles", "components.public-link-security": "Seguridad del enlace público", - "components.public-link-security_password": "Contraseña", - "components.public-link-security_password_copied": "Contraseña copiada en el portapapeles", + "components.public-link-security_password": "Contraseña", + "components.public-link-security_password_copied": "Contraseña copiada en el portapapeles", "components.public-link-security_expired": "Expiración", "scenes.app.popup.adduser.adresses_message": "Por favor, separe las direcciones con una coma o un espacio. Recuerda que Tdrive puede limitar el número de invitaciones en función de tu suscripción.", "scenes.app.channelsbar.currentuser.logout": "Cerrar sesión", diff --git a/tdrive/frontend/public/locales/fr.json b/tdrive/frontend/public/locales/fr.json index a7132d20..021923a2 100644 --- a/tdrive/frontend/public/locales/fr.json +++ b/tdrive/frontend/public/locales/fr.json @@ -173,12 +173,11 @@ "components.locked_features.locked_only_office_popup.text": "Oups !! Vous souhaitez modifier le fichier à partir de Twake Drive. Vous voudrez peut-être envisager de mettre à niveau votre plan si vous souhaitez collaborer autour de fichiers bureautiques. Explorez nos options de tarification et sélectionnez la meilleure option pour les besoins de collaboration de votre équipe.", "components.locked_features.locked_drive_popup.title": "Vous venez d'atteindre la limite d'importation", "components.locked_features.locked_drive_popup.subtitle": "Dans la version gratuite, vous ne pouvez pas stocker plus de 6 Go.", - "components.public-link-acess.public_link_acess": "Lien d'accès public", - "components.public-link-acess.info_acess_true": "N'importe qui ayant ce lien aura accès à ce fichier", - "components.public-link-acess.info_acess_false": "Ce fichier n'est pas accessible via un lien public", + "components.public-link-acess.public_link_access": "Partager avec un lien", "components.public-link-acess.info_acess_expired": "Expiré", - "components.public-link-copied-info": "Le lien public a été copié dans le presse-papiers", - "components.public-link-copy": "Copier le lien public dans le presse-papiers", + "components.public-link-get" : "Créer un lien", + "components.public-link-copied-info": "Copié", + "components.public-link-copy": "Copier le lien", "components.public-link-security": "Sécurité du lien public", "components.public-link-security_password": "Mot de passe", "components.public-link-security_password_copied": "Mot de passe copié dans le presse-papiers", @@ -297,7 +296,8 @@ "components.item_context_menu.download": "Télécharger", "components.item_context_menu.rename": "Renommer", "components.item_context_menu.manage_access": "Gérer l'accès", - "components.item_context_menu.manage_access_to": "Gérer l'accès à", + "components.item_context_menu.manage_access_to": "Partage de", + "components.item_context_menu.manage_access_advanced_to": "Gérer l'accès de", "components.item_context_menu.share": "Partager", "components.item_context_menu.copy_link": "Copier le lien", "components.item_context_menu.copy_link.success": "Lien copié dans le presse-papiers", diff --git a/tdrive/frontend/public/locales/ru.json b/tdrive/frontend/public/locales/ru.json index 003434fa..35d40270 100644 --- a/tdrive/frontend/public/locales/ru.json +++ b/tdrive/frontend/public/locales/ru.json @@ -173,16 +173,15 @@ "components.locked_features.locked_guests_popup.learn_more_button": "Узнать больше", "components.locked_features.locked_guests_popup.or": "Или", "scenes.client.main_view.main_header.search_input": "Поиск", - "components.public-link-acess.public_link_acess": "Public link acess", - "components.public-link-acess.info_acess_true": "Anyone with this link will have access to this item.", - "components.public-link-acess.info_acess_false": "This item is not available by public link.", + "components.public-link-acess.public_link_access": "Поделитесь ссылкой", "components.public-link-acess.info_acess_expired": "Expired", - "components.public-link-copied-info" : "Public link copied to clipboard", - "components.public-link-copy" : "Copy public link to clip board", + "components.public-link-copied-info" : "Ссылка скопирована", + "components.public-link-get" : "Создать ссылку", + "components.public-link-copy" : "Копировать ссылку", "components.public-link-security": "Public link security", - "components.public-link-security_password": "Password", - "components.public-link-security_password_copied": "Password copied to clipboard", - "components.public-link-security_expired": "Expiration", + "components.public-link-security_password": "Password", + "components.public-link-security_password_copied": "Password copied to clipboard", + "components.public-link-security_expired": "Expiration", "components.internal-access_access_manage":"General access management", "components.internal-access_inherit_parent":"Inherit parent folder", "components.internal-access_inherit_parent_perm":"Choose to inherit or not the parent folder permissions.", diff --git a/tdrive/frontend/public/locales/vn.json b/tdrive/frontend/public/locales/vn.json index 899dba12..739b1f79 100644 --- a/tdrive/frontend/public/locales/vn.json +++ b/tdrive/frontend/public/locales/vn.json @@ -67,7 +67,7 @@ "scenes.apps.account.account.main_email": "Email chính", "scenes.apps.account.account.email_add": "Thêm e-mail mới", "scenes.apps.account.account.email_add_modal.invalid_code": "Mã này không hợp lệ", - "scenes.apps.account.account.password": "Mật khẩu", + "scenes.apps.account.account.password": "Mật khẩu", "scenes.apps.account.account.password_modal.old_password": "Mật khẩu hiện tại", "scenes.apps.account.account.password_modal.password": "Mật khẩu mới", "scenes.apps.account.account.password_modal.bad_old_password": "Mật khẩu cũ không chính xác.", @@ -189,12 +189,11 @@ "components.locked_features.locked_only_office_popup.text": "Ồ, không! Có vẻ như bạn muốn chỉnh sửa tệp từ Tdrive. Bạn nên cân nhắc nâng cấp gói đăng ký nếu muốn cộng tác xung quanh các tệp văn phòng. Hãy khám phá các tùy chọn giá của chúng tôi và chọn tùy chọn phù hợp nhất với nhu cầu cộng tác nhóm của bạn.", "components.locked_features.locked_drive_popup.title": "Bạn vừa đạt đến giới hạn ổ đĩa", "components.locked_features.locked_drive_popup.subtitle": "Trong phiên bản miễn phí, bạn chỉ có thể lưu trữ 6GB.", - "components.public-link-acess.public_link_acess": "Quyền truy cập liên kết công khai", - "components.public-link-acess.info_acess_true": "Bất kỳ ai có liên kết này đều có thể truy cập vào mục này.", - "components.public-link-acess.info_acess_false": "Mục này không khả dụng theo liên kết công khai.", + "components.public-link-acess.public_link_access": "Chia sẻ bằng một liên kết", "components.public-link-acess.info_acess_expired": "Hết hạn", - "components.public-link-copied-info": "Liên kết công khai đã được sao chép vào khay nhớ tạm", - "components.public-link-copy": "Sao chép liên kết công khai vào khay nhớ tạm", + "components.public-link-copied-info": "Đã sao chép liên kết", + "components.public-link-get" : "Tạo đường link", + "components.public-link-copy": "Sao chép đường dẫn", "components.public-link-security": "Bảo mật liên kết công khai", "components.public-link-security_password": "Mật khẩu", "components.public-link-security_password_copied": "Mật khẩu đã được sao chép vào khay nhớ tạm", @@ -270,7 +269,7 @@ "components.side_menu.buttons.empty_trash": "Làm trống thùng rác", "components.header_path.shared_trash": "Thùng rác được chia sẻ", "components.header_path.my_trash": "Thùng rác của tôi", - "components.header_path.shared_with_me": "Chia sẻ với tôi", + "components.header_path.shared_with_me": "Chia sẻ với tôi", "scenes.app.drive.nothing": "Không có gì ở đây.", "scenes.app.drive.drag_and_drop": "Kéo và thả tệp để tải lên hoặc nhấp vào nút 'Thêm tài liệu'.", "scenes.app.drive.add_doc": "Thêm tài liệu hoặc thư mục", diff --git a/tdrive/frontend/src/app/atoms/button/button.tsx b/tdrive/frontend/src/app/atoms/button/button.tsx index 2548a283..b66b4fbd 100644 --- a/tdrive/frontend/src/app/atoms/button/button.tsx +++ b/tdrive/frontend/src/app/atoms/button/button.tsx @@ -3,7 +3,7 @@ import React from 'react'; import _ from 'lodash'; interface ButtonProps extends React.ButtonHTMLAttributes { - theme?: 'primary' | 'secondary' | 'danger' | 'default' | 'outline' | 'dark' | 'white'; + theme?: 'primary' | 'secondary' | 'danger' | 'default' | 'outline' | 'dark' | 'white' | 'green'; size?: 'md' | 'lg' | 'sm'; icon?: (props: any) => JSX.Element; iconSize?: 'md' | 'lg'; @@ -40,6 +40,10 @@ export const Button = (props: ButtonProps) => { className = 'text-zinc-300 border-0 bg-zinc-900 hover:bg-zinc-800 hover:text-white active:bg-zinc-900'; + if (props.theme === 'green') + className = + 'text-zinc-300 border-0 bg-green-700'; + if (disabled) className += ' opacity-50 pointer-events-none'; if (props.size === 'lg') className = className + ' text-lg h-10'; diff --git a/tdrive/frontend/src/app/atoms/input/input-checkbox.tsx b/tdrive/frontend/src/app/atoms/input/input-checkbox.tsx index b5103135..102639d7 100644 --- a/tdrive/frontend/src/app/atoms/input/input-checkbox.tsx +++ b/tdrive/frontend/src/app/atoms/input/input-checkbox.tsx @@ -1,8 +1,9 @@ import { CheckOutlineIcon } from '../icons-agnostic'; -import { BaseSmall } from '../text'; +import { Base, BaseSmall } from '../text'; export const Checkbox = (props: { label?: string; + labelNormalSize?: boolean; onChange?: (status: boolean) => void; value?: boolean; className?: string; @@ -43,9 +44,14 @@ export const Checkbox = (props: { }} > {renderSwitch()} - - {props.label} - + {props.labelNormalSize + ? + {props.label} + + : + {props.label} + + } ); } else { diff --git a/tdrive/frontend/src/app/atoms/input/input-select.tsx b/tdrive/frontend/src/app/atoms/input/input-select.tsx index 971e90c2..982626b6 100644 --- a/tdrive/frontend/src/app/atoms/input/input-select.tsx +++ b/tdrive/frontend/src/app/atoms/input/input-select.tsx @@ -1,8 +1,8 @@ import _ from 'lodash'; -import { defaultInputClassName, errorInputClassName } from './input-text'; +import { defaultInputClassName, errorInputClassName, ThemeName } from './input-text'; interface InputProps extends Omit, 'size'> { - theme?: 'plain' | 'outline'; + theme?: ThemeName; hasError?: boolean; size?: 'md' | 'lg' | 'sm'; className?: string; diff --git a/tdrive/frontend/src/app/atoms/input/input-text.tsx b/tdrive/frontend/src/app/atoms/input/input-text.tsx index 437a8e7a..1f0a126f 100644 --- a/tdrive/frontend/src/app/atoms/input/input-text.tsx +++ b/tdrive/frontend/src/app/atoms/input/input-text.tsx @@ -7,7 +7,7 @@ interface InputProps React.InputHTMLAttributes & React.TextareaHTMLAttributes, 'size' > { - theme?: 'plain' | 'outline'; + theme?: ThemeName; label?: string; size?: 'sm' | 'md' | 'lg'; feedback?: string; @@ -19,21 +19,28 @@ interface InputProps inputRef?: React.Ref; } -const baseInputClassName = - 'tw-input block w-full rounded-md focus:ring-1 focus:ring-blue-500 z-0 focus:z-10 dark:text-white text-black text-base '; +export type ThemeName = 'plain' | 'outline' | 'blue' | 'rose'; -export const defaultInputClassName = (theme: 'plain' | 'outline' = 'plain') => { +const baseInputClassName = + 'tw-input block w-full rounded-md focus:ring-1 focus:ring-blue-500 z-0 focus:z-10 text-base '; +const baseTextClassName = ' dark:text-white text-black '; + +export const defaultInputClassName = (theme: ThemeName = 'plain') => { + const themeClasses = { + 'plain': 'bg-zinc-100 border-zinc-100 dark:bg-zinc-800 dark:border-zinc-800' + baseTextClassName, + 'blue': 'bg-zinc-100 border-zinc-100 dark:bg-zinc-800 dark:border-zinc-800 text-blue-700 dark:text-blue-500', + 'rose': 'text-rose-500 bg-rose-100 dark:text-rose-300 dark:bg-rose-900 border-rose-500', + 'outline': 'bg-zinc-50 border-zinc-300 dark:bg-zinc-800 dark:border-zinc-700' + baseTextClassName, + }; return ( baseInputClassName + - (theme === 'plain' - ? 'bg-zinc-100 border-zinc-100 dark:bg-zinc-800 dark:border-zinc-800' - : 'bg-zinc-50 border-zinc-300 dark:bg-zinc-800 dark:border-zinc-700') + (themeClasses[theme] || themeClasses.outline) ); }; -export const errorInputClassName = (theme: 'plain' | 'outline' = 'plain') => { +export const errorInputClassName = (theme: ThemeName = 'plain') => { return ( - baseInputClassName + + baseInputClassName + baseTextClassName + (theme === 'plain' ? 'bg-rose-200 border-rose-200 dark:bg-rose-800 dark:border-rose-800' : 'bg-rose-50 border-rose-300 dark:bg-rose-900 dark:border-rose-800') diff --git a/tdrive/frontend/src/app/atoms/modal/index.tsx b/tdrive/frontend/src/app/atoms/modal/index.tsx index 09a25643..5579b1c4 100644 --- a/tdrive/frontend/src/app/atoms/modal/index.tsx +++ b/tdrive/frontend/src/app/atoms/modal/index.tsx @@ -17,6 +17,7 @@ export const Modal = (props: { onClose?: () => void; children?: React.ReactNode; closable?: boolean; + closeIcon?: ReactNode, className?: string; style?: React.CSSProperties; positioned?: boolean; @@ -130,7 +131,7 @@ export const Modal = (props: { className="hover:opacity-75 focus:outline-none " onClick={() => props.onClose && props.onClose()} > - + {props.closeIcon ? props.closeIcon : } )} diff --git a/tdrive/frontend/src/app/views/client/body/drive/components/select-users.tsx b/tdrive/frontend/src/app/views/client/body/drive/components/select-users.tsx index 5128f8f7..d73aad97 100644 --- a/tdrive/frontend/src/app/views/client/body/drive/components/select-users.tsx +++ b/tdrive/frontend/src/app/views/client/body/drive/components/select-users.tsx @@ -90,10 +90,10 @@ export default (props: { )} /> {isFocus && query?.trim() && ( -
+
{result.length === 0 && ( -
+
{Languages.t('components.user_picker.modal_no_result')}
)} diff --git a/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/common.tsx b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/common.tsx index a4616f3e..c5384e3f 100644 --- a/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/common.tsx +++ b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/common.tsx @@ -8,6 +8,7 @@ export const AccessLevel = ({ onChange, canRemove, hiddenLevels, + labelOverrides, className, }: { disabled?: boolean; @@ -15,23 +16,26 @@ export const AccessLevel = ({ onChange: (level: DriveFileAccessLevel & 'remove') => void; canRemove?: boolean; className?: string; + labelOverrides?: { [key: string]: string }; hiddenLevels?: string[]; }) => { + const createOption = (level: DriveFileAccessLevel, tKey: string) => + !hiddenLevels?.includes(level) && ; return ( ); diff --git a/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/cute-depiction-of-folder-hierarchy.tsx b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/cute-depiction-of-folder-hierarchy.tsx new file mode 100644 index 00000000..339a21a6 --- /dev/null +++ b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/cute-depiction-of-folder-hierarchy.tsx @@ -0,0 +1,29 @@ +import { Base } from '@atoms/text'; +import { FolderIcon } from '@heroicons/react/outline'; +import type { DriveItem } from 'app/features/drive/types'; + +export const CuteDepictionOfFolderHierarchy = (props: { + file?: DriveItem, + parent?: DriveItem, +}) => + <> + +
+ +
+
+ {props.parent?.name} +
+ + +
+ └ +
+
+ +
+
+ {props.file?.name} +
+ + ; diff --git a/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/index.tsx b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/index.tsx index 98300121..d01e014b 100644 --- a/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/index.tsx +++ b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/index.tsx @@ -1,6 +1,8 @@ +import A from '@atoms/link'; +import { Info } from '@atoms/text'; import { Modal, ModalContent } from '@atoms/modal'; import { useDriveItem } from '@features/drive/hooks/use-drive-item'; -import { useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { atom, useRecoilState } from 'recoil'; import { InternalAccessManager } from './internal-access'; import { PublicLinkManager } from './public-link-access'; @@ -9,6 +11,10 @@ import Languages from 'features/global/services/languages-service'; import FeatureTogglesService, { FeatureNames, } from '@features/global/services/feature-toggles-service'; +import { ArrowLeftIcon, LockClosedIcon } from '@heroicons/react/outline'; +import { PublicLinkAccessOptions } from './public-link-access-options'; +import { CuteDepictionOfFolderHierarchy } from './cute-depiction-of-folder-hierarchy'; +import { InheritAccessOptions } from './inherit-access-options'; export type AccessModalType = { open: boolean; @@ -25,29 +31,127 @@ export const AccessModalAtom = atom({ export const AccessModal = () => { const [state, setState] = useRecoilState(AccessModalAtom); + const [isOnAdvancedScreen, setIsOnAdvancedScreen] = useState(false); return ( - setState({ ...state, open: false })}> - {!!state.id && } + { + if (isOnAdvancedScreen) + setIsOnAdvancedScreen(false); + else + setState({ ...state, open: false }); + }} + closeIcon={isOnAdvancedScreen && } + > + {!!state.id && + setIsOnAdvancedScreen(active)} + />} ); }; -const AccessModalContent = ({ id }: { id: string }) => { - const { item, access, refresh } = useDriveItem(id); +const SwitchToAdvancedSettingsPanel = (props: { + disabled: boolean, + onShowAdvancedScreen: (active: boolean) => void, +}) => +
+
+ +
+
+
Advanced security settings
+
Set password, expiration date, etc.
+
+ +
; + +const AccessModalContent = (props: { + id: string, + isOnAdvancedScreen: boolean, + onShowAdvancedScreen: (active: boolean) => void, +}) => { + const { id } = props; + const { item, access, loading, update, refresh } = useDriveItem(id); + const { item: parentItem } = useDriveItem(item?.parent_id || ''); const { company, refresh: refreshCompany } = useCurrentCompany(); useEffect(() => { refresh(id); refreshCompany(); }, []); + const havePublicLink = (item?.access_info?.public?.level || 'none') !== 'none'; + const haveAdvancedSettings = parentItem?.parent_id !== null || havePublicLink; + + const updatePublicAccess = (key: string, value: string | number, skipLoading?: true) => + update({ + access_info: { + entities: item?.access_info.entities || [], + public: { + ...item!.access_info!.public!, + [key]: value || '', + }, + }, + }, skipLoading); + return ( - - {FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_SEARCH_USERS) && ( - - )} + {!props.isOnAdvancedScreen ? + <> + + + {FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_SEARCH_USERS) && ( + + )} + + {haveAdvancedSettings && } + : <> + {havePublicLink && + { + updatePublicAccess('password', password || '', true); + }} + onChangeExpiration={(expiration: number) => { + updatePublicAccess('expiration', expiration || 0); + }} + />} + { parentItem?.parent_id !== null && <> + + + } + } ); }; diff --git a/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/inherit-access-options.tsx b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/inherit-access-options.tsx new file mode 100644 index 00000000..bbc4d211 --- /dev/null +++ b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/inherit-access-options.tsx @@ -0,0 +1,137 @@ +import Languages from 'features/global/services/languages-service'; + +import type { DriveItem } from 'app/features/drive/types'; + +import { Base, Info, Subtitle } from '@atoms/text'; +import { Checkbox } from '@atoms/input/input-checkbox'; +import { AccessLevel } from './common'; +import AlertManager from '@features/global/services/alert-manager-service'; + +export const InheritAccessOptions = (props: { + item?: DriveItem, + disabled: boolean, + onUpdate: (item: Partial) => void, +}) => { + const folderEntity = props.item?.access_info.entities.filter(a => a.type === 'folder')?.[0] || { + type: 'folder', + id: 'parent', + level: 'manage', + }; + const companyEntity = props.item?.access_info.entities.filter(a => a.type === 'company')?.[0]; + const channelEntities = props.item?.access_info.entities.filter(a => a.type === 'channel') || []; + + return ( + <> + {(companyEntity || folderEntity?.level === 'none' || channelEntities.length > 0) && + {Languages.t('components.internal-access_access_manage')}} + + {folderEntity && ( +
+
+ {Languages.t('components.internal-access_inherit_parent')} +
+ {Languages.t('components.internal-access_inherit_parent_perm')} +
+
+ { + props.onUpdate({ + access_info: { + entities: [ + ...(props.item?.access_info.entities.filter(a => a.type !== 'folder') || []), + { ...folderEntity, level: status ? 'manage' : 'none' }, + ], + public: props.item?.access_info.public, + }, + }); + }} + value={folderEntity.level === 'manage'} + /> +
+
+ )} + + {companyEntity && folderEntity.level === 'none' && ( +
+
+ {Languages.t('components.internal-access_company_member')} +
+
+ { + props.onUpdate({ + access_info: { + entities: [ + ...(props.item?.access_info.entities.filter(a => a.type !== 'company') || []), + ...(level !== 'remove' ? [{ ...companyEntity, level }] : []), + ], + public: props.item?.access_info.public, + }, + }); + }} + level={companyEntity.level} + /> +
+
+ )} + + {channelEntities.length > 0 && ( +
+
+ {Languages.t('components.internal-access_cannal')} +
+ + {channelEntities.length} {Languages.t('components.internal-access_cannal_info')} + +
+
+ { + if (level === 'remove') { + AlertManager.confirm( + async () => { + //Remove channel access + props.onUpdate({ + access_info: { + entities: + props.item?.access_info?.entities.filter(e => e.type !== 'channel') || [], + public: props.item?.access_info.public, + }, + }); + }, + () => { + //Do nothing + }, + { + text: Languages.t('components.internal-access_cannal_info_give_back'), + }, + ); + } else { + props.onUpdate({ + access_info: { + entities: + props.item?.access_info?.entities.map(e => { + if (e.type === 'channel') { + return { ...e, level }; + } + return e; + }) || [], + public: props.item?.access_info.public, + }, + }); + } + }} + level={channelEntities[0].level} + /> +
+
+ )} + + ); +}; + diff --git a/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/internal-access.tsx b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/internal-access.tsx index 2a64d098..612f11a4 100644 --- a/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/internal-access.tsx +++ b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/internal-access.tsx @@ -1,9 +1,7 @@ import Avatar from '@atoms/avatar'; -import { Checkbox } from '@atoms/input/input-checkbox'; import { Base, Info } from '@atoms/text'; import { useDriveItem } from '@features/drive/hooks/use-drive-item'; import { DriveFileAccessLevel } from '@features/drive/types'; -import AlertManager from '@features/global/services/alert-manager-service'; import { useCurrentUser } from '@features/users/hooks/use-current-user'; import { useUser } from '@features/users/hooks/use-user'; import currentUserService from '@features/users/services/current-user-service'; @@ -15,132 +13,11 @@ import Languages from 'features/global/services/languages-service'; export const InternalAccessManager = ({ id, disabled }: { id: string; disabled: boolean }) => { - const { item, loading, update } = useDriveItem(id); - + const { item } = useDriveItem(id); const userEntities = item?.access_info.entities.filter(a => a.type === 'user') || []; - const folderEntity = item?.access_info.entities.filter(a => a.type === 'folder')?.[0] || { - type: 'folder', - id: 'parent', - level: 'manage', - }; - const companyEntity = item?.access_info.entities.filter(a => a.type === 'company')?.[0]; - const channelEntities = item?.access_info.entities.filter(a => a.type === 'channel') || []; return ( <> - {Languages.t('components.internal-access_access_manage')} - -
- {folderEntity && ( -
-
- {Languages.t('components.internal-access_inherit_parent')} -
- {Languages.t('components.internal-access_inherit_parent_perm')} -
-
- { - update({ - access_info: { - entities: [ - ...(item?.access_info.entities.filter(a => a.type !== 'folder') || []), - { ...folderEntity, level: status ? 'manage' : 'none' }, - ], - public: item?.access_info.public, - }, - }); - }} - value={folderEntity.level === 'manage'} - /> -
-
- )} - - {companyEntity && folderEntity.level === 'none' && ( -
-
- {Languages.t('components.internal-access_company_member')} -
-
- { - update({ - access_info: { - entities: [ - ...(item?.access_info.entities.filter(a => a.type !== 'company') || []), - ...(level !== 'remove' ? [{ ...companyEntity, level }] : []), - ], - public: item?.access_info.public, - }, - }); - }} - level={companyEntity.level} - /> -
-
- )} - - {channelEntities.length > 0 && ( -
-
- {Languages.t('components.internal-access_cannal')} -
- - {channelEntities.length} {Languages.t('components.internal-access_cannal_info')} - -
-
- { - if (level === 'remove') { - AlertManager.confirm( - async () => { - //Remove channel access - update({ - access_info: { - entities: - item?.access_info?.entities.filter(e => e.type !== 'channel') || [], - public: item?.access_info.public, - }, - }); - }, - () => { - //Do nothing - }, - { - text: Languages.t('components.internal-access_cannal_info_give_back'), - }, - ); - } else { - update({ - access_info: { - entities: - item?.access_info?.entities.map(e => { - if (e.type === 'channel') { - return { ...e, level }; - } - return e; - }) || [], - public: item?.access_info.public, - }, - }); - } - }} - level={channelEntities[0].level} - /> -
-
- )} - -
-
- {Languages.t('components.internal-access_specific_rules')}
diff --git a/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/public-link-access-options.tsx b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/public-link-access-options.tsx new file mode 100644 index 00000000..5fa4a456 --- /dev/null +++ b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/public-link-access-options.tsx @@ -0,0 +1,120 @@ +import { Info, Subtitle } from '@atoms/text'; +import { ToasterService } from '@features/global/services/toaster-service'; +import { copyToClipboard } from '@features/global/utils/CopyClipboard'; +import { Checkbox } from 'app/atoms/input/input-checkbox'; +import { Input } from 'app/atoms/input/input-text'; +import { useEffect, useRef, useState } from 'react'; +import moment from 'moment'; +import 'moment/min/locales'; +import Languages from 'features/global/services/languages-service'; +import { debounce } from 'lodash'; + +export const PublicLinkAccessOptions = (props: { + disabled?: boolean; + password?: string; + expiration?: number; + onChangePassword: Function; + onChangeExpiration: Function; +}) => { + const [usePassword, setUsePassword] = useState(!!props.password); + const [password, setPassword] = useState(props.password); + const [useExpiration, setUseExpiration] = useState((props.expiration || 0) > 0); + const [expiration, setExpiration] = useState(props.expiration); + const handlePasswordBlur = () => { + props.onChangePassword(password); + }; + + const debouncedOnChangePassword = debounce(passwordValue => { + props.onChangePassword(passwordValue); + }, 500); // 500ms delay + + useEffect(() => { + // Ensure the effect runs only if usePassword or password changes + debouncedOnChangePassword(usePassword ? password : ''); + + // Cleanup function to cancel the debounced call if the component is unmounted or the dependencies change + return () => { + debouncedOnChangePassword.cancel(); + }; + }, [usePassword, password]); + + useEffect(() => { + props.onChangeExpiration(useExpiration ? expiration : 0); + }, [useExpiration, expiration]); + + function expirationDate(exp: moment.MomentInput) { + moment.locale(Languages.getLanguage()); + return moment(exp).fromNow(true).toLocaleString(); + } + + return ( + <> + + {Languages.t('components.public-link-security')} + + +
+
+ { + setUsePassword(s); + if (!password && s) setPassword(Math.random().toString(36).slice(-8)); + }} + value={!!usePassword} + label={Languages.t('components.public-link-security_password')} + /> +
+ {!!usePassword && ( + setPassword(e.target.value)} + // saves and copies password + onClick={() => { + if (password) copyToClipboard(password); + ToasterService.success( + Languages.t('components.public-link-security_password_copied'), + ); + }} + /> + )} +
+
+ { + setUseExpiration(s); + if (!expiration && s) setExpiration(Date.now() + 1000 * 60 * 60 * 24 * 7); + }} + value={!!useExpiration} + label={Languages.t('components.public-link-security_expired')} + /> + {useExpiration && (expiration || 0) < Date.now() && ( + + ({Languages.t('components.public-link-security_expired')}) + + )} + {useExpiration && (expiration || 0) > Date.now() && ( + ({expirationDate(expiration)}) + )}{' '} +
+ {!!useExpiration && ( + { + e.target.value && setExpiration(new Date(e.target.value).getTime()); + }} + /> + )} +
+
+ + ); +}; diff --git a/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/public-link-access.tsx b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/public-link-access.tsx index 58fc3586..eb505003 100644 --- a/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/public-link-access.tsx +++ b/tdrive/frontend/src/app/views/client/body/drive/modals/update-access/public-link-access.tsx @@ -1,200 +1,105 @@ -import A from '@atoms/link'; -import { Base, Info, Subtitle } from '@atoms/text'; +import { Subtitle } from '@atoms/text'; import { useDriveItem, getPublicLink } from '@features/drive/hooks/use-drive-item'; -import { ToasterService } from '@features/global/services/toaster-service'; import { copyToClipboard } from '@features/global/utils/CopyClipboard'; -import { Checkbox } from 'app/atoms/input/input-checkbox'; import { Input } from 'app/atoms/input/input-text'; -import { useEffect, useRef, useState } from 'react'; +import { useState } from 'react'; import { AccessLevel } from './common'; -import moment from 'moment'; -import 'moment/min/locales'; import Languages from 'features/global/services/languages-service'; -import { debounce } from 'lodash'; +import { Button } from '@atoms/button/button'; +import { LinkIcon, UserGroupIcon, CheckCircleIcon } from '@heroicons/react/outline'; +import type { DriveFileAccessLevel } from 'app/features/drive/types'; export const PublicLinkManager = ({ id, disabled }: { id: string; disabled?: boolean }) => { const { item, loading, update } = useDriveItem(id); const publicLink = getPublicLink(item); + const defaultPublicLinkLevel = 'read'; + const publicLinkLevel = item?.access_info?.public?.level || 'none'; + const havePublicLink = publicLinkLevel !== 'none'; + const [publicLinkCreationLevel, setPublicLinkCreationLevel] = useState(defaultPublicLinkLevel); + const publicLinkCreationLevelSafe = havePublicLink ? publicLinkLevel || defaultPublicLinkLevel : publicLinkCreationLevel; + const updatePublicLinkLevel = (level: DriveFileAccessLevel) => { + update({ + access_info: { + entities: item?.access_info.entities || [], + public: { ...(item?.access_info?.public || { token: '' }), level }, + }, + }); + if (level === 'none') + setPublicLinkCreationLevel(defaultPublicLinkLevel); + }; + const [didJustCompleteACopy, setDidJustCompleteACopy] = useState(false); return ( <> - - {Languages.t('components.public-link-acess.public_link_acess')} - -
-
+
+ + {Languages.t('components.public-link-acess.public_link_access')} + +
+
+
+ target.select()} + theme={havePublicLink ? "blue" : "outline"} + value={havePublicLink ? publicLink : Languages.t('components.public-link-acess.public-link-placeholder')} + /> + +
+
+
+
+ +
- {item?.access_info?.public?.level !== 'none' && ( - {Languages.t('components.public-link-acess.info_acess_true')} - )} - {item?.access_info?.public?.level === 'none' && ( - {Languages.t('components.public-link-acess.info_acess_false')} - )} - {item?.access_info?.public?.level !== 'none' && ( - <> -
- { - copyToClipboard(publicLink); - ToasterService.success(Languages.t('components.public-link-copied-info')); - }} - > - {Languages.t('components.public-link-copy')} - - - )} + {Languages.t('components.public-link-access-level-' + (havePublicLink ? 'update' : 'create'))}
{ - update({ - access_info: { - entities: item?.access_info.entities || [], - public: { ...(item?.access_info?.public || { token: '' }), level }, - }, - }); + if (havePublicLink) { + updatePublicLinkLevel(level); + } else { + setPublicLinkCreationLevel(level); + } }} />
- {item?.access_info?.public?.level !== 'none' && ( - { - update({ - access_info: { - entities: item?.access_info.entities || [], - public: { - ...item!.access_info!.public!, - password: password || '', - }, - }, - }, true); - }} - onChangeExpiration={(expiration: number) => { - update({ - access_info: { - entities: item?.access_info.entities || [], - public: { - ...item!.access_info!.public!, - expiration: expiration || 0, - }, - }, - }); - }} - /> - )} -
{' '} - - ); -}; - -const PublicLinkOptions = (props: { - disabled?: boolean; - password?: string; - expiration?: number; - onChangePassword: Function; - onChangeExpiration: Function; -}) => { - const [usePassword, setUsePassword] = useState(!!props.password); - const [password, setPassword] = useState(props.password); - const [useExpiration, setUseExpiration] = useState((props.expiration || 0) > 0); - const [expiration, setExpiration] = useState(props.expiration); - const handlePasswordBlur = () => { - props.onChangePassword(password); - }; - - const debouncedOnChangePassword = debounce(passwordValue => { - props.onChangePassword(passwordValue); - }, 500); // 500ms delay - - useEffect(() => { - // Ensure the effect runs only if usePassword or password changes - debouncedOnChangePassword(usePassword ? password : ''); - - // Cleanup function to cancel the debounced call if the component is unmounted or the dependencies change - return () => { - debouncedOnChangePassword.cancel(); - }; - }, [usePassword, password]); - - useEffect(() => { - props.onChangeExpiration(useExpiration ? expiration : 0); - }, [useExpiration, expiration]); - - function expirationDate(exp: moment.MomentInput) { - moment.locale(Languages.getLanguage()); - return moment(exp).fromNow(true).toLocaleString(); - } - - return ( - <> - - {Languages.t('components.public-link-security')} - -
- { - setUsePassword(s); - if (!password && s) setPassword(Math.random().toString(36).slice(-8)); - }} - value={!!usePassword} - label={Languages.t('components.public-link-security_password')} - /> -
- {!!usePassword && ( - setPassword(e.target.value)} - // saves and copies password - onClick={() => { - if (password) copyToClipboard(password); - ToasterService.success( - Languages.t('components.public-link-security_password_copied'), - ); - }} - /> - )} -
-
- { - setUseExpiration(s); - if (!expiration && s) setExpiration(Date.now() + 1000 * 60 * 60 * 24 * 7); - }} - value={!!useExpiration} - label={Languages.t('components.public-link-security_expired')} - /> - {useExpiration && (expiration || 0) < Date.now() && ( - - ({Languages.t('components.public-link-security_expired')}) - - )} - {useExpiration && (expiration || 0) > Date.now() && ( - ({expirationDate(expiration)}) - )}{' '} -
- {!!useExpiration && ( - { - e.target.value && setExpiration(new Date(e.target.value).getTime()); - }} - /> - )}
);