refactor: files

This commit is contained in:
Tristan Yang
2025-05-15 10:56:42 +08:00
parent 7b8e6c28ac
commit 2aede0acc5
11 changed files with 2226 additions and 1760 deletions
+2 -2
View File
@@ -12,8 +12,8 @@ const prices: Price[] = [
},
];
const official_dev = `https://dev.voce.chat`;
const local_dev = `https://dev.voce.chat`;
// const local_dev = `http://localhost:3412`;
// const local_dev = `https://dev.voce.chat`;
const local_dev = `http://chat.jcdl369.top:3009`;
const dev_origin = process.env.REACT_APP_OFFICIAL_DEMO ? official_dev : local_dev;
// const local_dev = `https://im.ttt.td`;
+5 -5
View File
@@ -10,7 +10,7 @@ import {
DocPreview,
ImagePreview,
PdfPreview,
VideoPreview
VideoPreview,
} from "./preview";
import { shallowEqual } from "react-redux";
import DownloadArea from "../FileMessage/DownloadArea";
@@ -31,7 +31,7 @@ const renderPreview = (data: Data) => {
video: /^video/gi,
code: /(json|javascript|java|rb|c|php|xml|css|html)$/gi,
doc: /^text/gi,
pdf: /\/pdf$/gi
pdf: /\/pdf$/gi,
};
const _arr = name.split(".");
const _type = file_type || _arr[_arr.length - 1];
@@ -79,7 +79,7 @@ const FileBox: FC<Props> = ({
size,
created_at,
from_uid,
content
content,
}) => {
const [fetchError, setFetchError] = useState(false);
const { isExpired, setExpired } = useExpiredResMap();
@@ -95,7 +95,7 @@ const FileBox: FC<Props> = ({
}
} catch (e) {
console.warn(e);
setFetchError(true);
}
};
@@ -118,7 +118,7 @@ const FileBox: FC<Props> = ({
<div
className={clsx(
`rounded-md border border-solid border-gray-300 dark:border-gray-500 bg-gray-100 dark:bg-gray-900`,
flex ? "w-full" : "w-72 md:w-[370px]",
flex ? "w-full max-w-3xl" : "w-72 md:w-[370px]",
withPreview ? "relative overflow-hidden h-[281px]" : "h-[66px] ",
file_type.startsWith("audio") && "h-[125px]"
)}
+3 -3
View File
@@ -6,17 +6,17 @@ import CheckSign from "@/assets/icons/check.sign.svg";
type Props = {
select: number;
updateFilter: (param: { channel?: number }) => void;
updateFilter: (param: { gid?: number }) => void;
};
const Channel: FC<Props> = ({ select = 0, updateFilter }) => {
const { channels } = useFilteredChannels();
const handleClick = (gid?: number) => {
updateFilter({ channel: gid });
updateFilter({ gid });
};
return (
<div className="rounded-lg p-1 pt-0 bg-white dark:bg-gray-800 overflow-auto max-h-[400px] flex flex-col items-start relative drop-shadow">
<ul className="w-full flex flex-col gap-4 p-2">
<ul className="w-full min-w-48 flex flex-col gap-4 p-2">
<li
className="relative cursor-pointer flex items-center gap-2"
onClick={handleClick.bind(null, undefined)}
+13 -14
View File
@@ -3,30 +3,29 @@ import { FC } from "react";
import CheckSign from "@/assets/icons/check.sign.svg";
export const Dates = {
today: {
title: "Today",
duration: 2222
Day1: {
title: "Last 24 Hours",
},
in7d: {
title: "Last 7 Days"
Day7: {
title: "Last 7 Days",
},
in30d: {
title: "Last 30 Days"
Day30: {
title: "Last 30 Days",
},
in3m: {
title: "Last 3 months"
Day90: {
title: "Last 3 months",
},
Day180: {
title: "Last 6 months",
},
in12m: {
title: "Last 12 months"
}
};
type Props = {
select: number;
updateFilter: (param: { date?: string }) => void;
updateFilter: (param: { creation_time_type?: string }) => void;
};
const DateFilter: FC<Props> = ({ select = "", updateFilter }) => {
const handleClick = (dur?: string) => {
updateFilter({ date: dur });
updateFilter({ creation_time_type: dur });
};
return (
+2 -2
View File
@@ -7,12 +7,12 @@ import Search from "../Search";
type Props = {
select: number;
updateFilter: (param: { from?: number }) => void;
updateFilter: (param: { uid?: number }) => void;
};
const From: FC<Props> = ({ select = "", updateFilter }) => {
const { input, updateInput, users } = useFilteredUsers();
const handleClick = (uid?: number) => {
updateFilter({ from: uid });
updateFilter({ uid });
};
return (
+15 -25
View File
@@ -2,50 +2,40 @@ import { FC } from "react";
import CheckSign from "@/assets/icons/check.sign.svg";
import IconAudio from "@/assets/icons/file.audio.svg";
import IconCode from "@/assets/icons/file.code.svg";
import IconDoc from "@/assets/icons/file.doc.svg";
import IconImage from "@/assets/icons/file.image.svg";
import IconPdf from "@/assets/icons/file.pdf.svg";
import IconUnknown from "@/assets/icons/file.unknown.svg";
import IconVideo from "@/assets/icons/file.video.svg";
export const FileTypes = {
doc: {
Doc: {
title: "Documents",
icon: <IconDoc className="w-4 h-auto" />
icon: <IconDoc className="w-4 h-auto" />,
},
pdf: {
PDF: {
title: "PDFs",
icon: <IconPdf className="w-4 h-auto" />
icon: <IconPdf className="w-4 h-auto" />,
},
image: {
Image: {
title: "Images",
icon: <IconImage className="w-4 h-auto" />
icon: <IconImage className="w-4 h-auto" />,
},
audio: {
Audio: {
title: "Audio",
icon: <IconAudio className="w-4 h-auto" />
icon: <IconAudio className="w-4 h-auto" />,
},
video: {
Video: {
title: "Videos",
icon: <IconVideo className="w-4 h-auto" />
icon: <IconVideo className="w-4 h-auto" />,
},
code: {
title: "Code Snippets",
icon: <IconCode className="w-4 h-auto" />
},
unknown: {
title: "Unknown Files",
icon: <IconUnknown className="w-4 h-auto" />
}
};
type Props = {
select: number;
updateFilter: (param: { type?: string }) => void;
select: string;
updateFilter: (param: { file_type?: string }) => void;
};
const Type: FC<Props> = ({ select = "", updateFilter }) => {
const handleClick = (type?: string) => {
updateFilter({ type });
const handleClick = (file_type?: string) => {
updateFilter({ file_type });
};
return (
@@ -53,7 +43,7 @@ const Type: FC<Props> = ({ select = "", updateFilter }) => {
<ul className="w-full flex flex-col gap-4">
<li
className="relative cursor-pointer flex items-center gap-4 text-gray-500 dark:text-gray-300 font-semibold text-sm"
onClick={handleClick.bind(null, undefined)}
onClick={handleClick.bind(null, "")}
>
Any Type
{!select && <CheckSign className="absolute right-0 top-1/2 -translate-y-1/2" />}
+46 -37
View File
@@ -4,11 +4,11 @@ import Tippy from "@tippyjs/react";
import clsx from "clsx";
import { useAppSelector } from "@/app/store";
import Avatar from "@/components/Avatar";
// import Avatar from "@/components/Avatar";
import ArrowDown from "@/assets/icons/arrow.down.svg";
import FilterChannel from "./Channel";
import FilterDate, { Dates } from "./Date";
import FilterFrom from "./From";
// import FilterFrom from "./From";
import FilterType, { FileTypes } from "./Type";
import { shallowEqual } from "react-redux";
@@ -23,13 +23,15 @@ const getClass = (selected: boolean) => {
export default function Filter({ filter, updateFilter }) {
const { t } = useTranslation("file");
const [filtersVisible, setFiltersVisible] = useState({
channel: false,
date: false,
from: false,
type: false
gid: false,
creation_time_type: false,
uid: false,
file_type: false,
});
const toggleFilterVisible = (obj: any) => {
console.log({ obj });
setFiltersVisible((prev) => {
return { ...prev, ...obj };
});
@@ -39,82 +41,89 @@ export default function Filter({ filter, updateFilter }) {
updateFilter(data);
let _key = Object.keys(data)[0];
let tmp = {
[_key]: false
[_key]: false,
};
toggleFilterVisible(tmp);
};
const userMap = useAppSelector((store) => store.users.byId, shallowEqual);
const channelMap = useAppSelector((store) => store.channels.byId, shallowEqual);
const { from, channel, type, date } = filter;
const { uid, gid, file_type, creation_time_type } = filter;
const {
channel: channelVisible,
date: dateVisible,
type: typeVisible,
from: fromVisible
gid: channelVisible,
creation_time_type: dateVisible,
file_type: typeVisible,
uid: fromVisible,
} = filtersVisible;
return (
<div className="flex items-center gap-2">
<Tippy
<div className="flex items-center gap-3">
{/* <Tippy
interactive
onClickOutside={toggleFilterVisible.bind(null, { from: false })}
onClickOutside={toggleFilterVisible.bind(null, { uid: false })}
visible={fromVisible}
placement="bottom-start"
content={<FilterFrom select={filter.from} updateFilter={handleUpdateFilter} />}
content={<FilterFrom select={filter.uid} updateFilter={handleUpdateFilter} />}
>
<div className={getClass(from)} onClick={toggleFilterVisible.bind(null, { from: true })}>
{from && (
<div className={getClass(uid)} onClick={toggleFilterVisible.bind(null, { uid: true })}>
{uid && (
<Avatar
width={16}
height={16}
className="rounded-full w-4 h-4"
name={userMap[from].name}
src={userMap[from].avatar}
name={userMap[uid].name}
src={userMap[uid].avatar}
/>
)}
<span className="txt">
{t("from")} {from && userMap[from].name}
{t("from")} {uid && userMap[uid].name}
</span>
<ArrowDown className="dark:stroke-gray-100" />
</div>
</Tippy>
</Tippy> */}
<Tippy
interactive
onClickOutside={toggleFilterVisible.bind(null, { channel: false })}
onClickOutside={toggleFilterVisible.bind(null, { gid: false })}
visible={channelVisible}
placement="bottom-start"
content={<FilterChannel select={filter.channel} updateFilter={handleUpdateFilter} />}
content={<FilterChannel select={filter.gid} updateFilter={handleUpdateFilter} />}
>
<div
className={getClass(channel)}
onClick={toggleFilterVisible.bind(null, { channel: true })}
>
<span className="txt">{channel ? `In ${channelMap[channel].name}` : t("channel")}</span>
<div className={getClass(gid)} onClick={toggleFilterVisible.bind(null, { gid: true })}>
<span className="txt">{gid ? `In ${channelMap[gid].name}` : t("channel")}</span>
<ArrowDown className="dark:stroke-gray-100" />
</div>
</Tippy>
<Tippy
interactive
onClickOutside={toggleFilterVisible.bind(null, { type: false })}
onClickOutside={toggleFilterVisible.bind(null, { file_type: false })}
visible={typeVisible}
placement="bottom-start"
content={<FilterType select={filter.type} updateFilter={handleUpdateFilter} />}
content={<FilterType select={filter.file_type} updateFilter={handleUpdateFilter} />}
>
<div className={getClass(type)} onClick={toggleFilterVisible.bind(null, { type: true })}>
<span className="txt">{type ? FileTypes[type].title : t("type")}</span>
<div
className={getClass(file_type)}
onClick={toggleFilterVisible.bind(null, { file_type: true })}
>
<span className="txt">{file_type ? FileTypes[file_type].title : t("type")}</span>
<ArrowDown className="dark:stroke-gray-100" />
</div>
</Tippy>
<Tippy
interactive
onClickOutside={toggleFilterVisible.bind(null, { date: false })}
onClickOutside={toggleFilterVisible.bind(null, { creation_time_type: false })}
visible={dateVisible}
placement="bottom-start"
content={<FilterDate select={filter.date} updateFilter={handleUpdateFilter} />}
content={
<FilterDate select={filter.creation_time_type} updateFilter={handleUpdateFilter} />
}
>
<div className={getClass(date)} onClick={toggleFilterVisible.bind(null, { date: true })}>
<span className="txt">{date ? Dates[date].title : t("date")}</span>
<div
className={getClass(creation_time_type)}
onClick={toggleFilterVisible.bind(null, { creation_time_type: true })}
>
<span className="txt">
{creation_time_type ? Dates[creation_time_type].title : t("date")}
</span>
<ArrowDown className="dark:stroke-gray-100" />
</div>
</Tippy>
+16 -11
View File
@@ -12,7 +12,7 @@ import { useLazyGetFilesQuery } from "@/app/services/server";
import { shallowEqual } from "react-redux";
function Files() {
const [getFiles, { data }] = useLazyGetFilesQuery();
const [getFiles, { data, isLoading }] = useLazyGetFilesQuery();
const listContainerRef = useRef<HTMLDivElement>();
const [filter, setFilter] = useState({});
const view = useAppSelector((store) => store.ui.fileListView, shallowEqual);
@@ -29,14 +29,22 @@ function Files() {
});
};
useEffect(() => {
getFiles();
}, []);
// 过滤掉空值
const _f = Object.fromEntries(
Object.entries(filter).filter(([k, v]) => {
return !!v;
})
);
getFiles(_f);
}, [filter]);
if (!data) return null;
// return null;
const nonExpiredFiles = data.filter((item) => !item.expired);
const nonExpiredFiles = [
...data.filter((item) => !item.expired).sort((a, b) => b.created_at - a.created_at),
];
console.log({ view });
return (
<div className="h-screen md:overflow-y-scroll flex flex-col items-start my-2 mr-6 rounded-2xl bg-white dark:bg-gray-700">
<div className="h-screen md:overflow-y-scroll flex flex-col items-start my-5 mr-6 pb-8 rounded-2xl bg-white dark:bg-gray-700">
<Search value={filter.name} updateSearchValue={handleUpdateSearch} />
<div className="flex justify-between w-full px-4 py-5">
<Filter filter={filter} updateFilter={updateFilter} />
@@ -44,13 +52,10 @@ function Files() {
</div>
<div
className={clsx(
`h-full w-full px-4 overflow-y-scroll no-scrollbar`,
view == "item" && "flex gap-2 flex-col",
view == "grid" && "col-count-2 md:col-count-3 lg:col-count-5"
`h-full w-fit px-4 overflow-y-scroll no-scrollbar gap-4`,
view == "item" && "flex flex-col",
view == "grid" && "grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"
)}
style={{
columnGap: "5px"
}}
ref={listContainerRef}
>
{nonExpiredFiles.map((file) => {
+10 -12
View File
@@ -23,13 +23,13 @@ export default function MyAccount() {
name: {
label: t("username"),
title: t("change_name"),
intro: t("change_name_desc")
intro: t("change_name_desc"),
},
email: {
label: t("email"),
title: t("change_email"),
intro: t("change_email_desc")
}
intro: t("change_email_desc"),
},
};
const loginUser = useAppSelector(
@@ -69,6 +69,12 @@ export default function MyAccount() {
<div className="mt-2 mb-16 font-bold text-lg text-gray-800 dark:text-white">
{name} <span className="font-normal text-gray-500">#{uid}</span>
</div>
<div className="w-full flex items-start justify-between mb-6">
<div className="flex flex-col text-gray-500 dark:text-gray-50">
<span className="text-xs uppercase font-semibold">{t("email")}</span>
<span className="text-sm">{email}</span>
</div>
</div>
<div className="w-full flex items-start justify-between mb-6">
<div className="flex flex-col text-gray-500 dark:text-gray-50">
<span className="text-xs uppercase font-semibold">{t("username")}</span>
@@ -80,15 +86,7 @@ export default function MyAccount() {
{ct("action.edit")}
</Button>
</div>
<div className="w-full flex items-start justify-between mb-6">
<div className="flex flex-col text-gray-500 dark:text-gray-50">
<span className="text-xs uppercase font-semibold">{t("email")}</span>
<span className="text-sm">{email}</span>
</div>
<Button data-edit="email" onClick={handleBasicEdit}>
{ct("action.edit")}
</Button>
</div>
<div className="w-full flex items-start justify-between mb-6">
<div className="flex flex-col text-gray-500 dark:text-gray-50">
<span className="text-xs uppercase font-semibold">{t("password")}</span>