feat(share): improve UI for share link result (follow up #https://github.com/cloudreve/frontend/pull/259)

This commit is contained in:
Aaron Liu 2025-06-17 11:37:30 +08:00
parent 00f18b18ec
commit ededea6c45
6 changed files with 112 additions and 76 deletions

View File

@ -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",

View File

@ -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": "共有パスワード",

View File

@ -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": "分享密码",

View File

@ -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": "分享密碼",

View File

@ -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<ShareSetting>(initialSetting);
const [shareLink, setShareLink] = useState<string>("");
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 (
<>
<DraggableDialog
@ -137,6 +152,7 @@ const ShareDialog = () => {
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 && (
<Tooltip title={t("application:modals.sendLink")}>
<IconButton onClick={() => sendLink(target?.name ?? "", shareLink)}>
<IconButton onClick={() => sendLink(target?.name ?? "", finalShareLink)}>
<Share />
</IconButton>
</Tooltip>
@ -183,26 +192,19 @@ const ShareDialog = () => {
/>
)}
{shareLink && (
<List
sx={{
display: "flex",
flexDirection: "column",
gap: theme.spacing(1),
padding: theme.spacing(1),
}}
>
<Stack spacing={1}>
<FilledTextField
variant={"filled"}
inputProps={{ readonly: true }}
label={t("modals.shareLink")}
fullWidth
value={shareLinkPassword.shareLink}
value={finalShareLink ?? ""}
onFocus={(e) => e.target.select()}
slotProps={{
input: {
endAdornment: (
<IconButton
onClick={() => copyToClipboard(shareLink.substring(0, shareLink.lastIndexOf("/")))}
onClick={() => copyToClipboard(finalShareLink)}
size="small"
sx={{ marginRight: -1 }}
>
@ -213,31 +215,54 @@ const ShareDialog = () => {
}}
/>
{shareLinkPassword.password && (
<FilledTextField
variant={"filled"}
inputProps={{ readonly: true }}
label={t("modals.sharePassword")}
fullWidth
value={shareLinkPassword.password}
onFocus={(e) => e.target.select()}
slotProps={{
input: {
endAdornment: (
<IconButton
onClick={() =>
copyToClipboard(shareLink.substring(shareLink.lastIndexOf("/") + 1) ?? "")
}
<>
<Collapse in={!includePassword}>
<FilledTextField
variant={"filled"}
inputProps={{ readonly: true }}
label={t("modals.sharePassword")}
fullWidth
value={finalShareLinkPassword ?? ""}
onFocus={(e) => e.target.select()}
slotProps={{
input: {
endAdornment: (
<IconButton
onClick={() => copyToClipboard(finalShareLinkPassword ?? "")}
size="small"
sx={{ marginRight: -1 }}
>
<CopyOutlined />
</IconButton>
),
},
}}
/>
</Collapse>
<Tooltip enterDelay={100} title={t("application:modals.includePasswordInShareLinkDes")}>
<SmallFormControlLabel
sx={{
mt: "0!important",
}}
control={
<Checkbox
disableRipple
sx={{
pl: 0,
}}
size="small"
sx={{ marginRight: -1 }}
>
<CopyOutlined />
</IconButton>
),
},
}}
/>
checked={includePassword}
onChange={() => {
setIncludePassword(!includePassword);
}}
/>
}
label={t("application:modals.includePasswordInShareLink")}
/>
</Tooltip>
</>
)}
</List>
</Stack>
)}
</Box>
</CSSTransition>

View File

@ -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
</ListItemIcon>
<ListItemText primary={t("application:modals.privateShare")} />
<ListItemSecondaryAction>
<Checkbox disabled={editing} checked={setting.is_private} onChange={handleCheck("is_private")} />
<Checkbox disabled={editing} checked={!!setting.is_private} onChange={handleCheck("is_private")} />
</ListItemSecondaryAction>
</StyledListItemButton>
</AccordionSummary>
<AccordionDetails sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
<Typography>{t("application:modals.privateShareDes")}</Typography>
<Typography variant="body2">{t("application:modals.privateShareDes")}</Typography>
{setting.is_private && (
<Box sx={{ display: "flex", alignItems: "center", width: "100%" }}>
<Stack sx={{ mt: 1, width: "100%" }}>
{!editing && (
<Checkbox
checked={setting.use_custom_password}
onChange={() => {
onSettingChange({ ...setting, use_custom_password: !setting.use_custom_password });
}}
<SmallFormControlLabel
control={
<Checkbox
size="small"
checked={setting.use_custom_password}
onChange={() => {
onSettingChange({ ...setting, use_custom_password: !setting.use_custom_password });
}}
/>
}
label={t("application:modals.useCustomPassword")}
/>
)}
{!setting.use_custom_password && (
<Typography sx={{ flex: 1 }}>{t("application:modals.useCustomPassword")}</Typography>
)}
{setting.use_custom_password && (
<FormControl variant="standard" sx={{ mx: 1, flex: 1 }}>
<TextField
<Collapse in={setting.use_custom_password}>
<FormControl variant="standard" fullWidth sx={{ mt: 1 }}>
<FilledTextField
label={t("application:modals.sharePassword")}
variant="standard"
disabled={editing}
slotProps={{
htmlInput: {
maxLength: 32,
},
}}
value={setting.password ?? ""}
onChange={(e) => {
const value = e.target.value.trim();
@ -181,11 +190,10 @@ const ShareSettingContent = ({ setting, file, editing, onSettingChange }: ShareS
onSettingChange({ ...setting, password: value });
}}
required
fullWidth
/>
</FormControl>
)}
</Box>
</Collapse>
</Stack>
)}
</AccordionDetails>
</Accordion>