From ededea6c45922365d92c2bf576fb1c25e632594d Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Tue, 17 Jun 2025 11:37:30 +0800 Subject: [PATCH] feat(share): improve UI for share link result (follow up #https://github.com/cloudreve/frontend/pull/259) --- public/locales/en-US/application.json | 8 +- public/locales/ja-JP/application.json | 7 +- public/locales/zh-CN/application.json | 7 +- public/locales/zh-TW/application.json | 7 +- .../FileManager/Dialogs/Share/ShareDialog.tsx | 113 +++++++++++------- .../Dialogs/Share/ShareSetting.tsx | 46 ++++--- 6 files changed, 112 insertions(+), 76 deletions(-) diff --git a/public/locales/en-US/application.json b/public/locales/en-US/application.json index 7011377..8392698 100644 --- a/public/locales/en-US/application.json +++ b/public/locales/en-US/application.json @@ -307,7 +307,6 @@ "createShareLink": "Share", "viewDetails": "View details", "copy": "Copy", - "copyLinkAlongWithPassword": "Copy link and password", "bytes": " ({{bytes}} Bytes)", "storagePolicy": "Storage policy", "childFolders": "Child folders", @@ -371,6 +370,8 @@ "deleteViewSetting": "Delete view setting" }, "modals": { + "includePasswordInShareLink": "Include password in share link", + "includePasswordInShareLinkDes": "If selected, password will be included in the share link, and no password is required when accessing the share link.", "showFileName": "Show file name", "archiveFile": "Archive file", "cancelDownload": "Cancel download", @@ -468,10 +469,9 @@ "shareLinkShareContent": "I shared with you: {{name}} Link: {{link}}", "shareLinkPasswordInfo": "Password: {{password}}", "createShareLink": "Create share link", - "privateShare": "Hide from public", - "privateShareDes": "If selected, other people cannot see this share link on your homepage.", + "privateShare": "Protect with password", + "privateShareDes": "If selected, password is required to access the share link, others cannot see this share link on your homepage.", "useCustomPassword": "Custom share link password", - "passwordAutoGenerate": "Auto generate", "shareView": "Share view setting", "shareViewDes": "If selected, other users can see your view setting (layout, sorting, etc.) saved on the server server when accessing this shared folder.", "expireAfterDownload": "Expire after being downloaded", diff --git a/public/locales/ja-JP/application.json b/public/locales/ja-JP/application.json index 61eeb8b..843ab0c 100644 --- a/public/locales/ja-JP/application.json +++ b/public/locales/ja-JP/application.json @@ -308,7 +308,6 @@ "createShareLink": "共有リンクの作成", "viewDetails": "詳細情報", "copy": "コピー", - "copyLinkAlongWithPassword": "パスワード付きリンクをコピー", "bytes": " ({{bytes}} バイト)", "storagePolicy": "ストレージポリシー", "childFolders": "ディレクトリの内容", @@ -372,6 +371,8 @@ "deleteViewSetting": "ビュー設定を削除" }, "modals": { + "includePasswordInShareLink": "共有リンクにパスワードを含める", + "includePasswordInShareLinkDes": "チェックを入れると、共有リンクにパスワードが含まれます。このリンクにアクセスする際には、パスワードを入力する必要はありません。", "showFileName": "ファイル名を表示", "archiveFile": "圧縮ファイル", "cancelDownload": "ダウンロードキャンセル", @@ -469,8 +470,8 @@ "shareLinkShareContent": "{{name}} を共有しました。リンク:{{link}}", "shareLinkPasswordInfo": "パスワード:{{password}}", "createShareLink": "共有リンク作成", - "privateShare": "共有を非表示", - "privateShareDes": "チェックを入れると他の人はあなたのプロフィールページでこの共有リンクを見ることができません。", + "privateShare": "パスワードで保護", + "privateShareDes": "チェックを入れると、パスワードが必要です。他の人はあなたのプロフィールページでこの共有リンクを見ることができません。", "useCustomPassword": "カスタムパスワードを使用", "expireAfterDownload": "ダウンロード後に自動的に期限切れ", "sharePassword": "共有パスワード", diff --git a/public/locales/zh-CN/application.json b/public/locales/zh-CN/application.json index be33fc9..c0a4a35 100644 --- a/public/locales/zh-CN/application.json +++ b/public/locales/zh-CN/application.json @@ -307,7 +307,6 @@ "createShareLink": "创建分享链接", "viewDetails": "详细信息", "copy": "复制", - "copyLinkAlongWithPassword": "复制链接和密码", "bytes": " ({{bytes}} 字节)", "storagePolicy": "存储策略", "childFolders": "包含目录", @@ -371,6 +370,8 @@ "deleteViewSetting": "删除视图设置" }, "modals": { + "includePasswordInShareLink": "在链接中包含密码", + "includePasswordInShareLinkDes": "勾选后,分享链接中会包含密码,通过此链接访问时不需要再输入密码。", "showFileName": "显示文件名", "archiveFile": "压缩文件", "cancelDownload": "取消下载", @@ -468,8 +469,8 @@ "shareLinkShareContent": "我向你分享了:{{name}} 链接:{{link}}", "shareLinkPasswordInfo": " 密码: {{password}}", "createShareLink": "创建分享链接", - "privateShare": "隐藏分享", - "privateShareDes": "勾选后,其他人无法在你的个人主页看到此分享链接。", + "privateShare": "使用密码保护链接", + "privateShareDes": "勾选后,需要使用密码访问分享链接,其他人无法在你的个人主页看到此分享链接。", "useCustomPassword": "自定义分享密码", "expireAfterDownload": "下载后自动过期", "sharePassword": "分享密码", diff --git a/public/locales/zh-TW/application.json b/public/locales/zh-TW/application.json index 3a36e00..297e81b 100644 --- a/public/locales/zh-TW/application.json +++ b/public/locales/zh-TW/application.json @@ -307,7 +307,6 @@ "createShareLink": "建立分享連結", "viewDetails": "詳細資訊", "copy": "復制", - "copyLinkAlongWithPassword": "復制連結和密碼", "bytes": " ({{bytes}} 位元組)", "storagePolicy": "儲存策略", "childFolders": "包含目錄", @@ -367,6 +366,8 @@ "off": "關閉" }, "modals": { + "includePasswordInShareLink": "在連結中包含密碼", + "includePasswordInShareLinkDes": "勾選後,分享連結中會包含密碼,通過此連結訪問時不需要再輸入密碼。", "showFileName": "顯示檔名", "archiveFile": "壓縮檔案", "cancelDownload": "取消下載", @@ -464,8 +465,8 @@ "shareLinkShareContent": "我向你分享了:{{name}} 連結:{{link}}", "shareLinkPasswordInfo": " 密碼: {{password}}", "createShareLink": "建立分享連結", - "privateShare": "隱藏分享", - "privateShareDes": "勾選後,其他人無法在你的個人主頁看到此分享連結。", + "privateShare": "使用密碼保護連結", + "privateShareDes": "勾選後,需要使用密碼訪問分享連結,其他人無法在你的個人主頁看到此分享連結。", "useCustomPassword": "使用自定義密碼", "expireAfterDownload": "下載後自動過期", "sharePassword": "分享密碼", diff --git a/src/component/FileManager/Dialogs/Share/ShareDialog.tsx b/src/component/FileManager/Dialogs/Share/ShareDialog.tsx index f4f8737..9fc1026 100644 --- a/src/component/FileManager/Dialogs/Share/ShareDialog.tsx +++ b/src/component/FileManager/Dialogs/Share/ShareDialog.tsx @@ -1,4 +1,4 @@ -import { Box, DialogContent, IconButton, List, Tooltip, useTheme } from "@mui/material"; +import { Box, Checkbox, Collapse, DialogContent, IconButton, Stack, Tooltip, useTheme } from "@mui/material"; import dayjs from "dayjs"; import { TFunction } from "i18next"; import React, { useCallback, useEffect, useMemo, useState } from "react"; @@ -10,12 +10,12 @@ import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; import { createOrUpdateShareLink } from "../../../../redux/thunks/share.ts"; import { copyToClipboard, sendLink } from "../../../../util"; import AutoHeight from "../../../Common/AutoHeight.tsx"; -import { FilledTextField } from "../../../Common/StyledComponents.tsx"; +import { FilledTextField, SmallFormControlLabel } from "../../../Common/StyledComponents.tsx"; import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import CopyOutlined from "../../../Icons/CopyOutlined.tsx"; import Share from "../../../Icons/Share.tsx"; import { FileManagerIndex } from "../../FileManager.tsx"; import ShareSettingContent, { downloadOptions, expireOptions, ShareSetting } from "./ShareSetting.tsx"; -import CopyOutlined from "../../../Icons/CopyOutlined.tsx"; const initialSetting: ShareSetting = { expires_val: expireOptions[2], @@ -72,6 +72,7 @@ const ShareDialog = () => { const [loading, setLoading] = useState(false); const [setting, setSetting] = useState(initialSetting); const [shareLink, setShareLink] = useState(""); + const [includePassword, setIncludePassword] = useState(true); const shareLinkPassword = useMemo(() => { const start = shareLink.lastIndexOf("/s/"); const shareLinkParts = shareLink.substring(start + 3).split("/"); @@ -130,6 +131,20 @@ const ShareDialog = () => { [dispatch, target, shareLink, editTarget, setLoading, setting, setShareLink], ); + const finalShareLink = useMemo(() => { + if (includePassword) { + return shareLink; + } + return shareLink.substring(0, shareLink.lastIndexOf("/")); + }, [includePassword, shareLink]); + + const finalShareLinkPassword = useMemo(() => { + if (!includePassword) { + return shareLink.substring(shareLink.lastIndexOf("/") + 1); + } + return undefined; + }, [includePassword, shareLink]); + return ( <> { showActions loading={loading} showCancel + hideOk={!!shareLink} onAccept={onAccept} dialogProps={{ open: open ?? false, @@ -145,19 +161,12 @@ const ShareDialog = () => { maxWidth: "xs", }} cancelText={shareLink ? "common:close" : undefined} - okText={ - shareLink - ? shareLinkPassword.password - ? "fileManager.copyLinkAlongWithPassword" - : "fileManager.copy" - : undefined - } secondaryAction={ shareLink ? // @ts-ignore navigator.share && ( - sendLink(target?.name ?? "", shareLink)}> + sendLink(target?.name ?? "", finalShareLink)}> @@ -183,26 +192,19 @@ const ShareDialog = () => { /> )} {shareLink && ( - + e.target.select()} slotProps={{ input: { endAdornment: ( copyToClipboard(shareLink.substring(0, shareLink.lastIndexOf("/")))} + onClick={() => copyToClipboard(finalShareLink)} size="small" sx={{ marginRight: -1 }} > @@ -213,31 +215,54 @@ const ShareDialog = () => { }} /> {shareLinkPassword.password && ( - e.target.select()} - slotProps={{ - input: { - endAdornment: ( - - copyToClipboard(shareLink.substring(shareLink.lastIndexOf("/") + 1) ?? "") - } + <> + + e.target.select()} + slotProps={{ + input: { + endAdornment: ( + copyToClipboard(finalShareLinkPassword ?? "")} + size="small" + sx={{ marginRight: -1 }} + > + + + ), + }, + }} + /> + + + - - - ), - }, - }} - /> + checked={includePassword} + onChange={() => { + setIncludePassword(!includePassword); + }} + /> + } + label={t("application:modals.includePasswordInShareLink")} + /> + + )} - + )} diff --git a/src/component/FileManager/Dialogs/Share/ShareSetting.tsx b/src/component/FileManager/Dialogs/Share/ShareSetting.tsx index f490802..c329c48 100644 --- a/src/component/FileManager/Dialogs/Share/ShareSetting.tsx +++ b/src/component/FileManager/Dialogs/Share/ShareSetting.tsx @@ -1,7 +1,7 @@ import { Autocomplete, - Box, Checkbox, + Collapse, createFilterOptions, FormControl, List, @@ -9,6 +9,7 @@ import { ListItemIcon, ListItemSecondaryAction, ListItemText, + Stack, styled, TextField, Typography, @@ -19,6 +20,7 @@ import MuiAccordionSummary from "@mui/material/AccordionSummary"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { FileResponse, FileType } from "../../../../api/explorer.ts"; +import { FilledTextField, SmallFormControlLabel } from "../../../Common/StyledComponents.tsx"; import ClockArrowDownload from "../../../Icons/ClockArrowDownload.tsx"; import Eye from "../../../Icons/Eye.tsx"; import TableSettingsOutlined from "../../../Icons/TableSettings.tsx"; @@ -149,31 +151,38 @@ const ShareSettingContent = ({ setting, file, editing, onSettingChange }: ShareS - + - {t("application:modals.privateShareDes")} + {t("application:modals.privateShareDes")} {setting.is_private && ( - + {!editing && ( - { - onSettingChange({ ...setting, use_custom_password: !setting.use_custom_password }); - }} + { + onSettingChange({ ...setting, use_custom_password: !setting.use_custom_password }); + }} + /> + } + label={t("application:modals.useCustomPassword")} /> )} - {!setting.use_custom_password && ( - {t("application:modals.useCustomPassword")} - )} - {setting.use_custom_password && ( - - + + { const value = e.target.value.trim(); @@ -181,11 +190,10 @@ const ShareSettingContent = ({ setting, file, editing, onSettingChange }: ShareS onSettingChange({ ...setting, password: value }); }} required - fullWidth /> - )} - + + )}