feat: use dialog instead of page

pull/42/head
email 3 years ago
parent 2b5ee78397
commit da1ccabd1e

@ -0,0 +1,79 @@
import { useCallback, useEffect, useState } from "react";
import useLoading from "../hooks/useLoading";
import { locationService, memoService } from "../services";
import { showDialog } from "./Dialog";
import toastHelper from "./Toast";
import DeletedMemo from "./DeletedMemo";
import "../less/memo-trash-dialog.less";
interface Props extends DialogProps {}
const MemoTrashDialog: React.FC<Props> = (props: Props) => {
const { destroy } = props;
const loadingState = useLoading();
const [deletedMemos, setDeletedMemos] = useState<Model.Memo[]>([]);
useEffect(() => {
memoService.fetchAllMemos();
memoService
.fetchDeletedMemos()
.then((result) => {
if (result !== false) {
setDeletedMemos(result);
}
})
.catch((error) => {
toastHelper.error("Failed to fetch deleted memos: ", error);
})
.finally(() => {
loadingState.setFinish();
});
locationService.clearQuery();
}, []);
const handleDeletedMemoAction = useCallback((memoId: string) => {
setDeletedMemos((deletedMemos) => deletedMemos.filter((memo) => memo.id !== memoId));
}, []);
return (
<>
<div className="dialog-header-container">
<p className="title-text">
<span className="icon-text">🗑</span>
Trash Bin
</p>
<button className="btn close-btn" onClick={destroy}>
<img className="icon-img" src="/icons/close.svg" />
</button>
</div>
<div className="dialog-content-container">
{loadingState.isLoading ? (
<div className="tip-text-container">
<p className="tip-text">fetching data...</p>
</div>
) : deletedMemos.length === 0 ? (
<div className="tip-text-container">
<p className="tip-text">Here is No Zettels.</p>
</div>
) : (
<div className="deleted-memos-container">
{deletedMemos.map((memo) => (
<DeletedMemo key={`${memo.id}-${memo.updatedAt}`} memo={memo} handleDeletedMemoAction={handleDeletedMemoAction} />
))}
</div>
)}
</div>
</>
);
};
export default function showMemoTrashDialog(): void {
showDialog(
{
className: "memo-trash-dialog",
useAppContext: true,
},
MemoTrashDialog,
{}
);
}

