feat: files with filter
This commit is contained in:
@@ -4,15 +4,12 @@ import styled from "styled-components";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
min-height: 56px;
|
||||
padding: 0 20px;
|
||||
padding-right: 5px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
|
||||
&.expand {
|
||||
/* &.expand {
|
||||
padding-right: 16px;
|
||||
}
|
||||
} */
|
||||
.server {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -9,6 +9,7 @@ const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&.left {
|
||||
align-items: center;
|
||||
position: relative;
|
||||
background: #e5e7eb;
|
||||
width: 64px;
|
||||
@@ -21,7 +22,7 @@ const StyledWrapper = styled.div`
|
||||
margin: 8px 16px;
|
||||
}
|
||||
&.expand {
|
||||
width: 180px;
|
||||
width: 140px;
|
||||
}
|
||||
}
|
||||
&.right {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// 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,87 @@
|
||||
// import React from 'react'
|
||||
// import { useSelector } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import CheckSign from "../../../assets/icons/check.sign.svg";
|
||||
import ChannelIcon from "../../../common/component/ChannelIcon";
|
||||
import Search from "../Search";
|
||||
import useFilteredChannels from "../../../common/hook/useFilteredChannels";
|
||||
const Styled = styled.div`
|
||||
padding: 0 4px 4px 4px;
|
||||
background: #ffffff;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
box-shadow: 0px 24px 48px -12px rgba(16, 24, 40, 0.18);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
position: relative;
|
||||
> .search {
|
||||
z-index: 1;
|
||||
background-color: #fff;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
input {
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
> .list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 8px;
|
||||
.channel {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.name {
|
||||
color: #616161;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.check {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export default function Channel({ select = "", updateFilter }) {
|
||||
const { input, updateInput, channels } = useFilteredChannels();
|
||||
const handleClick = (gid) => {
|
||||
updateFilter({ channel: gid });
|
||||
};
|
||||
return (
|
||||
<Styled>
|
||||
<div className="search">
|
||||
<Search embed={true} value={input} updateSearchValue={updateInput} />
|
||||
</div>
|
||||
<ul className="list">
|
||||
<li className="channel" onClick={handleClick.bind(null, undefined)}>
|
||||
<ChannelIcon />
|
||||
<span className="name">Any Channel</span>
|
||||
{!select && <CheckSign className="check" />}
|
||||
</li>
|
||||
{channels.map(({ gid, is_public, name }) => {
|
||||
return (
|
||||
<li
|
||||
key={gid}
|
||||
className="channel"
|
||||
onClick={handleClick.bind(null, gid)}
|
||||
>
|
||||
<ChannelIcon personal={!is_public} />
|
||||
<span className="name">{name}</span>
|
||||
{select == gid && <CheckSign className="check" />}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// import React from 'react'
|
||||
import { useSelector } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import Search from "../Search";
|
||||
import CheckSign from "../../../assets/icons/check.sign.svg";
|
||||
|
||||
import Contact from "../../../common/component/Contact";
|
||||
import useFilteredUsers from "../../../common/hook/useFilteredUsers";
|
||||
const Styled = styled.div`
|
||||
padding: 0 4px 4px 4px;
|
||||
background: #ffffff;
|
||||
max-height: 230px;
|
||||
overflow: auto;
|
||||
box-shadow: 0px 24px 48px -12px rgba(16, 24, 40, 0.18);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
position: relative;
|
||||
> .search {
|
||||
z-index: 1;
|
||||
background-color: #fff;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
input {
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
> .list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.contact {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
&.none {
|
||||
padding: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #616161;
|
||||
}
|
||||
.check {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export default function From({ select = "", updateFilter }) {
|
||||
const { input, updateInput, contacts } = useFilteredUsers();
|
||||
// const contacts=useSelector(store=>store.contacts);
|
||||
|
||||
// const uid=contacts.ids;
|
||||
// const dataMap=contacts.byId;
|
||||
const handleClick = (uid) => {
|
||||
updateFilter({ from: uid });
|
||||
};
|
||||
return (
|
||||
<Styled>
|
||||
<div className="search">
|
||||
<Search embed={true} value={input} updateSearchValue={updateInput} />
|
||||
</div>
|
||||
<ul className="list">
|
||||
<li
|
||||
className="contact none"
|
||||
onClick={handleClick.bind(null, undefined)}
|
||||
>
|
||||
Anyone
|
||||
{!select && <CheckSign className="check" />}
|
||||
</li>
|
||||
{contacts.map(({ uid }) => {
|
||||
return (
|
||||
<li
|
||||
key={uid}
|
||||
className="contact"
|
||||
onClick={handleClick.bind(null, uid)}
|
||||
>
|
||||
<Contact uid={uid} interactive={true} />
|
||||
{select == uid && <CheckSign className="check" />}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// import React from 'react'
|
||||
import { useSelector } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import IconPdf from "../../../assets/icons/file.pdf.svg";
|
||||
import IconAudio from "../../../assets/icons/file.audio.svg";
|
||||
import IconVideo from "../../../assets/icons/file.video.svg";
|
||||
import IconUnkown from "../../../assets/icons/file.unkown.svg";
|
||||
import IconDoc from "../../../assets/icons/file.doc.svg";
|
||||
import IconCode from "../../../assets/icons/file.code.svg";
|
||||
import IconImage from "../../../assets/icons/file.image.svg";
|
||||
import CheckSign from "../../../assets/icons/check.sign.svg";
|
||||
|
||||
const Styled = styled.div`
|
||||
padding: 12px;
|
||||
background: #ffffff;
|
||||
min-width: 200px;
|
||||
/* max-height: 230px; */
|
||||
overflow: auto;
|
||||
box-shadow: 0px 24px 48px -12px rgba(16, 24, 40, 0.18);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
position: relative;
|
||||
> .list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
.type {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #616161;
|
||||
.icon {
|
||||
width: 15px;
|
||||
height: auto;
|
||||
}
|
||||
.check {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const FileTypes = {
|
||||
doc: {
|
||||
title: "Documents",
|
||||
icon: <IconDoc className="icon" />,
|
||||
},
|
||||
pdf: {
|
||||
title: "PDFs",
|
||||
icon: <IconPdf className="icon" />,
|
||||
},
|
||||
image: {
|
||||
title: "Images",
|
||||
icon: <IconImage className="icon" />,
|
||||
},
|
||||
audio: {
|
||||
title: "Audio",
|
||||
icon: <IconAudio className="icon" />,
|
||||
},
|
||||
video: {
|
||||
title: "Videos",
|
||||
icon: <IconVideo className="icon" />,
|
||||
},
|
||||
code: {
|
||||
title: "Code Snippets",
|
||||
icon: <IconCode className="icon" />,
|
||||
},
|
||||
unkown: {
|
||||
title: "Unkown Files",
|
||||
icon: <IconUnkown className="icon" />,
|
||||
},
|
||||
};
|
||||
export default function Type({ select = "", updateFilter }) {
|
||||
// const { input, updateInput, contacts } = useFilteredUsers();
|
||||
// const contacts=useSelector(store=>store.contacts);
|
||||
|
||||
// const uid=contacts.ids;
|
||||
// const dataMap=contacts.byId;
|
||||
const handleClick = (uid) => {
|
||||
updateFilter({ type: uid });
|
||||
};
|
||||
return (
|
||||
<Styled>
|
||||
<ul className="list">
|
||||
<li className="type" onClick={handleClick.bind(null, undefined)}>
|
||||
Any Type
|
||||
{!select && <CheckSign className="check" />}
|
||||
</li>
|
||||
{Object.entries(FileTypes).map(([type, { title, icon }]) => {
|
||||
return (
|
||||
<li
|
||||
key={title}
|
||||
className="type"
|
||||
onClick={handleClick.bind(null, type)}
|
||||
>
|
||||
{icon} {title}
|
||||
{select == type && <CheckSign className="check" />}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// import React from "react";
|
||||
import styled from "styled-components";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { FileTypes } from "./Type";
|
||||
import Avatar from "../../../common/component/Avatar";
|
||||
import FilterFrom from "./From";
|
||||
import FilterChannel from "./Channel";
|
||||
import FilterType from "./Type";
|
||||
import ArrowDown from "../../../assets/icons/arrow.down.svg";
|
||||
const Styled = styled.div`
|
||||
/* padding: 20px 0; */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.filter {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: 1px solid #d0d5dd;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
border-radius: var(--br);
|
||||
padding: 7px 12px;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
&.selected {
|
||||
border: none;
|
||||
color: #fff;
|
||||
background-color: #22ccee;
|
||||
.arrow path {
|
||||
stroke: #fff;
|
||||
}
|
||||
}
|
||||
.avatar {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function Filter({ filter, updateFilter }) {
|
||||
const { contactMap, channelMap } = useSelector((store) => {
|
||||
return { contactMap: store.contacts.byId, channelMap: store.channels.byId };
|
||||
});
|
||||
console.log("maps", contactMap, filter);
|
||||
const { from, channel, type } = filter;
|
||||
return (
|
||||
<Styled>
|
||||
<Tippy
|
||||
interactive
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
content={
|
||||
<FilterFrom select={filter.from} updateFilter={updateFilter} />
|
||||
}
|
||||
>
|
||||
<div className={`filter ${from && "selected"}`}>
|
||||
{from && (
|
||||
<Avatar
|
||||
className="avatar"
|
||||
name={contactMap[from].name}
|
||||
url={contactMap[from].avatar}
|
||||
/>
|
||||
)}
|
||||
<span className="txt">From {from && contactMap[from].name}</span>
|
||||
<ArrowDown className="arrow" />
|
||||
</div>
|
||||
</Tippy>
|
||||
<Tippy
|
||||
interactive
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
content={
|
||||
<FilterChannel select={filter.channel} updateFilter={updateFilter} />
|
||||
}
|
||||
>
|
||||
<div className={`filter ${channel && "selected"}`}>
|
||||
<span className="txt">
|
||||
{channel ? `In ${channelMap[channel].name}` : `Channel`}
|
||||
</span>
|
||||
<ArrowDown className="arrow" />
|
||||
</div>
|
||||
</Tippy>
|
||||
<Tippy
|
||||
interactive
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
content={
|
||||
<FilterType select={filter.type} updateFilter={updateFilter} />
|
||||
}
|
||||
>
|
||||
<div className={`filter ${type && "selected"}`}>
|
||||
<span className="txt">{type ? FileTypes[type].title : "Type"}</span>
|
||||
<ArrowDown className="arrow" />
|
||||
</div>
|
||||
</Tippy>
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,44 @@
|
||||
// import React from "react";
|
||||
import styled from "styled-components";
|
||||
import iconSearch from "../../assets/icons/search.svg?url";
|
||||
const Styled = styled.div`
|
||||
width: 100%;
|
||||
padding: 6px 16px;
|
||||
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
|
||||
&.embed {
|
||||
padding: 6px 8px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.search {
|
||||
outline: none;
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
border-radius: 25px;
|
||||
padding: 10px 8px 10px 36px;
|
||||
color: #a1a1aa;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
background-image: url(${iconSearch});
|
||||
background-repeat: no-repeat;
|
||||
background-position: 8px center;
|
||||
}
|
||||
`;
|
||||
export default function Search() {
|
||||
return <Styled>s</Styled>;
|
||||
export default function Search({
|
||||
value = "",
|
||||
updateSearchValue = null,
|
||||
embed = false,
|
||||
}) {
|
||||
const handleChange = (evt) => {
|
||||
updateSearchValue(evt.target.value);
|
||||
};
|
||||
return (
|
||||
<Styled className={embed ? "embed" : ""}>
|
||||
<input
|
||||
value={value}
|
||||
onChange={updateSearchValue ? handleChange : null}
|
||||
className="search"
|
||||
placeholder="Search..."
|
||||
/>
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import Styled from "./styled";
|
||||
import { useSelector } from "react-redux";
|
||||
import Masonry from "masonry-layout";
|
||||
// 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";
|
||||
let msnry = null;
|
||||
export default function ResourceManagement() {
|
||||
const listContainerRef = useRef(null);
|
||||
const [filter, setFilter] = useState({});
|
||||
const [view, setView] = useState(Views.item);
|
||||
const { fileMessages, message } = useSelector((store) => {
|
||||
return { message: store.message, fileMessages: store.fileMessage };
|
||||
@@ -16,13 +19,29 @@ export default function ResourceManagement() {
|
||||
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]);
|
||||
const updateFilter = (data) => {
|
||||
setFilter((prev) => {
|
||||
return { ...prev, ...data };
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (view == Views.grid && listContainerRef) {
|
||||
msnry = new Masonry(listContainerRef.current, {
|
||||
// options
|
||||
itemSelector: ".file_box",
|
||||
// columnWidth: 200
|
||||
});
|
||||
} else {
|
||||
if (msnry) {
|
||||
msnry.destroy();
|
||||
}
|
||||
}
|
||||
// return ()=>{
|
||||
// if(msnry){
|
||||
// msnry.destory()
|
||||
// }
|
||||
// }
|
||||
}, [view]);
|
||||
|
||||
console.log("files", fileMessages);
|
||||
return (
|
||||
@@ -30,7 +49,7 @@ export default function ResourceManagement() {
|
||||
<Search />
|
||||
<div className="divider"></div>
|
||||
<div className="opts">
|
||||
<Filter />
|
||||
<Filter filter={filter} updateFilter={updateFilter} />
|
||||
<View view={view} toggleView={toggleView} />
|
||||
</div>
|
||||
<div className={`list ${view}`} ref={listContainerRef}>
|
||||
|
||||
@@ -5,27 +5,33 @@ const Styled = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 0 16px;
|
||||
|
||||
.opts {
|
||||
padding: 20px 0;
|
||||
padding: 20px 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
.list {
|
||||
> .list {
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
|
||||
display: flex;
|
||||
&.item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
/* align-items: flex-start; */
|
||||
}
|
||||
&.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-rows: auto;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
> .file_box {
|
||||
flex-direction: column;
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user