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