refactor: add typescript definition and fix some type error
This commit is contained in:
@@ -1,10 +1,17 @@
|
||||
import { useState, useEffect, memo } from "react";
|
||||
import { useState, useEffect, memo, SyntheticEvent, FC } from "react";
|
||||
import { getInitials, getInitialsAvatar } from "../utils";
|
||||
|
||||
const Avatar = ({ url = "", name = "unkonw name", type = "user", ...rest }) => {
|
||||
interface Props {
|
||||
url?: string;
|
||||
name?: string;
|
||||
type?: "user" | "channel";
|
||||
}
|
||||
|
||||
const Avatar: FC<Props> = ({ url = "", name = "unknown name", type = "user", ...rest }) => {
|
||||
// console.log("avatar url", url);
|
||||
const [src, setSrc] = useState("");
|
||||
const handleError = (err) => {
|
||||
|
||||
const handleError = (err: SyntheticEvent<HTMLImageElement>) => {
|
||||
console.log("load avatar error", err);
|
||||
const tmp = getInitialsAvatar({
|
||||
initials: getInitials(name),
|
||||
@@ -13,6 +20,7 @@ const Avatar = ({ url = "", name = "unkonw name", type = "user", ...rest }) => {
|
||||
});
|
||||
setSrc(tmp);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!url) {
|
||||
const tmp = getInitialsAvatar({
|
||||
@@ -26,6 +34,7 @@ const Avatar = ({ url = "", name = "unkonw name", type = "user", ...rest }) => {
|
||||
}
|
||||
}, [url, name]);
|
||||
if (!src) return null;
|
||||
|
||||
return <img src={src} onError={handleError} {...rest} />;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { ChangeEvent, FC, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import Avatar from "./Avatar";
|
||||
import uploadIcon from "../../assets/icons/upload.image.svg?url";
|
||||
@@ -61,21 +61,31 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function AvatarUploader({
|
||||
interface Props {
|
||||
url?: string;
|
||||
name?: string;
|
||||
type?: "user";
|
||||
disabled?: boolean;
|
||||
uploadImage: (file: File) => Promise<any>;
|
||||
}
|
||||
|
||||
const AvatarUploader: FC<Props> = ({
|
||||
url = "",
|
||||
name = "",
|
||||
type = "user",
|
||||
uploadImage,
|
||||
disabled = false
|
||||
}) {
|
||||
}) => {
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const handleUpload = async (evt) => {
|
||||
const handleUpload = async (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
if (uploading) return;
|
||||
const [file] = evt.target.files;
|
||||
if (!evt.target.files) return;
|
||||
const [file] = Array.from(evt.target.files);
|
||||
setUploading(true);
|
||||
await uploadImage(file);
|
||||
setUploading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="avatar">
|
||||
@@ -87,6 +97,7 @@ export default function AvatarUploader({
|
||||
multiple={false}
|
||||
onChange={handleUpload}
|
||||
type="file"
|
||||
// todo: xss
|
||||
accept="image/*"
|
||||
name="avatar"
|
||||
id="avatar"
|
||||
@@ -97,4 +108,6 @@ export default function AvatarUploader({
|
||||
{!disabled && <img src={uploadIcon} alt="icon" className="icon" />}
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default AvatarUploader;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { FC, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import ChannelModal from "./ChannelModal";
|
||||
@@ -67,7 +67,11 @@ const Styled = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function BlankPlaceholder({ type = "chat" }) {
|
||||
interface Props {
|
||||
type?: "chat";
|
||||
}
|
||||
|
||||
const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
const server = useAppSelector((store) => store.server);
|
||||
const [inviteModalVisible, setInviteModalVisible] = useState(false);
|
||||
const [createChannelVisible, setCreateChannelVisible] = useState(false);
|
||||
@@ -83,7 +87,8 @@ export default function BlankPlaceholder({ type = "chat" }) {
|
||||
};
|
||||
const chatTip =
|
||||
type == "chat" ? "Create a Channel to Start a Conversation" : "Send a Direct Message";
|
||||
const chatHanlder = type == "chat" ? toggleChannelModalVisible : toggleContactListVisible;
|
||||
const chatHandler = type == "chat" ? toggleChannelModalVisible : toggleContactListVisible;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Styled>
|
||||
@@ -99,7 +104,7 @@ export default function BlankPlaceholder({ type = "chat" }) {
|
||||
<IconInvite className="icon" />
|
||||
<div className="txt">Invite your friends or teammates</div>
|
||||
</div>
|
||||
<div className="box" onClick={chatHanlder}>
|
||||
<div className="box" onClick={chatHandler}>
|
||||
<IconChat className="icon" />
|
||||
<div className="txt">{chatTip}</div>
|
||||
</div>
|
||||
@@ -109,7 +114,7 @@ export default function BlankPlaceholder({ type = "chat" }) {
|
||||
</a>
|
||||
<NavLink to={"#"} className="box">
|
||||
<IconAsk className="icon" />
|
||||
<div className="txt">Got quesions? Visit our help center </div>
|
||||
<div className="txt">Got questions? Visit our help center </div>
|
||||
</NavLink>
|
||||
</div>
|
||||
</Styled>
|
||||
@@ -120,4 +125,6 @@ export default function BlankPlaceholder({ type = "chat" }) {
|
||||
{inviteModalVisible && <InviteModal closeModal={toggleInviteModalVisible} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default BlankPlaceholder;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC, MouseEvent } from "react";
|
||||
import StyledMenu from "./styled/Menu";
|
||||
|
||||
interface Item {
|
||||
export interface Item {
|
||||
title: string;
|
||||
icon: string;
|
||||
handler: (e: MouseEvent) => void;
|
||||
|
||||
@@ -11,8 +11,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const DeleteMessageConfirmModal: FC<Props> = ({ closeModal, mids = [] }) => {
|
||||
// const dispatch = useDispatch();
|
||||
const mid_arr = mids ? (Array.isArray(mids) ? mids : [mids]) : [];
|
||||
const mid_arr: number[] = mids ? (Array.isArray(mids) ? mids : [mids]) : [];
|
||||
const [ids] = useState(mid_arr);
|
||||
const { deleteMessage, isDeleting } = useDeleteMessage();
|
||||
const handleDelete = async () => {
|
||||
@@ -40,7 +39,7 @@ const DeleteMessageConfirmModal: FC<Props> = ({ closeModal, mids = [] }) => {
|
||||
ids.length > 1 ? "these messages" : "this message"
|
||||
}?`}
|
||||
>
|
||||
{ids.length == 1 && <PreviewMessage mid={ids[0]} />}
|
||||
{ids.length === 1 && <PreviewMessage mid={ids[0]} />}
|
||||
</StyledModal>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,6 @@ const Styled = styled.div`
|
||||
|
||||
export default function FAQ() {
|
||||
const { data: serverVersion } = useGetServerVersionQuery();
|
||||
console.log("build time", serverVersion);
|
||||
return (
|
||||
<Styled>
|
||||
<div className="item">Client Version: {process.env.VERSION}</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// import React from 'react'
|
||||
import { FC, ReactElement } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import { useSelector } from "react-redux";
|
||||
import Styled from "./styled";
|
||||
import {
|
||||
VideoPreview,
|
||||
@@ -13,13 +12,20 @@ import {
|
||||
} from "./preview";
|
||||
import { getFileIcon, formatBytes } from "../../utils";
|
||||
import IconDownload from "../../../assets/icons/download.svg";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
|
||||
// todo
|
||||
// todo: move to root file
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const renderPreview = (data) => {
|
||||
interface Data {
|
||||
file_type: string;
|
||||
name: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const renderPreview = (data: Data) => {
|
||||
const { file_type, name, content } = data;
|
||||
let preview = null;
|
||||
let preview: null | ReactElement = null;
|
||||
|
||||
const checks = {
|
||||
image: /^image/gi,
|
||||
@@ -60,20 +66,31 @@ const renderPreview = (data) => {
|
||||
}
|
||||
return preview;
|
||||
};
|
||||
export default function FileBox({
|
||||
preview = false,
|
||||
|
||||
interface Props {
|
||||
preview?: boolean;
|
||||
flex: boolean;
|
||||
file_type: string;
|
||||
name: string;
|
||||
size: number;
|
||||
created_at: number;
|
||||
from_uid: number;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const FileBox: FC<Props> = ({
|
||||
preview,
|
||||
flex,
|
||||
file_type = "",
|
||||
file_type,
|
||||
name,
|
||||
size,
|
||||
created_at,
|
||||
from_uid,
|
||||
content
|
||||
}) {
|
||||
const fromUser = useSelector((store) => store.contacts.byId[from_uid]);
|
||||
}) => {
|
||||
const fromUser = useAppSelector((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);
|
||||
const previewContent = renderPreview({ file_type, content, name });
|
||||
const withPreview = preview && previewContent;
|
||||
return (
|
||||
@@ -101,4 +118,6 @@ export default function FileBox({
|
||||
{withPreview && <div className="preview">{previewContent}</div>}
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default FileBox;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReactEventHandler, useState } from "react";
|
||||
import { FC, ReactEventHandler, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Styled = styled.div`
|
||||
@@ -17,7 +17,11 @@ const Styled = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function Audio({ url = "" }) {
|
||||
interface Props {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const Audio: FC<Props> = ({ url }) => {
|
||||
const [err, setErr] = useState(false);
|
||||
|
||||
const handleError: ReactEventHandler<HTMLAudioElement> = (err) => {
|
||||
@@ -35,4 +39,6 @@ export default function Audio({ url = "" }) {
|
||||
)}
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Audio;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, FC } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Styled = styled.div`
|
||||
@@ -12,7 +12,11 @@ const Styled = styled.div`
|
||||
color: #eee;
|
||||
`;
|
||||
|
||||
export default function Code({ url = "" }) {
|
||||
interface Props {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const Code: FC<Props> = ({ url }) => {
|
||||
const [content, setContent] = useState("");
|
||||
useEffect(() => {
|
||||
const getContent = async (url: string) => {
|
||||
@@ -26,4 +30,6 @@ export default function Code({ url = "" }) {
|
||||
if (!content) return null;
|
||||
|
||||
return <Styled>{content}</Styled>;
|
||||
}
|
||||
};
|
||||
|
||||
export default Code;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, FC } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Styled = styled.div`
|
||||
@@ -11,7 +11,11 @@ const Styled = styled.div`
|
||||
word-break: break-all;
|
||||
`;
|
||||
|
||||
export default function Doc({ url = "" }) {
|
||||
interface Props {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const Doc: FC<Props> = ({ url }) => {
|
||||
const [content, setContent] = useState("");
|
||||
useEffect(() => {
|
||||
const getContent = async (url: string) => {
|
||||
@@ -25,4 +29,6 @@ export default function Doc({ url = "" }) {
|
||||
if (!content) return null;
|
||||
|
||||
return <Styled>{content}</Styled>;
|
||||
}
|
||||
};
|
||||
|
||||
export default Doc;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Styled = styled.div`
|
||||
@@ -11,11 +11,18 @@ const Styled = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function Image({ url = "" }) {
|
||||
interface Props {
|
||||
url: string;
|
||||
alt?: string;
|
||||
}
|
||||
|
||||
const Image: FC<Props> = ({ url, alt }) => {
|
||||
if (!url) return null;
|
||||
return (
|
||||
<Styled>
|
||||
<img src={url} />
|
||||
<img src={url} alt={alt} />
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Image;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import styled from "styled-components";
|
||||
import { FC } from "react";
|
||||
|
||||
const Styled = styled.div`
|
||||
padding: 8px;
|
||||
@@ -10,14 +11,17 @@ const Styled = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function Pdf({ url = "" }) {
|
||||
interface Props {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const Pdf: FC<Props> = ({ url }) => {
|
||||
// const [content, setContent] = useState("");
|
||||
// const [pageNumber, setPageNumber] = useState(1);
|
||||
// const [numPages, setNumPages] = useState(null);
|
||||
// const onDocumentLoadSuccess = ({ numPages }) => {
|
||||
// setNumPages(numPages);
|
||||
// };
|
||||
if (!url) return null;
|
||||
return (
|
||||
<Styled>
|
||||
<embed src={url} type="application/pdf" />
|
||||
@@ -26,4 +30,6 @@ export default function Pdf({ url = "" }) {
|
||||
</Document> */}
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Pdf;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Styled = styled.div`
|
||||
@@ -10,11 +10,16 @@ const Styled = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function Video({ url = "" }) {
|
||||
if (!url) return null;
|
||||
interface Props {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const Video: FC<Props> = ({ url }) => {
|
||||
return (
|
||||
<Styled>
|
||||
<video controls src={url} />
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Video;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import { CircularProgressbar, buildStyles } from "react-circular-progressbar";
|
||||
import "react-circular-progressbar/dist/styles.css";
|
||||
@@ -33,17 +33,25 @@ const Styled = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function ImageMessage({
|
||||
interface Props {
|
||||
uploading: boolean;
|
||||
progress: number;
|
||||
thumbnail: string;
|
||||
download: string;
|
||||
content: string;
|
||||
properties: { width: number; height: number };
|
||||
}
|
||||
|
||||
const ImageMessage: FC<Props> = ({
|
||||
uploading,
|
||||
progress,
|
||||
thumbnail,
|
||||
download,
|
||||
content,
|
||||
properties = {}
|
||||
}) {
|
||||
properties
|
||||
}) => {
|
||||
const [url, setUrl] = useState(thumbnail);
|
||||
const { width = 0, height = 0 } = getDefaultSize(properties);
|
||||
console.log("image props", properties, width, height);
|
||||
useEffect(() => {
|
||||
const newUrl = thumbnail;
|
||||
const img = new Image();
|
||||
@@ -64,10 +72,7 @@ export default function ImageMessage({
|
||||
<CircularProgressbar
|
||||
value={progress}
|
||||
strokeWidth={50}
|
||||
styles={buildStyles({
|
||||
storke: "#000",
|
||||
strokeLinecap: "butt"
|
||||
})}
|
||||
styles={buildStyles({ strokeLinecap: "butt" })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,4 +90,6 @@ export default function ImageMessage({
|
||||
/>
|
||||
</Styled>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ImageMessage;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// import { useState, useEffect } from "react";
|
||||
// import { useGoogleLogin } from "react-google-login";
|
||||
import { FC } from "react";
|
||||
import IconGithub from "../../assets/icons/github.svg";
|
||||
import styled from "styled-components";
|
||||
import Button from "./styled/Button";
|
||||
@@ -14,16 +13,23 @@ const StyledSocialButton = styled(Button)`
|
||||
color: #344054;
|
||||
border: 1px solid #d0d5dd;
|
||||
background: none !important;
|
||||
|
||||
.icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function GithubLoginButton({ config = {} }) {
|
||||
interface Props {
|
||||
config: {
|
||||
client_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
const GithubLoginButton: FC<Props> = ({ config }) => {
|
||||
const { client_id } = config;
|
||||
const handleGithubLogin = () => {
|
||||
location.href = `http://github.com/login/oauth/authorize?client_id=${client_id}`;
|
||||
location.href = `https://github.com/login/oauth/authorize?client_id=${client_id}`;
|
||||
// console.log("github login");
|
||||
};
|
||||
// console.log("google login ", loaded);
|
||||
@@ -31,7 +37,9 @@ export default function GithubLoginButton({ config = {} }) {
|
||||
<StyledSocialButton onClick={handleGithubLogin}>
|
||||
<IconGithub className="icon" />
|
||||
Sign in with Github
|
||||
{/* {loaded ? `Sign in with Github` : `Initailizing`} */}
|
||||
{/* {loaded ? `Sign in with Github` : `Initializing`} */}
|
||||
</StyledSocialButton>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default GithubLoginButton;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
import { useRef, useState, useEffect, FC } from "react";
|
||||
import styled, { keyframes } from "styled-components";
|
||||
import { useOutsideClick, useKey } from "rooks";
|
||||
import { Ring } from "@uiball/loaders";
|
||||
import Modal from "./Modal";
|
||||
|
||||
const AniFadeIn = keyframes`
|
||||
from{
|
||||
background: transparent;
|
||||
}
|
||||
to{
|
||||
background: rgba(1, 1, 1, 0.9);
|
||||
}
|
||||
from {
|
||||
background: transparent;
|
||||
}
|
||||
to {
|
||||
background: rgba(1, 1, 1, 0.9);
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
@@ -23,6 +23,7 @@ const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.box {
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
@@ -30,14 +31,17 @@ const StyledWrapper = styled.div`
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
> .loading {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
> .image {
|
||||
overflow: auto;
|
||||
|
||||
img {
|
||||
max-width: 70vw;
|
||||
max-height: 80vh;
|
||||
@@ -46,13 +50,16 @@ const StyledWrapper = styled.div`
|
||||
object-fit: contain; */
|
||||
}
|
||||
}
|
||||
|
||||
&.loading .image img {
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.origin {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
color: #aaa;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: #fff;
|
||||
@@ -61,11 +68,26 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function ImagePreviewModal({ download = true, data, closeModal }) {
|
||||
export interface PreviewImageData {
|
||||
originUrl: string;
|
||||
thumbnail: string;
|
||||
downloadLink: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
download?: boolean;
|
||||
data?: PreviewImageData;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const ImagePreviewModal: FC<Props> = ({ download = true, data, closeModal }) => {
|
||||
const [url, setUrl] = useState(data?.thumbnail);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const wrapperRef = useRef();
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
useOutsideClick(wrapperRef, closeModal);
|
||||
|
||||
useKey(
|
||||
"Escape",
|
||||
() => {
|
||||
@@ -74,6 +96,7 @@ export default function ImagePreviewModal({ download = true, data, closeModal })
|
||||
},
|
||||
{ eventTypes: ["keyup"] }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const { originUrl } = data;
|
||||
@@ -126,4 +149,6 @@ export default function ImagePreviewModal({ download = true, data, closeModal })
|
||||
</StyledWrapper>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ImagePreviewModal;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// import { useState, useEffect } from "react";
|
||||
import { FC } from "react";
|
||||
import styled, { keyframes } from "styled-components";
|
||||
import { Ring } from "@uiball/loaders";
|
||||
import Button from "./styled/Button";
|
||||
import useLogout from "../hook/useLogout";
|
||||
|
||||
const DelayVisible = keyframes`
|
||||
from{
|
||||
opacity: 0;
|
||||
}
|
||||
to{
|
||||
opacity: 1;
|
||||
}
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
@@ -21,17 +21,15 @@ const StyledWrapper = styled.div`
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.fullscreen {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.reload {
|
||||
opacity: 0;
|
||||
|
||||
&.visible {
|
||||
animation: ${DelayVisible} 1s forwards;
|
||||
animation-delay: 30s;
|
||||
@@ -39,20 +37,26 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function Loading({ reload = false, fullscreen = false }) {
|
||||
interface Props {
|
||||
reload?: boolean;
|
||||
fullscreen?: boolean;
|
||||
}
|
||||
|
||||
const Loading: FC<Props> = ({ reload = false, fullscreen = false }) => {
|
||||
const { clearLocalData } = useLogout();
|
||||
const handleReload = () => {
|
||||
clearLocalData();
|
||||
// todo: only firefox has argument
|
||||
location.reload(true);
|
||||
location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className={fullscreen ? "fullscreen" : ""}>
|
||||
<Ring className="loading" size={40} lineWeight={5} speed={2} color="black" />
|
||||
<Ring size={40} lineWeight={5} speed={2} color="black" />
|
||||
<Button className={`reload danger ${reload ? "visible" : ""}`} onClick={handleReload}>
|
||||
Reload
|
||||
</Button>
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Loading;
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { FC, ReactNode, useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
export default function Modal({ id = "root-modal", mask = true, children }) {
|
||||
const [wrapper, setWrapper] = useState(null);
|
||||
// let eleRef = useRef(null);
|
||||
interface Props {
|
||||
id?: string;
|
||||
mask?: boolean;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
// todo: check memory leak
|
||||
const Modal: FC<Props> = ({ id = "root-modal", mask = true, children }) => {
|
||||
const [wrapper, setWrapper] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const modalRoot = document.getElementById(id);
|
||||
if (!modalRoot) return;
|
||||
if (mask) {
|
||||
modalRoot.classList.add("mask");
|
||||
}
|
||||
@@ -17,7 +25,9 @@ export default function Modal({ id = "root-modal", mask = true, children }) {
|
||||
modalRoot.removeChild(wrapper);
|
||||
};
|
||||
}, [id, mask]);
|
||||
|
||||
if (!wrapper) return null;
|
||||
console.log("create portal");
|
||||
return createPortal(children, wrapper);
|
||||
}
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FC, ReactElement } from "react";
|
||||
import EmojiThumbUp from "../../assets/icons/emoji.thumb.up.svg";
|
||||
import EmojiThumbDown from "../../assets/icons/emoji.thumb.down.svg";
|
||||
import EmojiSmile from "../../assets/icons/emoji.smile.svg";
|
||||
@@ -7,7 +8,18 @@ import EmojiHeart from "../../assets/icons/emoji.heart.svg";
|
||||
import EmojiRocket from "../../assets/icons/emoji.rocket.svg";
|
||||
import EmojiLook from "../../assets/icons/emoji.look.svg";
|
||||
|
||||
const Emojis = {
|
||||
interface Emojis {
|
||||
"👍": ReactElement;
|
||||
"👎": ReactElement;
|
||||
"😄": ReactElement;
|
||||
"👀": ReactElement;
|
||||
"🚀": ReactElement;
|
||||
"❤️": ReactElement;
|
||||
"🙁": ReactElement;
|
||||
"🎉": ReactElement;
|
||||
}
|
||||
|
||||
const emojis: Emojis = {
|
||||
"👍": <EmojiThumbUp className="emoji" />,
|
||||
"👎": <EmojiThumbDown className="emoji" />,
|
||||
"😄": <EmojiSmile className="emoji" />,
|
||||
@@ -18,8 +30,13 @@ const Emojis = {
|
||||
"🎉": <EmojiCelebrate className="emoji" />
|
||||
};
|
||||
|
||||
export default function ReactionItem({ native = "" }) {
|
||||
if (!native || !Emojis[native]) return null;
|
||||
|
||||
return Emojis[native];
|
||||
interface Props {
|
||||
native?: keyof Emojis;
|
||||
}
|
||||
|
||||
const ReactionItem: FC<Props> = ({ native }) => {
|
||||
if (!native) return null;
|
||||
return emojis[native] ?? null;
|
||||
};
|
||||
|
||||
export default ReactionItem;
|
||||
|
||||
@@ -14,7 +14,7 @@ const StyledWrapper = styled.div`
|
||||
background: #fff;
|
||||
/* gap: 20px; */
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
|
||||
box-shadow: 0 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
|
||||
border-radius: 25px;
|
||||
.txt {
|
||||
padding: 8px;
|
||||
@@ -37,7 +37,7 @@ const StyledWrapper = styled.div`
|
||||
background: #1fe1f9;
|
||||
border: 1px solid #1fe1f9;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
|
||||
border-radius: 25px;
|
||||
&.reset {
|
||||
background: none;
|
||||
|
||||
@@ -34,11 +34,10 @@ const StyledWrapper = styled.div`
|
||||
`;
|
||||
|
||||
export default function Search() {
|
||||
console.log("searching");
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="search">
|
||||
<img src={searchIcon} />
|
||||
<img src={searchIcon} alt="search icon" />
|
||||
<input placeholder="Search..." className="input" />
|
||||
</div>
|
||||
<Tooltip tip="More" placement="bottom">
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function Server() {
|
||||
<NavLink to={`/setting?f=${pathname}`}>
|
||||
<div className="server">
|
||||
<div className="logo">
|
||||
<img src={logo} />
|
||||
<img alt="logo" src={logo} />
|
||||
</div>
|
||||
<div className="info">
|
||||
<h3 className="name" title={description}>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { FC, ReactElement } from "react";
|
||||
import styled, { createGlobalStyle } from "styled-components";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { roundArrow } from "tippy.js";
|
||||
import { Placement, roundArrow } from "tippy.js";
|
||||
import "tippy.js/dist/svg-arrow.css";
|
||||
import { updateUserGuide } from "../../../app/slices/ui";
|
||||
import steps from "./steps";
|
||||
import { useAppDispatch, useAppSelector } from "../../../app/store";
|
||||
import { useAppDispatch } from "../../../app/store";
|
||||
|
||||
const Styled = styled.div`
|
||||
display: flex;
|
||||
@@ -57,15 +58,21 @@ const OverrideTippyArrowColor = createGlobalStyle`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function UserGuide({ step = 1, placement = "right-start", delay = null, children }) {
|
||||
interface Props {
|
||||
step?: number;
|
||||
placement: Placement;
|
||||
delay?: number | [number | null, number | null];
|
||||
children: ReactElement;
|
||||
}
|
||||
|
||||
const UserGuide: FC<Props> = ({ step = 1, placement = "right-start", delay, children }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { visible, step: reduxStep } = useAppSelector((store) => store.ui.userGuide);
|
||||
const currStep = steps[step - 1];
|
||||
const isLastStep = steps.length == step;
|
||||
// if (!visible) return children;
|
||||
if (!currStep) return null;
|
||||
const { title, description } = currStep;
|
||||
const defaultDuration = [300, 250];
|
||||
const defaultDuration: [number, number] = [300, 250];
|
||||
const handleSkip = () => {
|
||||
// to do
|
||||
dispatch(updateUserGuide({ visible: false }));
|
||||
@@ -104,4 +111,6 @@ export default function UserGuide({ step = 1, placement = "right-start", delay =
|
||||
</Tippy>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default UserGuide;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, ReactElement } from "react";
|
||||
import { FC, ReactNode } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Styled = styled.div`
|
||||
@@ -43,18 +43,12 @@ const Styled = styled.div`
|
||||
interface Props {
|
||||
title?: string;
|
||||
description?: string;
|
||||
buttons: ReactElement[] | ReactElement;
|
||||
children?: ReactElement;
|
||||
buttons?: ReactNode;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const StyledModal: FC<Props> = ({
|
||||
title = "",
|
||||
description = "",
|
||||
buttons = null,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const StyledModal: FC<Props> = ({ title = "", description = "", buttons, children, ...props }) => {
|
||||
return (
|
||||
<Styled {...props}>
|
||||
{title && <h3 className="title">{title}</h3>}
|
||||
|
||||
Reference in New Issue
Block a user