feat: upload files and management
@@ -22,6 +22,10 @@ const tables = [
|
|||||||
storeName: "message",
|
storeName: "message",
|
||||||
description: "store message with key-val full data",
|
description: "store message with key-val full data",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
storeName: "messageFile",
|
||||||
|
description: "store file message list refs",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
storeName: "messageReaction",
|
storeName: "messageReaction",
|
||||||
description: "store message reaction with key-val full data",
|
description: "store message reaction with key-val full data",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { fullfillUserMsg } from "../slices/message.user";
|
|||||||
import { fullfillChannels } from "../slices/channels";
|
import { fullfillChannels } from "../slices/channels";
|
||||||
import { fullfillContacts } from "../slices/contacts";
|
import { fullfillContacts } from "../slices/contacts";
|
||||||
import { fullfillFootprint } from "../slices/footprint";
|
import { fullfillFootprint } from "../slices/footprint";
|
||||||
|
import { fullfillFileMessage } from "../slices/message.file";
|
||||||
import { fullfillUI } from "../slices/ui";
|
import { fullfillUI } from "../slices/ui";
|
||||||
const useRehydrate = () => {
|
const useRehydrate = () => {
|
||||||
const [iterated, setIterated] = useState(false);
|
const [iterated, setIterated] = useState(false);
|
||||||
@@ -17,6 +18,7 @@ const useRehydrate = () => {
|
|||||||
const rehydrateData = {
|
const rehydrateData = {
|
||||||
channels: [],
|
channels: [],
|
||||||
contacts: [],
|
contacts: [],
|
||||||
|
fileMessage: {},
|
||||||
channelMessage: {},
|
channelMessage: {},
|
||||||
userMessage: {},
|
userMessage: {},
|
||||||
reactionMessage: {},
|
reactionMessage: {},
|
||||||
@@ -50,6 +52,9 @@ const useRehydrate = () => {
|
|||||||
case "messageChannel":
|
case "messageChannel":
|
||||||
rehydrateData.channelMessage[key] = data;
|
rehydrateData.channelMessage[key] = data;
|
||||||
break;
|
break;
|
||||||
|
case "messageFile":
|
||||||
|
rehydrateData.fileMessage[key] = data || [];
|
||||||
|
break;
|
||||||
case "messageDM":
|
case "messageDM":
|
||||||
rehydrateData.userMessage[key] = data;
|
rehydrateData.userMessage[key] = data;
|
||||||
break;
|
break;
|
||||||
@@ -74,6 +79,8 @@ const useRehydrate = () => {
|
|||||||
dispatch(fullfillServer(rehydrateData.server));
|
dispatch(fullfillServer(rehydrateData.server));
|
||||||
console.log("fullfill channels from indexedDB");
|
console.log("fullfill channels from indexedDB");
|
||||||
dispatch(fullfillChannels(rehydrateData.channels));
|
dispatch(fullfillChannels(rehydrateData.channels));
|
||||||
|
// file message
|
||||||
|
dispatch(fullfillFileMessage(rehydrateData.fileMessage.list));
|
||||||
dispatch(fullfillChannelMsg(rehydrateData.channelMessage));
|
dispatch(fullfillChannelMsg(rehydrateData.channelMessage));
|
||||||
dispatch(fullfillUserMsg(rehydrateData.userMessage));
|
dispatch(fullfillUserMsg(rehydrateData.userMessage));
|
||||||
dispatch(fullfillMessage(rehydrateData.message));
|
dispatch(fullfillMessage(rehydrateData.message));
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import clearTable from "./clear.handler";
|
||||||
|
export default async function handler({ operation, data = {} }) {
|
||||||
|
const table = window.CACHE["messageFile"];
|
||||||
|
if (operation.startsWith("reset")) {
|
||||||
|
clearTable("messageFile");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (operation) {
|
||||||
|
case "addFileMessage":
|
||||||
|
case "removeFileMessage":
|
||||||
|
{
|
||||||
|
console.log("file message opt", data, operation);
|
||||||
|
await table.setItem("list", data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,16 +23,16 @@ export default async function handler({ operation, data = {}, payload }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "readMessage":
|
// case "readMessage":
|
||||||
{
|
// {
|
||||||
const mids = Array.isArray(payload) ? payload : [payload];
|
// const mids = Array.isArray(payload) ? payload : [payload];
|
||||||
await Promise.all(
|
// await Promise.all(
|
||||||
mids.map(async (mid) => {
|
// mids.map(async (mid) => {
|
||||||
await table.setItem(mid + "", data[mid]);
|
// await table.setItem(mid + "", data[mid]);
|
||||||
})
|
// })
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
break;
|
// break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import channelMsgHandler from "./handler.channel.msg";
|
|||||||
import dmMsgHandler from "./handler.dm.msg";
|
import dmMsgHandler from "./handler.dm.msg";
|
||||||
import serverHandler from "./handler.server";
|
import serverHandler from "./handler.server";
|
||||||
import messageHandler from "./handler.message";
|
import messageHandler from "./handler.message";
|
||||||
|
import fileMessageHandler from "./handler.file.msg";
|
||||||
import reactionHandler from "./handler.reaction";
|
import reactionHandler from "./handler.reaction";
|
||||||
import UIHandler from "./handler.ui";
|
import UIHandler from "./handler.ui";
|
||||||
import footprintHandler from "./handler.footprint";
|
import footprintHandler from "./handler.footprint";
|
||||||
@@ -16,6 +17,7 @@ const operations = [
|
|||||||
"contacts",
|
"contacts",
|
||||||
"userMessage",
|
"userMessage",
|
||||||
"reactionMessage",
|
"reactionMessage",
|
||||||
|
"fileMessage",
|
||||||
"message",
|
"message",
|
||||||
"ui",
|
"ui",
|
||||||
"footprint",
|
"footprint",
|
||||||
@@ -28,7 +30,7 @@ const listenerMiddleware = createListenerMiddleware();
|
|||||||
// Add one or more listener entries that look for specific actions.
|
// Add one or more listener entries that look for specific actions.
|
||||||
// They may contain any sync or async logic, similar to thunks.
|
// They may contain any sync or async logic, similar to thunks.
|
||||||
listenerMiddleware.startListening({
|
listenerMiddleware.startListening({
|
||||||
predicate: (action, currentState, previousState) => {
|
predicate: (action) => {
|
||||||
const { type = "" } = action;
|
const { type = "" } = action;
|
||||||
const [prefix] = type.split("/");
|
const [prefix] = type.split("/");
|
||||||
console.log("operation", type, operations.includes(prefix));
|
console.log("operation", type, operations.includes(prefix));
|
||||||
@@ -89,6 +91,15 @@ listenerMiddleware.startListening({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "fileMessage":
|
||||||
|
{
|
||||||
|
await fileMessageHandler({
|
||||||
|
operation,
|
||||||
|
// payload,
|
||||||
|
data: state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "message":
|
case "message":
|
||||||
{
|
{
|
||||||
await messageHandler({
|
await messageHandler({
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
const initialState = [];
|
||||||
|
const fileMessageSlice = createSlice({
|
||||||
|
name: "fileMessage",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
resetFileMessage() {
|
||||||
|
return initialState;
|
||||||
|
},
|
||||||
|
fullfillFileMessage(state, action) {
|
||||||
|
return action.payload || [];
|
||||||
|
},
|
||||||
|
addFileMessage(state, action) {
|
||||||
|
const mid = action.payload;
|
||||||
|
// 加入file message 列表
|
||||||
|
const fidIdx = state.findIndex((fid) => fid == mid);
|
||||||
|
if (fidIdx == -1) {
|
||||||
|
state.unshift(+mid);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeFileMessage(state, action) {
|
||||||
|
const mids = Array.isArray(action.payload)
|
||||||
|
? action.payload
|
||||||
|
: [action.payload];
|
||||||
|
mids.forEach((id) => {
|
||||||
|
// 从file message 列表删掉
|
||||||
|
const fidIdx = state.fileMessages.findIndex((fid) => fid == id);
|
||||||
|
if (fidIdx > -1) {
|
||||||
|
state.splice(fidIdx, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export const {
|
||||||
|
removeFileMessage,
|
||||||
|
resetFileMessage,
|
||||||
|
fullfillFileMessage,
|
||||||
|
addFileMessage,
|
||||||
|
} = fileMessageSlice.actions;
|
||||||
|
export default fileMessageSlice.reducer;
|
||||||
@@ -2,7 +2,6 @@ import { createSlice } from "@reduxjs/toolkit";
|
|||||||
import BASE_URL, { ContentTypes } from "../config";
|
import BASE_URL, { ContentTypes } from "../config";
|
||||||
const initialState = {
|
const initialState = {
|
||||||
replying: {},
|
replying: {},
|
||||||
fileMessages: [],
|
|
||||||
};
|
};
|
||||||
const messageSlice = createSlice({
|
const messageSlice = createSlice({
|
||||||
name: "message",
|
name: "message",
|
||||||
@@ -18,16 +17,6 @@ const messageSlice = createSlice({
|
|||||||
const { mid, ...rest } = action.payload;
|
const { mid, ...rest } = action.payload;
|
||||||
state[mid] = { ...state[mid], ...rest };
|
state[mid] = { ...state[mid], ...rest };
|
||||||
},
|
},
|
||||||
readMessage(state, action) {
|
|
||||||
const mids = Array.isArray(action.payload)
|
|
||||||
? action.payload
|
|
||||||
: [action.payload];
|
|
||||||
mids.forEach((id) => {
|
|
||||||
if (state[id]) {
|
|
||||||
state[id].read = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
addMessage(state, action) {
|
addMessage(state, action) {
|
||||||
const data = action.payload;
|
const data = action.payload;
|
||||||
const { mid, sending, content_type, content } = data;
|
const { mid, sending, content_type, content } = data;
|
||||||
@@ -41,8 +30,6 @@ const messageSlice = createSlice({
|
|||||||
data.content = `${BASE_URL}/resource/file?file_path=${encodeURIComponent(
|
data.content = `${BASE_URL}/resource/file?file_path=${encodeURIComponent(
|
||||||
data.file_path
|
data.file_path
|
||||||
)}`;
|
)}`;
|
||||||
// 加入file message 列表
|
|
||||||
state.fileMessages.unshift(mid);
|
|
||||||
}
|
}
|
||||||
// image
|
// image
|
||||||
if (!sending && isImage) {
|
if (!sending && isImage) {
|
||||||
@@ -62,11 +49,6 @@ const messageSlice = createSlice({
|
|||||||
: [action.payload];
|
: [action.payload];
|
||||||
mids.forEach((id) => {
|
mids.forEach((id) => {
|
||||||
delete state[id];
|
delete state[id];
|
||||||
// 从file message 列表删掉
|
|
||||||
const fidIdx = state.fileMessages.findIndex((fid) => fid == id);
|
|
||||||
if (fidIdx > -1) {
|
|
||||||
state.fileMessages.splice(fidIdx, 1);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
addReplyingMessage(state, action) {
|
addReplyingMessage(state, action) {
|
||||||
@@ -87,7 +69,6 @@ export const {
|
|||||||
fullfillMessage,
|
fullfillMessage,
|
||||||
setMessage,
|
setMessage,
|
||||||
updateMessage,
|
updateMessage,
|
||||||
readMessage,
|
|
||||||
addMessage,
|
addMessage,
|
||||||
removeMessage,
|
removeMessage,
|
||||||
addReplyingMessage,
|
addReplyingMessage,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import contactsReducer from "./slices/contacts";
|
|||||||
import reactionMsgReducer from "./slices/message.reaction";
|
import reactionMsgReducer from "./slices/message.reaction";
|
||||||
import channelMsgReducer from "./slices/message.channel";
|
import channelMsgReducer from "./slices/message.channel";
|
||||||
import userMsgReducer from "./slices/message.user";
|
import userMsgReducer from "./slices/message.user";
|
||||||
|
import fileMsgReducer from "./slices/message.file";
|
||||||
import messageReducer from "./slices/message";
|
import messageReducer from "./slices/message";
|
||||||
import { authApi } from "./services/auth";
|
import { authApi } from "./services/auth";
|
||||||
import { contactApi } from "./services/contact";
|
import { contactApi } from "./services/contact";
|
||||||
@@ -27,6 +28,7 @@ const reducer = combineReducers({
|
|||||||
reactionMessage: reactionMsgReducer,
|
reactionMessage: reactionMsgReducer,
|
||||||
userMessage: userMsgReducer,
|
userMessage: userMsgReducer,
|
||||||
channelMessage: channelMsgReducer,
|
channelMessage: channelMsgReducer,
|
||||||
|
fileMessage: fileMsgReducer,
|
||||||
message: messageReducer,
|
message: messageReducer,
|
||||||
[authApi.reducerPath]: authApi.reducer,
|
[authApi.reducerPath]: authApi.reducer,
|
||||||
[messageApi.reducerPath]: messageApi.reducer,
|
[messageApi.reducerPath]: messageApi.reducer,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<path d="M8.79053 19.3376C10.0779 20.3775 11.7161 21.0002 13.4999 21.0002C14.6315 21.0002 15.7064 20.7491 16.6701 20.299L20.1027 20.9856C20.3486 21.0348 20.6029 20.9579 20.7802 20.7806C20.9575 20.6032 21.0345 20.349 20.9853 20.1031L20.2988 16.6702C20.7488 15.7065 20.9999 14.6318 20.9999 13.5002C20.9999 11.7168 20.3774 10.0788 19.338 8.79163C19.4443 9.3448 19.4999 9.91599 19.4999 10.5002C19.4999 11.0652 19.4479 11.618 19.3483 12.1541C19.4475 12.5868 19.4999 13.0374 19.4999 13.5002C19.4999 14.4837 19.2638 15.41 18.8458 16.2274C18.7689 16.3779 18.745 16.5501 18.7782 16.7159L19.2938 19.2941L16.7159 18.7784C16.55 18.7452 16.3778 18.7691 16.2273 18.8461C15.4098 19.2641 14.4835 19.5002 13.4999 19.5002C13.037 19.5002 12.5864 19.4478 12.1537 19.3486C11.6176 19.4481 11.0648 19.5002 10.4999 19.5002C9.91613 19.5002 9.34454 19.4443 8.79053 19.3376ZM10.5 3C6.35788 3 3.00001 6.35786 3.00001 10.5C3.00001 11.6316 3.2511 12.7064 3.70112 13.6701L3.01458 17.103C2.9654 17.3489 3.04237 17.6031 3.2197 17.7804C3.39702 17.9577 3.65123 18.0347 3.89713 17.9855L7.32974 17.2988C8.29349 17.7489 9.36836 18 10.5 18C14.6421 18 18 14.6421 18 10.5C18 6.35786 14.6421 3 10.5 3Z" fill="#4B5563"/>
|
||||||
<path d="M11.7207 25.7835C13.4371 27.17 15.6215 28.0002 17.9998 28.0002C19.5087 28.0002 20.9418 27.6654 22.2269 27.0653L26.8037 27.9809C27.1315 28.0464 27.4705 27.9438 27.7069 27.7074C27.9433 27.471 28.046 27.132 27.9804 26.8042L27.065 22.227C27.665 20.942 27.9998 19.509 27.9998 18.0002C27.9998 15.6224 27.1699 13.4385 25.7839 11.7222C25.9257 12.4597 25.9999 13.2213 25.9999 14.0002C25.9999 14.7535 25.9305 15.4906 25.7977 16.2055C25.93 16.7824 25.9998 17.3832 25.9998 18.0002C25.9998 19.3117 25.685 20.5466 25.1278 21.6365C25.0252 21.8373 24.9934 22.0668 25.0376 22.2879L25.7251 25.7255L22.2878 25.0379C22.0667 24.9936 21.8371 25.0254 21.6364 25.1281C20.5464 25.6854 19.3113 26.0002 17.9998 26.0002C17.3827 26.0002 16.7819 25.9304 16.2049 25.7981C15.4901 25.9308 14.7531 26.0002 13.9999 26.0002C13.2215 26.0002 12.4594 25.9258 11.7207 25.7835ZM14 4C8.47717 4 4.00002 8.47715 4.00002 14C4.00002 15.5088 4.3348 16.9418 4.93483 18.2267L4.01943 22.8039C3.95386 23.1318 4.05649 23.4707 4.29293 23.7072C4.52936 23.9436 4.86831 24.0462 5.19617 23.9806L9.77298 23.0651C11.058 23.6652 12.4911 24 14 24C19.5229 24 24 19.5228 24 14C24 8.47715 19.5229 4 14 4Z" fill="#4B5563"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<path d="M3.74984 6.82964C3.47658 6.82964 3.22041 6.90284 2.99988 7.0307C2.553 7.28978 2.25244 7.77331 2.25244 8.32704C2.25244 8.88077 2.553 9.3643 2.99988 9.62339C3.22041 9.75124 3.47658 9.82444 3.74984 9.82444C4.57683 9.82444 5.24724 9.15403 5.24724 8.32704C5.24724 7.50005 4.57683 6.82964 3.74984 6.82964ZM3.74984 5.32964C3.58384 5.32964 3.42098 5.34313 3.26232 5.36909C3.81224 3.98139 5.16645 3 6.74988 3H17.2499C19.3209 3 20.9999 4.67893 20.9999 6.75V17.25C20.9999 19.3211 19.3209 21 17.2499 21H6.74988C4.67881 21 2.99988 19.3211 2.99988 17.25V11.2299C3.23957 11.2916 3.49087 11.3244 3.74984 11.3244C5.40526 11.3244 6.74724 9.98246 6.74724 8.32704C6.74724 6.67162 5.40526 5.32964 3.74984 5.32964ZM8.24988 9.75C8.24988 10.1642 8.58566 10.5 8.99988 10.5H14.9999C15.4141 10.5 15.7499 10.1642 15.7499 9.75C15.7499 9.33579 15.4141 9 14.9999 9H8.99988C8.58566 9 8.24988 9.33579 8.24988 9.75ZM8.24988 14.25C8.24988 14.6642 8.58566 15 8.99988 15H12.9207C13.335 15 13.6707 14.6642 13.6707 14.25C13.6707 13.8358 13.335 13.5 12.9207 13.5H8.99988C8.58566 13.5 8.24988 13.8358 8.24988 14.25Z" fill="#4B5563"/>
|
||||||
<path d="M4.99995 9.10619C4.6356 9.10619 4.29404 9.20378 4 9.37426C3.40416 9.71971 3.00342 10.3644 3.00342 11.1027C3.00342 11.841 3.40416 12.4857 4 12.8312C4.29404 13.0017 4.6356 13.0993 4.99995 13.0993C6.10261 13.0993 6.99649 12.2054 6.99649 11.1027C6.99649 10.0001 6.10261 9.10619 4.99995 9.10619ZM4.99995 7.10619C4.77861 7.10619 4.56147 7.12418 4.34992 7.15878C5.08315 5.30851 6.88877 4 9 4H23C25.7614 4 28 6.23858 28 9V23C28 25.7614 25.7614 28 23 28H9C6.23858 28 4 25.7614 4 23V14.9731C4.31959 15.0555 4.65466 15.0993 4.99995 15.0993C7.20718 15.0993 8.99649 13.3099 8.99649 11.1027C8.99649 8.8955 7.20718 7.10619 4.99995 7.10619ZM11 13C11 13.5523 11.4477 14 12 14H20C20.5523 14 21 13.5523 21 13C21 12.4477 20.5523 12 20 12H12C11.4477 12 11 12.4477 11 13ZM11 19C11 19.5523 11.4477 20 12 20H17.2278C17.7801 20 18.2278 19.5523 18.2278 19C18.2278 18.4477 17.7801 18 17.2278 18H12C11.4477 18 11 18.4477 11 19Z" fill="#4B5563"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3.5 2C2.67157 2 2 2.67157 2 3.5V5.5C2 6.32843 2.67157 7 3.5 7H5.5C6.32843 7 7 6.32843 7 5.5V3.5C7 2.67157 6.32843 2 5.5 2H3.5ZM10.5 2C9.67157 2 9 2.67157 9 3.5V5.5C9 6.32843 9.67157 7 10.5 7H12.5C13.3284 7 14 6.32843 14 5.5V3.5C14 2.67157 13.3284 2 12.5 2H10.5ZM3.5 9C2.67157 9 2 9.67157 2 10.5V12.5C2 13.3284 2.67157 14 3.5 14H5.5C6.32843 14 7 13.3284 7 12.5V10.5C7 9.67157 6.32843 9 5.5 9H3.5ZM10.5 9C9.67157 9 9 9.67157 9 10.5V12.5C9 13.3284 9.67157 14 10.5 14H12.5C13.3284 14 14 13.3284 14 12.5V10.5C14 9.67157 13.3284 9 12.5 9H10.5Z" fill="#344054"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 669 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2 3.75C2 3.33579 2.33579 3 2.75 3H10.25C10.6642 3 11 3.33579 11 3.75C11 4.16421 10.6642 4.5 10.25 4.5H2.75C2.33579 4.5 2 4.16421 2 3.75ZM2 11.75C2 11.3358 2.33579 11 2.75 11H9.25C9.66421 11 10 11.3358 10 11.75C10 12.1642 9.66421 12.5 9.25 12.5H2.75C2.33579 12.5 2 12.1642 2 11.75ZM2.75 7C2.33579 7 2 7.33579 2 7.75C2 8.16421 2.33579 8.5 2.75 8.5H13.25C13.6642 8.5 14 8.16421 14 7.75C14 7.33579 13.6642 7 13.25 7H2.75Z" fill="#344054"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 549 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.821 6.5H19.75C20.8867 6.5 21.8266 7.34297 21.9785 8.43788L21.9948 8.59595L22 8.75V17.75C22 18.9409 21.0748 19.9156 19.904 19.9948L19.75 20H4.25C3.05914 20 2.08436 19.0748 2.00519 17.904L2 17.75V10.499L8.20693 10.5L8.40335 10.4914C8.79396 10.4572 9.16896 10.3214 9.49094 10.0977L9.64734 9.9785L13.821 6.5ZM8.20693 4C8.66749 4 9.1153 4.14129 9.49094 4.40235L9.64734 4.5215L11.75 6.273L8.68706 8.82617L8.60221 8.88738C8.51363 8.94232 8.41452 8.9782 8.31129 8.9927L8.20693 9L2 8.999V6.25C2 5.05914 2.92516 4.08436 4.09595 4.00519L4.25 4H8.20693Z" fill="#4B5563"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 676 B |
@@ -0,0 +1,98 @@
|
|||||||
|
// import React from 'react'
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import Styled from "./styled";
|
||||||
|
import {
|
||||||
|
VideoPreview,
|
||||||
|
AudioPreview,
|
||||||
|
ImagePreview,
|
||||||
|
PdfPreview,
|
||||||
|
CodePreview,
|
||||||
|
} from "./preview";
|
||||||
|
import { getFileIcon, formatBytes } from "../../utils";
|
||||||
|
import IconDownload from "../../../assets/icons/download.svg";
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
const renderPreview = (data) => {
|
||||||
|
const { file_type, name, content } = data;
|
||||||
|
let preview = null;
|
||||||
|
|
||||||
|
const checks = {
|
||||||
|
image: /^image/gi,
|
||||||
|
audio: /^audio/gi,
|
||||||
|
video: /^video/gi,
|
||||||
|
code: /(json|javascript|java|rb|c|php|xml|css|html)$/gi,
|
||||||
|
doc: /^text/gi,
|
||||||
|
pdf: /\/pdf$/gi,
|
||||||
|
};
|
||||||
|
const _arr = name.split(".");
|
||||||
|
const _type = file_type || _arr[_arr.length - 1];
|
||||||
|
switch (true) {
|
||||||
|
case checks.image.test(_type):
|
||||||
|
{
|
||||||
|
console.log("image");
|
||||||
|
preview = <ImagePreview url={content} />;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case checks.pdf.test(_type):
|
||||||
|
preview = <PdfPreview url={content} />;
|
||||||
|
break;
|
||||||
|
case checks.code.test(_type):
|
||||||
|
preview = <CodePreview url={content} />;
|
||||||
|
break;
|
||||||
|
// case checks.doc.test(_type):
|
||||||
|
// icon = <IconDoc className="icon" />;
|
||||||
|
// break;
|
||||||
|
case checks.audio.test(_type):
|
||||||
|
preview = <AudioPreview url={content} />;
|
||||||
|
break;
|
||||||
|
case checks.video.test(_type):
|
||||||
|
preview = <VideoPreview url={content} />;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// default:
|
||||||
|
// icon = <IconUnkown className="icon" />;
|
||||||
|
// break;
|
||||||
|
}
|
||||||
|
return preview;
|
||||||
|
};
|
||||||
|
export default function FileBox({
|
||||||
|
preview = false,
|
||||||
|
flex,
|
||||||
|
file_type,
|
||||||
|
name,
|
||||||
|
size,
|
||||||
|
created_at,
|
||||||
|
from_uid,
|
||||||
|
content,
|
||||||
|
}) {
|
||||||
|
const fromUser = useSelector((store) => store.contacts.byId[from_uid]);
|
||||||
|
const icon = getFileIcon(file_type, name);
|
||||||
|
if (!content || !fromUser || !name) return null;
|
||||||
|
console.log("file content", content, name, flex);
|
||||||
|
return (
|
||||||
|
<Styled className={flex ? "flex" : ""}>
|
||||||
|
<div className="basic">
|
||||||
|
{icon}
|
||||||
|
<div className="info">
|
||||||
|
<span className="name">{name}</span>
|
||||||
|
<span className="details">
|
||||||
|
<i className="size">{formatBytes(size)}</i>
|
||||||
|
<i className="time">{dayjs(created_at).fromNow()}</i>
|
||||||
|
<i className="from">
|
||||||
|
by <strong>{fromUser.name}</strong>
|
||||||
|
</i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<a className="download" download={name} href={content}>
|
||||||
|
<IconDownload />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{preview && (
|
||||||
|
<div className="preview">
|
||||||
|
{renderPreview({ file_type, content, name })}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Styled>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function Audio({ url = "" }) {
|
||||||
|
if (!url) return null;
|
||||||
|
return <audio src={url} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
const Styled = styled.div`
|
||||||
|
background-color: #fff;
|
||||||
|
height: 218px;
|
||||||
|
padding: 15px 15px 0 15px;
|
||||||
|
line-height: 1.4;
|
||||||
|
overflow: scroll;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
`;
|
||||||
|
export default function Code({ url = "" }) {
|
||||||
|
const [content, setContent] = useState("");
|
||||||
|
useEffect(() => {
|
||||||
|
const getContent = async (url) => {
|
||||||
|
if (!url) return;
|
||||||
|
const resp = await fetch(url);
|
||||||
|
const txt = await resp.text();
|
||||||
|
setContent(txt);
|
||||||
|
};
|
||||||
|
getContent(url);
|
||||||
|
}, [url]);
|
||||||
|
if (!content) return null;
|
||||||
|
|
||||||
|
return <Styled>{content}</Styled>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
const Styled = styled.div`
|
||||||
|
height: 218px;
|
||||||
|
overflow: hidden;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export default function Image({ url = "" }) {
|
||||||
|
if (!url) return null;
|
||||||
|
return (
|
||||||
|
<Styled>
|
||||||
|
<img src={url} />
|
||||||
|
</Styled>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { Document, Page } from "react-pdf";
|
||||||
|
const Styled = styled.div`
|
||||||
|
height: 218px;
|
||||||
|
overflow: hidden;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export default function Pdf({ url = "" }) {
|
||||||
|
const [pageNumber, setPageNumber] = useState(1);
|
||||||
|
const [numPages, setNumPages] = useState(null);
|
||||||
|
if (!url) return null;
|
||||||
|
const onDocumentLoadSuccess = ({ numPages }) => {
|
||||||
|
setNumPages(numPages);
|
||||||
|
};
|
||||||
|
console.log("pdf url", url);
|
||||||
|
return (
|
||||||
|
<Styled>
|
||||||
|
<Document file={url} onLoadSuccess={onDocumentLoadSuccess}>
|
||||||
|
<Page pageNumber={pageNumber} />
|
||||||
|
</Document>
|
||||||
|
</Styled>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
const Styled = styled.div`
|
||||||
|
height: 218px;
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export default function Video({ url = "" }) {
|
||||||
|
if (!url) return null;
|
||||||
|
return (
|
||||||
|
<Styled>
|
||||||
|
<video controls src={url} />
|
||||||
|
</Styled>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import VideoPreview from "./Video";
|
||||||
|
import AudioPreview from "./Audio";
|
||||||
|
import ImagePreview from "./Image";
|
||||||
|
import PdfPreview from "./Pdf";
|
||||||
|
import CodePreview from "./Code";
|
||||||
|
|
||||||
|
export { VideoPreview, AudioPreview, ImagePreview, PdfPreview, CodePreview };
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
const Styled = styled.div`
|
||||||
|
background: #f3f4f6;
|
||||||
|
border: 1px solid #d4d4d4;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 370px;
|
||||||
|
max-height: 281px;
|
||||||
|
height: fit-content;
|
||||||
|
overflow: hidden;
|
||||||
|
&.flex {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.basic {
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
.icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
width: 100%;
|
||||||
|
.name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #1c1c1e;
|
||||||
|
}
|
||||||
|
.details {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
color: #616161;
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
.from strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.download {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.preview {
|
||||||
|
/* todo */
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export default Styled;
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
// import React from 'react'
|
|
||||||
import styled from "styled-components";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { getFileIcon, formatBytes } from "../../utils";
|
|
||||||
import IconDownload from "../../../assets/icons/download.svg";
|
|
||||||
dayjs.extend(relativeTime);
|
|
||||||
const Styled = styled.div`
|
|
||||||
padding: 8px;
|
|
||||||
background: #f3f4f6;
|
|
||||||
border: 1px solid #d4d4d4;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 6px;
|
|
||||||
width: 370px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
.icon {
|
|
||||||
width: 36px;
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
.info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
width: 100%;
|
|
||||||
.name {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 20px;
|
|
||||||
color: #1c1c1e;
|
|
||||||
}
|
|
||||||
.details {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 18px;
|
|
||||||
color: #616161;
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
.from strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.download {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export default function FileBox({
|
|
||||||
file_type,
|
|
||||||
name,
|
|
||||||
size,
|
|
||||||
created_at,
|
|
||||||
from_uid,
|
|
||||||
content,
|
|
||||||
}) {
|
|
||||||
const fromUser = useSelector((store) => store.contacts.byId[from_uid]);
|
|
||||||
const icon = getFileIcon(file_type, name);
|
|
||||||
if (!content || !fromUser || !name) return null;
|
|
||||||
console.log("file content", content, name);
|
|
||||||
return (
|
|
||||||
<Styled>
|
|
||||||
{icon}
|
|
||||||
<div className="info">
|
|
||||||
<span className="name">{name}</span>
|
|
||||||
<span className="details">
|
|
||||||
<i className="size">{formatBytes(size)}</i>
|
|
||||||
<i className="time">{dayjs(created_at).fromNow()}</i>
|
|
||||||
<i className="from">
|
|
||||||
by <strong>{fromUser.name}</strong>
|
|
||||||
</i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<a className="download" download={name} href={content}>
|
|
||||||
<IconDownload />
|
|
||||||
</a>
|
|
||||||
</Styled>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,14 @@ export default function PreviewMessage({ mid = 0 }) {
|
|||||||
return { msg: store.message[mid], contactsData: store.contacts.byId };
|
return { msg: store.message[mid], contactsData: store.contacts.byId };
|
||||||
});
|
});
|
||||||
if (!msg) return null;
|
if (!msg) return null;
|
||||||
const { from_uid, created_at, content_type, content, thumbnail } = msg;
|
const {
|
||||||
|
from_uid,
|
||||||
|
created_at,
|
||||||
|
content_type,
|
||||||
|
content,
|
||||||
|
thumbnail,
|
||||||
|
properties,
|
||||||
|
} = msg;
|
||||||
const { name, avatar } = contactsData[from_uid];
|
const { name, avatar } = contactsData[from_uid];
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className={`preview`}>
|
<StyledWrapper className={`preview`}>
|
||||||
@@ -24,7 +31,13 @@ export default function PreviewMessage({ mid = 0 }) {
|
|||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
<div className={`down`}>
|
<div className={`down`}>
|
||||||
{renderContent({ content_type, content, thumbnail })}
|
{renderContent({
|
||||||
|
content_type,
|
||||||
|
content,
|
||||||
|
thumbnail,
|
||||||
|
from_uid,
|
||||||
|
properties,
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Linkify from "react-linkify";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import MrakdownRender from "../MrakdownRender";
|
import MrakdownRender from "../MrakdownRender";
|
||||||
import { getDefaultSize } from "../../utils";
|
import { getDefaultSize } from "../../utils";
|
||||||
import FileBox from "./FileBox";
|
import FileBox from "../FileBox";
|
||||||
const renderContent = ({
|
const renderContent = ({
|
||||||
from_uid,
|
from_uid,
|
||||||
created_at,
|
created_at,
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export default function useUploadImageMessage({
|
|||||||
}
|
}
|
||||||
sliceUploadedCountRef.current = sliceUploadedCountRef.current + 1;
|
sliceUploadedCountRef.current = sliceUploadedCountRef.current + 1;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log("upload file error", error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,13 @@ import {
|
|||||||
updateMessage,
|
updateMessage,
|
||||||
} from "../../../app/slices/message";
|
} from "../../../app/slices/message";
|
||||||
import { toggleReactionMessage } from "../../../app/slices/message.reaction";
|
import { toggleReactionMessage } from "../../../app/slices/message.reaction";
|
||||||
|
import {
|
||||||
|
addFileMessage,
|
||||||
|
removeFileMessage,
|
||||||
|
} from "../../../app/slices/message.file";
|
||||||
import { addUserMsg, removeUserMsg } from "../../../app/slices/message.user";
|
import { addUserMsg, removeUserMsg } from "../../../app/slices/message.user";
|
||||||
import { updateAfterMid } from "../../../app/slices/footprint";
|
import { updateAfterMid } from "../../../app/slices/footprint";
|
||||||
|
import { ContentTypes } from "../../../app/config";
|
||||||
const handler = (data, dispatch, currState) => {
|
const handler = (data, dispatch, currState) => {
|
||||||
const {
|
const {
|
||||||
mid,
|
mid,
|
||||||
@@ -72,6 +77,10 @@ const handler = (data, dispatch, currState) => {
|
|||||||
local_id: properties ? properties.local_id : null,
|
local_id: properties ? properties.local_id : null,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
// 加到file message 列表
|
||||||
|
if (content_type == ContentTypes.file) {
|
||||||
|
dispatch(addFileMessage(mid));
|
||||||
|
}
|
||||||
// }
|
// }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -121,6 +130,10 @@ const handler = (data, dispatch, currState) => {
|
|||||||
dispatch(removeContextMessage({ id, mid: detailMid }));
|
dispatch(removeContextMessage({ id, mid: detailMid }));
|
||||||
dispatch(removeMessage(detailMid));
|
dispatch(removeMessage(detailMid));
|
||||||
});
|
});
|
||||||
|
// 从file message 列表移除
|
||||||
|
if (content_type == ContentTypes.file) {
|
||||||
|
dispatch(removeFileMessage(detailMid));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "edit":
|
case "edit":
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import ChannelSettingModal from "../../common/component/ChannelSetting";
|
|||||||
|
|
||||||
import ChatIcon from "../../assets/icons/chat.svg?url";
|
import ChatIcon from "../../assets/icons/chat.svg?url";
|
||||||
import ContactIcon from "../../assets/icons/contact.svg?url";
|
import ContactIcon from "../../assets/icons/contact.svg?url";
|
||||||
|
import FolderIcon from "../../assets/icons/folder.svg?url";
|
||||||
// import NotificationHub from "../../common/component/NotificationHub";
|
// import NotificationHub from "../../common/component/NotificationHub";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
@@ -25,7 +26,7 @@ export default function HomePage() {
|
|||||||
ui: store.ui,
|
ui: store.ui,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const { data, loading, error, success } = usePreload();
|
const { data, loading } = usePreload();
|
||||||
const toggleExpand = () => {
|
const toggleExpand = () => {
|
||||||
dispatch(toggleMenuExpand());
|
dispatch(toggleMenuExpand());
|
||||||
};
|
};
|
||||||
@@ -58,6 +59,12 @@ export default function HomePage() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink className="link" to={"/files"}>
|
||||||
|
<img src={FolderIcon} alt="folder icon" />{" "}
|
||||||
|
{menuExpand && (
|
||||||
|
<span className="animate__animated animate__fadeIn">Files</span>
|
||||||
|
)}
|
||||||
|
</NavLink>
|
||||||
</nav>
|
</nav>
|
||||||
<div className="divider"></div>
|
<div className="divider"></div>
|
||||||
{/* <Tools expand={menuExpand} /> */}
|
{/* <Tools expand={menuExpand} /> */}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
const Styled = styled.div`
|
||||||
|
/* padding: 20px 0; */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
.filter {
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function Filter() {
|
||||||
|
return (
|
||||||
|
<Styled>
|
||||||
|
<div className="filter">filter item</div>
|
||||||
|
</Styled>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
const Styled = styled.div`
|
||||||
|
padding: 6px 16px;
|
||||||
|
`;
|
||||||
|
export default function Search() {
|
||||||
|
return <Styled>s</Styled>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// import React from 'react'
|
||||||
|
import styled from "styled-components";
|
||||||
|
import IconList from "../../assets/icons/file.list.svg";
|
||||||
|
import IconGrid from "../../assets/icons/file.grid.svg";
|
||||||
|
const Styled = styled.ul`
|
||||||
|
display: flex;
|
||||||
|
border: 1px solid #d0d5dd;
|
||||||
|
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.view {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
&.item .item,
|
||||||
|
&.grid .grid {
|
||||||
|
border: 1px solid #52edff;
|
||||||
|
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
svg {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
path {
|
||||||
|
fill: #52edff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export const Views = {
|
||||||
|
item: "item",
|
||||||
|
grid: "grid",
|
||||||
|
};
|
||||||
|
export default function View({ view = Views.item, toggleView }) {
|
||||||
|
const handleChangeView = (evt) => {
|
||||||
|
const { view: clickView } = evt.currentTarget.dataset;
|
||||||
|
if (clickView == view) return;
|
||||||
|
toggleView();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Styled className={view}>
|
||||||
|
<li
|
||||||
|
className="view item"
|
||||||
|
data-view={Views.item}
|
||||||
|
onClick={handleChangeView}
|
||||||
|
>
|
||||||
|
<IconList />
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
className="view grid"
|
||||||
|
data-view={Views.grid}
|
||||||
|
onClick={handleChangeView}
|
||||||
|
>
|
||||||
|
<IconGrid />
|
||||||
|
</li>
|
||||||
|
</Styled>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { useState, useEffect, useRef } from "react";
|
||||||
|
import Styled from "./styled";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
// import waterfall from "waterfall.js/src/waterfall";
|
||||||
|
import View, { Views } from "./View";
|
||||||
|
import Search from "./Search";
|
||||||
|
import Filter from "./Filter";
|
||||||
|
import FileBox from "../../common/component/FileBox";
|
||||||
|
export default function ResourceManagement() {
|
||||||
|
const listContainerRef = useRef(null);
|
||||||
|
const [view, setView] = useState(Views.item);
|
||||||
|
const { fileMessages, message } = useSelector((store) => {
|
||||||
|
return { message: store.message, fileMessages: store.fileMessage };
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggleView = () => {
|
||||||
|
setView((prev) => (prev == Views.item ? Views.grid : Views.item));
|
||||||
|
};
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (view == Views.grid && listContainerRef) {
|
||||||
|
// const wtf = waterfall(listContainerRef.current);
|
||||||
|
// console.log("wtf", wtf);
|
||||||
|
// waterfall
|
||||||
|
// }
|
||||||
|
// }, [view]);
|
||||||
|
|
||||||
|
console.log("files", fileMessages);
|
||||||
|
return (
|
||||||
|
<Styled>
|
||||||
|
<Search />
|
||||||
|
<div className="divider"></div>
|
||||||
|
<div className="opts">
|
||||||
|
<Filter />
|
||||||
|
<View view={view} toggleView={toggleView} />
|
||||||
|
</div>
|
||||||
|
<div className={`list ${view}`} ref={listContainerRef}>
|
||||||
|
{fileMessages.map((id) => {
|
||||||
|
const data = message[id];
|
||||||
|
if (!data) return null;
|
||||||
|
const {
|
||||||
|
mid,
|
||||||
|
content,
|
||||||
|
created_at,
|
||||||
|
from_uid,
|
||||||
|
properties: { name, file_type, size },
|
||||||
|
} = data;
|
||||||
|
return (
|
||||||
|
<FileBox
|
||||||
|
preview={view == Views.grid}
|
||||||
|
flex={view == Views.item}
|
||||||
|
key={mid}
|
||||||
|
file_type={file_type}
|
||||||
|
content={content}
|
||||||
|
created_at={created_at}
|
||||||
|
from_uid={from_uid}
|
||||||
|
size={size}
|
||||||
|
name={name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Styled>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import styled from "styled-components";
|
||||||
|
const Styled = styled.div`
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: scroll;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 0 16px;
|
||||||
|
.opts {
|
||||||
|
padding: 20px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
width: 100%;
|
||||||
|
gap: 8px;
|
||||||
|
&.item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
/* align-items: flex-start; */
|
||||||
|
}
|
||||||
|
&.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-template-rows: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Styled;
|
||||||