@ -1,6 +1,8 @@
import { useEffect, useRef } from "react";
import { locationService, userService } from "../services";
import showAboutSiteDialog from "./AboutSiteDialog";
import showSettingDialog from "./SettingDialog";
import showMemoTrashDialog from "./MemoTrashDialog";
import "../less/menu-btns-popup.less";
interface Props {
@ -29,11 +31,11 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
}, [shownStatus]);
const handleMyAccountBtnClick = () => {
locationService.pushHistory("/setting");
showSettingDialog();
};
const handleMemosTrashBtnClick = () => {
locationService.pushHistory("/trash");
showMemoTrashDialog();
};
const handleAboutBtnClick = () => {
@ -49,7 +51,7 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
return (
<div className={`menu-btns-popup ${shownStatus ? "" : "hidden"}`} ref={popupElRef}>
<button className="btn action-btn" onClick={handleMyAccountBtnClick}>
<span className="icon">👤</span> Settings
<span className="icon">👤</span> Setting
</button>
<button className="btn action-btn" onClick={handleMemosTrashBtnClick}>
<span className="icon">🗑</span> Recycle Bin

@ -0,0 +1,45 @@
import { useEffect } from "react";
import { memoService } from "../services";
import { showDialog } from "./Dialog";
import MyAccountSection from "./MyAccountSection";
import PreferencesSection from "./PreferencesSection";
import "../less/setting-dialog.less";
interface Props extends DialogProps {}
const SettingDialog: React.FC<Props> = (props: Props) => {
const { destroy } = props;
useEffect(() => {
memoService.fetchAllMemos();
}, []);
return (
<>
<div className="dialog-header-container">
<p className="title-text">
<span className="icon-text">👤</span>
Setting
</p>
<button className="btn close-btn" onClick={destroy}>
<img className="icon-img" src="/icons/close.svg" />
</button>
</div>
<div className="dialog-content-container">
<MyAccountSection />
<PreferencesSection />
</div>
</>
);
};
export default function showSettingDialog(): void {
showDialog(
{
className: "setting-dialog",
useAppContext: true,
},
SettingDialog,
{}
);
}

@ -67,14 +67,11 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
const { shortcut, isActive } = props;
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
console.log(props);
const handleShortcutClick = () => {
console.log("here");
if (isActive) {
locationService.setMemoShortcut("");
} else {
if (!["/", "/trash"].includes(locationService.getState().pathname)) {
if (!["/"].includes(locationService.getState().pathname)) {
locationService.setPathname("/");
}
locationService.setMemoShortcut(shortcut.id);

@ -101,7 +101,7 @@ const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContain
locationService.setTagQuery("");
} else {
utils.copyTextToClipboard(`#${tag.text} `);
if (!["/", "/trash"].includes(locationService.getState().pathname)) {
if (!["/"].includes(locationService.getState().pathname)) {
locationService.setPathname("/");
}
locationService.setTagQuery(tag.text);

@ -76,7 +76,7 @@ const UsageHeatMap: React.FC<Props> = () => {
locationService.setFromAndToQuery(0, 0);
setCurrentStat(null);
} else if (item.count > 0) {
if (!["/", "/trash"].includes(locationService.getState().pathname)) {
if (!["/"].includes(locationService.getState().pathname)) {
locationService.setPathname("/");
}
locationService.setFromAndToQuery(item.timestamp, item.timestamp + DAILY_TIMESTAMP);

@ -0,0 +1,23 @@
@import "./mixin.less";
@import "./memos-header.less";
.memo-trash-dialog {
> .dialog-container {
@apply w-128 max-w-full mb-8;
> .dialog-content-container {
.flex(column, flex-start, flex-start);
@apply w-full overflow-y-scroll;
> .tip-text-container {
@apply w-full h-32;
.flex(column, center, center);
}
> .deleted-memos-container {
.flex(column, flex-start, flex-start);
@apply w-full;
}
}
}
}

@ -1,39 +0,0 @@
@import "./mixin.less";
@import "./memos-header.less";
.memo-trash-wrapper {
@apply px-8;
.flex(column, flex-start, flex-start);
width: 100%;
height: 100%;
flex-grow: 1;
overflow-y: scroll;
.hide-scroll-bar();
> .section-header-container {
width: 100%;
height: 40px;
margin-bottom: 0;
> .title-text {
font-weight: bold;
font-size: 18px;
color: @text-black;
}
}
> .tip-text-container {
width: 100%;
height: 128px;
.flex(column, center, center);
}
> .deleted-memos-container {
.flex(column, flex-start, flex-start);
flex-grow: 1;
width: 100%;
overflow-y: scroll;
padding-bottom: 64px;
.hide-scroll-bar();
}
}

@ -0,0 +1,33 @@
@import "./mixin.less";
@import "./memos-header.less";
.setting-dialog {
> .dialog-container {
@apply w-3/5 max-w-full mb-8;
> .dialog-content-container {
.flex(column, flex-start, flex-start);
@apply w-full overflow-y-scroll;
.hide-scroll-bar();
> .section-container {
.flex(column, flex-start, flex-start);
@apply w-full my-2;
> .title-text {
@apply text-base font-bold mb-2;
color: @text-black;
}
> .form-label {
.flex(row, flex-start, center);
@apply w-full text-sm mb-2;
> .normal-text {
@apply shrink-0;
}
}
}
}
}
}

@ -1,47 +0,0 @@
@import "./mixin.less";
@import "./memos-header.less";
.preference-wrapper {
.flex(column, flex-start, flex-start);
@apply w-full h-full grow overflow-y-scroll px-8;
.hide-scroll-bar();
> .section-header-container {
@apply w-full h-10 mb-0;
> .title-text {
@apply font-bold text-lg;
color: @text-black;
}
}
> .tip-text-container {
.flex(column, center, center);
@apply w-full h-32;
}
> .sections-wrapper {
.flex(column, flex-start, flex-start);
@apply grow w-full overflow-y-scroll pb-16;
.hide-scroll-bar();
> .section-container {
.flex(column, flex-start, flex-start);
@apply w-full bg-white my-2 mx-0 p-4 pb-2 rounded-lg;
> .title-text {
@apply text-base font-bold mb-2;
color: @text-black;
}
> .form-label {
.flex(row, flex-start, center);
@apply w-full text-sm mb-2;
> .normal-text {
@apply shrink-0;
}
}
}
}
}

@ -1,30 +0,0 @@
import { useEffect } from "react";
import { memoService } from "../services";
import MyAccountSection from "../components/MyAccountSection";
import PreferencesSection from "../components/PreferencesSection";
import "../less/setting.less";
interface Props {}
const Setting: React.FC<Props> = () => {
useEffect(() => {
memoService.fetchAllMemos();
}, []);
return (
<div className="preference-wrapper">
<div className="section-header-container">
<div className="title-text">
<span className="normal-text">Settings</span>
</div>
</div>
<div className="sections-wrapper">
<MyAccountSection />
<PreferencesSection />
</div>
</div>
);
};
export default Setting;

@ -1,129 +0,0 @@
import { useCallback, useContext, useEffect, useState } from "react";
import appContext from "../stores/appContext";
import useLoading from "../hooks/useLoading";
import { locationService, memoService, shortcutService } from "../services";
import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts";
import utils from "../helpers/utils";
import { checkShouldShowMemoWithFilters } from "../helpers/filter";
import toastHelper from "../components/Toast";
import DeletedMemo from "../components/DeletedMemo";
import MemoFilter from "../components/MemoFilter";
import "../less/memo-trash.less";
interface Props {}
const Trash: React.FC<Props> = () => {
const {
locationState: { query },
} = useContext(appContext);
const loadingState = useLoading();
const [deletedMemos, setDeletedMemos] = useState<Model.Memo[]>([]);
const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query;
const queryFilter = shortcutService.getShortcutById(shortcutId);
const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || queryFilter);
const shownMemos =
showMemoFilter || queryFilter
? deletedMemos.filter((memo) => {
let shouldShow = true;
if (queryFilter) {
const filters = JSON.parse(queryFilter.payload) as Filter[];
if (Array.isArray(filters)) {
shouldShow = checkShouldShowMemoWithFilters(memo, filters);
}
}
if (tagQuery) {
const tagsSet = new Set<string>();
for (const t of Array.from(memo.content.match(TAG_REG) ?? [])) {
const tag = t.replace(TAG_REG, "$1").trim();
const items = tag.split("/");
let temp = "";
for (const i of items) {
temp += i;
tagsSet.add(temp);
temp += "/";
}
}
if (!tagsSet.has(tagQuery)) {
shouldShow = false;
}
}
if (
duration &&
duration.from < duration.to &&
(utils.getTimeStampByDate(memo.createdAt) < duration.from || utils.getTimeStampByDate(memo.createdAt) > duration.to)
) {
shouldShow = false;
}
if (memoType) {
if (memoType === "NOT_TAGGED" && memo.content.match(TAG_REG) !== null) {
shouldShow = false;
} else if (memoType === "LINKED" && memo.content.match(LINK_REG) === null) {
shouldShow = false;
} else if (memoType === "IMAGED" && memo.content.match(IMAGE_URL_REG) === null) {
shouldShow = false;
} else if (memoType === "CONNECTED" && memo.content.match(MEMO_LINK_REG) === null) {
shouldShow = false;
}
}
if (textQuery && !memo.content.includes(textQuery)) {
shouldShow = false;
}
return shouldShow;
})
: deletedMemos;
useEffect(() => {
memoService.fetchAllMemos();
memoService
.fetchDeletedMemos()
.then((result) => {
if (result !== false) {
setDeletedMemos(result);
}
})
.catch((error) => {
toastHelper.error("Failed to fetch deleted memos: ", error);
})
.finally(() => {
loadingState.setFinish();
});
locationService.clearQuery();
}, []);
const handleDeletedMemoAction = useCallback((memoId: string) => {
setDeletedMemos((deletedMemos) => deletedMemos.filter((memo) => memo.id !== memoId));
}, []);
return (
<div className="memo-trash-wrapper">
<div className="section-header-container">
<div className="title-text">
<span className="normal-text">Recycle Bin</span>
</div>
</div>
<MemoFilter />
{loadingState.isLoading ? (
<div className="tip-text-container">
<p className="tip-text">fetching data...</p>
</div>
) : deletedMemos.length === 0 ? (
<div className="tip-text-container">
<p className="tip-text">Here is No Zettels.</p>
</div>
) : (
<div className="deleted-memos-container">
{shownMemos.map((memo) => (
<DeletedMemo key={`${memo.id}-${memo.updatedAt}`} memo={memo} handleDeletedMemoAction={handleDeletedMemoAction} />
))}
</div>
)}
</div>
);
};
export default Trash;

@ -1,10 +1,6 @@
import Memos from "../pages/Memos";
import Trash from "../pages/Trash";
import Setting from "../pages/Setting";
const homeRouter = {
"/trash": <Trash />,
"/setting": <Setting />,
"*": <Memos />,
};

@ -185,7 +185,7 @@ class LocationService {
};
public getValidPathname = (pathname: string): AppRouter => {
if (["/", "/signin", "/trash", "/setting"].includes(pathname)) {
if (["/", "/signin"].includes(pathname)) {
return pathname as AppRouter;
} else {
return "/";

@ -11,7 +11,7 @@ interface Query {
shortcutId: string;
}
type AppRouter = "/" | "/signin" | "/trash" | "/setting";
type AppRouter = "/" | "/signin";
interface AppLocation {
pathname: AppRouter;

Loading…
Cancel
Save