feat: files with filter

This commit is contained in:
zerosoul
2022-03-30 23:12:46 +08:00
parent 862b16a5d2
commit 95023e8c21
21 changed files with 680 additions and 60 deletions
-18
View File
@@ -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>
);
}
+87
View File
@@ -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>
);
}
+89
View File
@@ -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>
);
}
+111
View File
@@ -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>
);
}
+103
View File
@@ -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>
);
}
+38 -2
View File
@@ -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>
);
}
+27 -8
View File
@@ -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}>
+14 -8
View File
@@ -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;
}
}
}
`;