refactor: files
This commit is contained in:
+2
-2
@@ -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`;
|
||||
|
||||
@@ -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]"
|
||||
)}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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" />}
|
||||
|
||||
@@ -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
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user