refactor: expired file message

This commit is contained in:
Tristan Yang
2023-06-09 16:35:51 +08:00
parent 10ccfc588b
commit 5db3adfef8
10 changed files with 111 additions and 30 deletions
+29 -3
View File
@@ -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
+1 -1
View File
@@ -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(
+11 -2
View File
@@ -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(
+1 -1
View File
@@ -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(
+1 -1
View File
@@ -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(
+12 -4
View File
@@ -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
+30
View File
@@ -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;
+3 -4
View File
@@ -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>
+22 -13
View File
@@ -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;