refactor: expired file message
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { FC, ReactElement } from "react";
|
||||
import { FC, ReactElement, useEffect, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { useAppSelector } from "@/app/store";
|
||||
import useExpiredResMap from "@/hooks/useExpiredResMap";
|
||||
import { formatBytes, fromNowTime, getFileIcon } from "@/utils";
|
||||
import IconDownload from "@/assets/icons/download.svg";
|
||||
import {
|
||||
@@ -79,11 +80,36 @@ const FileBox: FC<Props> = ({
|
||||
from_uid,
|
||||
content
|
||||
}) => {
|
||||
const [fetchError, setFetchError] = useState(false);
|
||||
const { isExpired, setExpired } = useExpiredResMap();
|
||||
const fromUser = useAppSelector((store) => store.users.byId[from_uid]);
|
||||
const icon = getFileIcon(file_type, name, "icon w-9 h-12");
|
||||
if (!content || !name) return null;
|
||||
const expired = isExpired(content);
|
||||
useEffect(() => {
|
||||
const tryFetch = async (url: string) => {
|
||||
try {
|
||||
let statusNum = (await fetch(url)).status;
|
||||
if (statusNum >= 400) {
|
||||
setFetchError(true);
|
||||
}
|
||||
} catch (e) {
|
||||
setFetchError(true);
|
||||
}
|
||||
};
|
||||
if (!expired && content) {
|
||||
tryFetch(content);
|
||||
}
|
||||
}, [content, expired]);
|
||||
useEffect(() => {
|
||||
if (fetchError) {
|
||||
setExpired(content);
|
||||
}
|
||||
}, [fetchError, content]);
|
||||
|
||||
if (!content || expired || fetchError) return null;
|
||||
|
||||
const previewContent = renderPreview({ file_type, content, name });
|
||||
const withPreview = preview && previewContent;
|
||||
const withPreview = preview && previewContent && !fetchError && !expired;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -23,7 +23,7 @@ const AudioMessage = ({ url, name, size, download }: Props) => {
|
||||
setError(true);
|
||||
};
|
||||
const _size = formatBytes(size);
|
||||
if (error) return <ExpiredMessage type="audio" />;
|
||||
if (error) return <ExpiredMessage type="audio" url={url} />;
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import useExpiredResMap from "@/hooks/useExpiredResMap";
|
||||
import IconAudio from "@/assets/icons/file.audio.svg";
|
||||
import IconImage from "@/assets/icons/file.image.svg";
|
||||
import IconUnknown from "@/assets/icons/file.unknown.svg";
|
||||
@@ -9,6 +10,7 @@ import IconInfo from "@/assets/icons/info.svg";
|
||||
|
||||
type Props = {
|
||||
type?: "file" | "audio" | "image" | "video";
|
||||
url?: string;
|
||||
};
|
||||
const InfoMap = {
|
||||
file: {
|
||||
@@ -32,8 +34,15 @@ const InfoMap = {
|
||||
icon: <IconVideo className="w-9 shrink-0 h-auto grayscale" />
|
||||
}
|
||||
};
|
||||
const ExpiredMessage = ({ type = "file" }: Props) => {
|
||||
const ExpiredMessage = ({ type = "file", url = "" }: Props) => {
|
||||
const { setExpired } = useExpiredResMap();
|
||||
const { title, desc, icon } = InfoMap[type];
|
||||
useEffect(() => {
|
||||
if (url) {
|
||||
setExpired(url);
|
||||
}
|
||||
}, [url]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
|
||||
@@ -37,7 +37,7 @@ const ImageMessage: FC<Props> = ({
|
||||
};
|
||||
img.src = url;
|
||||
}, [url]);
|
||||
if (status == "error") return <ExpiredMessage type="image" />;
|
||||
if (status == "error") return <ExpiredMessage type="image" url={url} />;
|
||||
return (
|
||||
<div
|
||||
className={`relative overflow-hidden`}
|
||||
|
||||
@@ -48,7 +48,7 @@ const OtherFileMessage = ({
|
||||
});
|
||||
}
|
||||
}, [content]);
|
||||
if (error) return <ExpiredMessage />;
|
||||
if (error) return <ExpiredMessage url={content} />;
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
|
||||
@@ -38,7 +38,7 @@ const VideoMessage = ({ url, name, size, download }: Props) => {
|
||||
setError(true);
|
||||
};
|
||||
const tipClass = "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2";
|
||||
if (error) return <ExpiredMessage type="video" />;
|
||||
if (error) return <ExpiredMessage type="video" url={url} />;
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
|
||||
@@ -2,11 +2,13 @@ import { FC, useEffect, useState } from "react";
|
||||
|
||||
import { useAppSelector } from "@/app/store";
|
||||
import { ChatContext } from "@/types/common";
|
||||
import useExpiredResMap from "@/hooks/useExpiredResMap";
|
||||
import useRemoveLocalMessage from "@/hooks/useRemoveLocalMessage";
|
||||
import useSendMessage from "@/hooks/useSendMessage";
|
||||
import useUploadFile from "@/hooks/useUploadFile";
|
||||
import { getImageSize, isImage } from "@/utils";
|
||||
import AudioMessage from "./AudioMessage";
|
||||
import ExpiredMessage from "./ExpiredMessage";
|
||||
import ImageMessage from "./ImageMessage";
|
||||
import OtherFileMessage from "./OtherFileMessage";
|
||||
import VideoMessage from "./VideoMessage";
|
||||
@@ -41,6 +43,7 @@ const FileMessage: FC<Props> = ({
|
||||
thumbnail = "",
|
||||
properties = { local_id: 0, name: "", size: 0, content_type: "" }
|
||||
}) => {
|
||||
const { isExpired } = useExpiredResMap();
|
||||
const [imageSize, setImageSize] = useState(null);
|
||||
const [uploadingFile, setUploadingFile] = useState(false);
|
||||
const removeLocalMessage = useRemoveLocalMessage({ context, id: to });
|
||||
@@ -136,7 +139,9 @@ const FileMessage: FC<Props> = ({
|
||||
const sending = uploadingFile || isSending;
|
||||
// image
|
||||
if (isImage(content_type, size))
|
||||
return (
|
||||
return isExpired(thumbnail || content) ? (
|
||||
<ExpiredMessage type="image" />
|
||||
) : (
|
||||
<ImageMessage
|
||||
key={properties?.local_id}
|
||||
uploading={sending}
|
||||
@@ -147,12 +152,15 @@ const FileMessage: FC<Props> = ({
|
||||
thumbnail={thumbnail}
|
||||
/>
|
||||
);
|
||||
|
||||
const isVideo = content_type.startsWith("video");
|
||||
const isAudio = content_type.startsWith("audio");
|
||||
if (isExpired(content) && !sending)
|
||||
return <ExpiredMessage type={isAudio ? "audio" : isVideo ? "video" : "file"} />;
|
||||
// video
|
||||
if (content_type.startsWith("video") && !sending)
|
||||
if (isVideo && !sending)
|
||||
return <VideoMessage size={size} url={content} name={name} download={download} />;
|
||||
// audio
|
||||
if (content_type.startsWith("audio") && !sending)
|
||||
if (isAudio && !sending)
|
||||
return <AudioMessage size={size} url={content} name={name} download={download} />;
|
||||
return (
|
||||
<OtherFileMessage
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useLocalstorageState } from "rooks";
|
||||
|
||||
const LOCAL_KEY = `EXPIRED_FILES_MAP`;
|
||||
const useExpiredResMap = () => {
|
||||
const [map, setMap, remove] = useLocalstorageState(LOCAL_KEY, {} as Record<string, number>);
|
||||
const setExpired = (url: string) => {
|
||||
const key = new URL(url).searchParams.get("file_path");
|
||||
if (!key || map[key]) return;
|
||||
setMap((prev) => {
|
||||
return { ...prev, [key]: Date.now() };
|
||||
});
|
||||
};
|
||||
const isExpired = (url: string) => {
|
||||
const key = new URL(url).searchParams.get("file_path");
|
||||
if (!key || !map[key]) return false;
|
||||
return true;
|
||||
};
|
||||
// const removeFromExpired = (key: string) => {
|
||||
// setMap((prev) => {
|
||||
// const { [key]: _, ...rest } = prev;
|
||||
// return rest;
|
||||
// });
|
||||
// };
|
||||
const clearExpired = () => {
|
||||
remove();
|
||||
};
|
||||
|
||||
return { setExpired, isExpired, clearExpired };
|
||||
};
|
||||
export default useExpiredResMap;
|
||||
@@ -40,10 +40,9 @@ const HomePage = lazy(() => import("./home"));
|
||||
let toastId: string;
|
||||
const PageRoutes = () => {
|
||||
const {
|
||||
ui: { online },
|
||||
fileMessages
|
||||
ui: { online }
|
||||
} = useAppSelector((store) => {
|
||||
return { ui: store.ui, fileMessages: store.fileMessage };
|
||||
return { ui: store.ui };
|
||||
}, isEqual);
|
||||
// 提前获取device token
|
||||
useDeviceToken(vapidKey);
|
||||
@@ -283,7 +282,7 @@ const PageRoutes = () => {
|
||||
path="files"
|
||||
element={
|
||||
<LazyIt>
|
||||
<ResourceManagement fileMessages={fileMessages} />
|
||||
<ResourceManagement />
|
||||
</LazyIt>
|
||||
}
|
||||
></Route>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// @ts-nocheck
|
||||
import { memo, useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import Masonry from "masonry-layout";
|
||||
|
||||
import { useAppSelector } from "@/app/store";
|
||||
import FileBox from "@/components/FileBox";
|
||||
import useExpiredResMap from "@/hooks/useExpiredResMap";
|
||||
import Filter from "./Filter";
|
||||
import Search from "./Search";
|
||||
import View from "./View";
|
||||
@@ -37,15 +38,16 @@ const checkFilter = (data, filter, channelMessage) => {
|
||||
};
|
||||
|
||||
let msnry: Masonry | null;
|
||||
function ResourceManagement({ fileMessages }) {
|
||||
function ResourceManagement() {
|
||||
const { isExpired } = useExpiredResMap();
|
||||
const listContainerRef = useRef<HTMLDivElement>();
|
||||
const [filter, setFilter] = useState({});
|
||||
const { message, view, channelMessage, userData } = useAppSelector((store) => {
|
||||
const { message, view, channelMessage, fileMsgs } = useAppSelector((store) => {
|
||||
return {
|
||||
fileMsgs: store.fileMessage,
|
||||
message: store.message,
|
||||
channelMessage: store.channelMessage,
|
||||
view: store.ui.fileListView,
|
||||
userData: store.users.byId
|
||||
view: store.ui.fileListView
|
||||
};
|
||||
});
|
||||
|
||||
@@ -83,6 +85,15 @@ function ResourceManagement({ fileMessages }) {
|
||||
}
|
||||
}
|
||||
}, [view, filter]);
|
||||
// const fileMessages = fileMsgs.filter((id) => {
|
||||
// const data = message[id];
|
||||
// if (!data) return false;
|
||||
// const { content, thumbnail } = data;
|
||||
// let url = thumbnail || content;
|
||||
// console.log("ff fitler", url, isExpired(url));
|
||||
|
||||
// return !isExpired(url);
|
||||
// });
|
||||
|
||||
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">
|
||||
@@ -99,15 +110,17 @@ function ResourceManagement({ fileMessages }) {
|
||||
)}
|
||||
ref={listContainerRef}
|
||||
>
|
||||
{fileMessages.map((id) => {
|
||||
{fileMsgs.map((id) => {
|
||||
const data = message[id];
|
||||
if (!data) return null;
|
||||
const isSelected = checkFilter(data, filter, channelMessage);
|
||||
// 过滤掉不存在的用户
|
||||
// const fromUser = userData[data.from_uid];
|
||||
if (!isSelected) return null;
|
||||
const { mid, content, created_at, from_uid, properties } = data;
|
||||
const { mid, thumbnail, content, created_at, from_uid, properties } = data;
|
||||
const { name, content_type, size } = properties ?? {};
|
||||
const url = thumbnail || content;
|
||||
if (isExpired(url)) return null;
|
||||
return (
|
||||
<div key={mid} className="grid-box mb-2">
|
||||
<FileBox
|
||||
@@ -115,7 +128,7 @@ function ResourceManagement({ fileMessages }) {
|
||||
flex={view == "item"}
|
||||
key={mid}
|
||||
file_type={content_type}
|
||||
content={content}
|
||||
content={thumbnail || content}
|
||||
created_at={created_at}
|
||||
from_uid={from_uid}
|
||||
size={size}
|
||||
@@ -129,8 +142,4 @@ function ResourceManagement({ fileMessages }) {
|
||||
);
|
||||
}
|
||||
|
||||
const equals = (a, b) => a.length === b.length && a.every((v, i) => v == b[i]);
|
||||
|
||||
export default memo(ResourceManagement, (prevs, nexts) => {
|
||||
return equals(prevs.fileMessages, nexts.fileMessages);
|
||||
});
|
||||
export default ResourceManagement;
|
||||
|
||||
Reference in New Issue
Block a user