mirror of
https://github.com/usememos/memos.git
synced 2024-12-21 10:11:42 +03:00
chore: add CommonDialog
This commit is contained in:
parent
5617118fa8
commit
2b8078a19b
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import * as api from "../helpers/api";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import { showDialog } from "./Dialog";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import GitHubBadge from "./GitHubBadge";
|
||||
import "../less/about-site-dialog.less";
|
||||
|
||||
@ -63,7 +63,7 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
};
|
||||
|
||||
export default function showAboutSiteDialog(): void {
|
||||
showDialog(
|
||||
generateDialog(
|
||||
{
|
||||
className: "about-site-dialog",
|
||||
},
|
||||
|
@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { memoService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import { showDialog } from "./Dialog";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
import ArchivedMemo from "./ArchivedMemo";
|
||||
import "../less/archived-memo-dialog.less";
|
||||
@ -62,7 +62,7 @@ const ArchivedMemoDialog: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
export default function showArchivedMemo(): void {
|
||||
showDialog(
|
||||
generateDialog(
|
||||
{
|
||||
className: "archived-memo-dialog",
|
||||
useAppContext: true,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { validate, ValidatorConfig } from "../helpers/validator";
|
||||
import { userService } from "../services";
|
||||
import { showDialog } from "./Dialog";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
import "../less/change-password-dialog.less";
|
||||
|
||||
@ -96,7 +96,7 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
};
|
||||
|
||||
function showChangePasswordDialog() {
|
||||
showDialog(
|
||||
generateDialog(
|
||||
{
|
||||
className: "change-password-dialog",
|
||||
},
|
||||
|
@ -1,75 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import { userService } from "../services";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { showDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
import "../less/confirm-reset-openid-dialog.less";
|
||||
|
||||
interface Props extends DialogProps {}
|
||||
|
||||
const ConfirmResetOpenIdDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
const resetBtnClickLoadingState = useLoading(false);
|
||||
|
||||
useEffect(() => {
|
||||
// do nth
|
||||
}, []);
|
||||
|
||||
const handleCloseBtnClick = () => {
|
||||
destroy();
|
||||
};
|
||||
|
||||
const handleConfirmBtnClick = async () => {
|
||||
if (resetBtnClickLoadingState.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
resetBtnClickLoadingState.setLoading();
|
||||
try {
|
||||
const user = userService.getState().user as User;
|
||||
await userService.patchUser({
|
||||
id: user.id,
|
||||
resetOpenId: true,
|
||||
});
|
||||
} catch (error) {
|
||||
toastHelper.error("Request reset open API failed.");
|
||||
return;
|
||||
}
|
||||
toastHelper.success("Reset open API succeeded.");
|
||||
handleCloseBtnClick();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dialog-header-container">
|
||||
<p className="title-text">Reset Open API</p>
|
||||
<button className="btn close-btn" onClick={handleCloseBtnClick}>
|
||||
<i className="fa-solid fa-xmark fa-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="dialog-content-container">
|
||||
<p className="warn-text">
|
||||
❗️The existing API will be invalidated and a new one will be generated, are you sure you want to reset?
|
||||
</p>
|
||||
<div className="btns-container">
|
||||
<span className="btn cancel-btn" onClick={handleCloseBtnClick}>
|
||||
Cancel
|
||||
</span>
|
||||
<span className={`btn confirm-btn ${resetBtnClickLoadingState.isLoading ? "loading" : ""}`} onClick={handleConfirmBtnClick}>
|
||||
Reset!
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function showConfirmResetOpenIdDialog() {
|
||||
showDialog(
|
||||
{
|
||||
className: "confirm-reset-openid-dialog",
|
||||
},
|
||||
ConfirmResetOpenIdDialog
|
||||
);
|
||||
}
|
||||
|
||||
export default showConfirmResetOpenIdDialog;
|
@ -2,7 +2,7 @@ import { memo, useCallback, useEffect, useState } from "react";
|
||||
import { memoService, shortcutService } from "../services";
|
||||
import { checkShouldShowMemoWithFilters, filterConsts, getDefaultFilter, relationConsts } from "../helpers/filter";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { showDialog } from "./Dialog";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import toastHelper from "./Toast";
|
||||
import Selector from "./common/Selector";
|
||||
import "../less/create-shortcut-dialog.less";
|
||||
@ -304,7 +304,7 @@ const FilterInputer: React.FC<MemoFilterInputerProps> = (props: MemoFilterInpute
|
||||
const MemoFilterInputer: React.FC<MemoFilterInputerProps> = memo(FilterInputer);
|
||||
|
||||
export default function showCreateShortcutDialog(shortcutId?: ShortcutId): void {
|
||||
showDialog(
|
||||
generateDialog(
|
||||
{
|
||||
className: "create-shortcut-dialog",
|
||||
},
|
||||
|
@ -4,7 +4,7 @@ import toImage from "../labs/html2image";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import { DAILY_TIMESTAMP } from "../helpers/consts";
|
||||
import * as utils from "../helpers/utils";
|
||||
import { showDialog } from "./Dialog";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import DatePicker from "./common/DatePicker";
|
||||
import showPreviewImageDialog from "./PreviewImageDialog";
|
||||
import DailyMemo from "./DailyMemo";
|
||||
@ -108,7 +108,7 @@ const DailyReviewDialog: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
export default function showDailyReviewDialog(datestamp: DateStamp = Date.now()): void {
|
||||
showDialog(
|
||||
generateDialog(
|
||||
{
|
||||
className: "daily-review-dialog",
|
||||
useAppContext: true,
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { ANIMATION_DURATION } from "../helpers/consts";
|
||||
import { Provider } from "react-redux";
|
||||
import store from "../store";
|
||||
import "../less/dialog.less";
|
||||
import { ANIMATION_DURATION } from "../../helpers/consts";
|
||||
import store from "../../store";
|
||||
import "../../less/base-dialog.less";
|
||||
|
||||
interface DialogConfig {
|
||||
className: string;
|
||||
@ -32,7 +32,7 @@ const BaseDialog: React.FC<Props> = (props: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export function showDialog<T extends DialogProps>(
|
||||
export function generateDialog<T extends DialogProps>(
|
||||
config: DialogConfig,
|
||||
DialogComponent: React.FC<T>,
|
||||
props?: Omit<T, "destroy">
|
84
web/src/components/Dialog/CommonDialog.tsx
Normal file
84
web/src/components/Dialog/CommonDialog.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
import { generateDialog } from "./BaseDialog";
|
||||
import "../../less/common-dialog.less";
|
||||
|
||||
type DialogStyle = "info" | "warning";
|
||||
|
||||
interface Props extends DialogProps {
|
||||
title: string;
|
||||
content: string;
|
||||
style?: DialogStyle;
|
||||
closeBtnText?: string;
|
||||
confirmBtnText?: string;
|
||||
onClose?: () => void;
|
||||
onConfirm?: () => void;
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
title: "",
|
||||
content: "",
|
||||
style: "info",
|
||||
closeBtnText: "Close",
|
||||
confirmBtnText: "Confirm",
|
||||
onClose: () => null,
|
||||
onConfirm: () => null,
|
||||
};
|
||||
|
||||
const CommonDialog: React.FC<Props> = (props: Props) => {
|
||||
const { title, content, destroy, closeBtnText, confirmBtnText, onClose, onConfirm, style } = {
|
||||
...defaultProps,
|
||||
...props,
|
||||
};
|
||||
|
||||
const handleCloseBtnClick = () => {
|
||||
onClose();
|
||||
destroy();
|
||||
};
|
||||
|
||||
const handleConfirmBtnClick = async () => {
|
||||
onConfirm();
|
||||
destroy();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dialog-header-container">
|
||||
<p className="title-text">{title}</p>
|
||||
<button className="btn close-btn" onClick={handleCloseBtnClick}>
|
||||
<i className="fa-solid fa-xmark fa-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="dialog-content-container">
|
||||
<p className="content-text">{content}</p>
|
||||
<div className="btns-container">
|
||||
<span className="btn cancel-btn" onClick={handleCloseBtnClick}>
|
||||
{closeBtnText}
|
||||
</span>
|
||||
<span className={`btn confirm-btn ${style}`} onClick={handleConfirmBtnClick}>
|
||||
{confirmBtnText}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface CommonDialogProps {
|
||||
title: string;
|
||||
content: string;
|
||||
className?: string;
|
||||
style?: DialogStyle;
|
||||
closeBtnText?: string;
|
||||
confirmBtnText?: string;
|
||||
onClose?: () => void;
|
||||
onConfirm?: () => void;
|
||||
}
|
||||
|
||||
export const showCommonDialog = (props: CommonDialogProps) => {
|
||||
generateDialog(
|
||||
{
|
||||
className: `common-dialog ${props?.className ?? ""}`,
|
||||
},
|
||||
CommonDialog,
|
||||
props
|
||||
);
|
||||
};
|
1
web/src/components/Dialog/index.ts
Normal file
1
web/src/components/Dialog/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { generateDialog } from "./BaseDialog";
|
@ -5,7 +5,7 @@ import * as utils from "../helpers/utils";
|
||||
import { parseHtmlToRawText } from "../helpers/marked";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import toastHelper from "./Toast";
|
||||
import { showDialog } from "./Dialog";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import Image from "./Image";
|
||||
import { formatMemoContent } from "./Memo";
|
||||
import "../less/memo-card-dialog.less";
|
||||
@ -127,14 +127,16 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
|
||||
return (
|
||||
<>
|
||||
<Only when={!userService.isVisitorMode()}>
|
||||
<div className="visibility-selector-container">
|
||||
<i className="fa-solid fa-eye text-white mr-2"></i>
|
||||
<Selector
|
||||
className="w-32"
|
||||
dataSource={visibilityList}
|
||||
value={memo.visibility}
|
||||
handleValueChanged={(value) => handleVisibilitySelectorChange(value as Visibility)}
|
||||
/>
|
||||
<div className="card-header-container">
|
||||
<div className="visibility-selector-container">
|
||||
<i className="fa-solid fa-eye mr-2"></i>
|
||||
<Selector
|
||||
className="visibility-selector"
|
||||
dataSource={visibilityList}
|
||||
value={memo.visibility}
|
||||
handleValueChanged={(value) => handleVisibilitySelectorChange(value as Visibility)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Only>
|
||||
<div className="memo-card-container">
|
||||
@ -221,7 +223,7 @@ const MemoCardDialog: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
export default function showMemoCardDialog(memo: Memo): void {
|
||||
showDialog(
|
||||
generateDialog(
|
||||
{
|
||||
className: "memo-card-dialog",
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { showDialog } from "./Dialog";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import * as utils from "../helpers/utils";
|
||||
import "../less/preview-image-dialog.less";
|
||||
|
||||
@ -36,7 +36,7 @@ const PreviewImageDialog: React.FC<Props> = ({ destroy, imgUrl }: Props) => {
|
||||
};
|
||||
|
||||
export default function showPreviewImageDialog(imgUrl: string): void {
|
||||
showDialog(
|
||||
generateDialog(
|
||||
{
|
||||
className: "preview-image-dialog",
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useAppSelector } from "../store";
|
||||
import { showDialog } from "./Dialog";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import MyAccountSection from "./Settings/MyAccountSection";
|
||||
import PreferencesSection from "./Settings/PreferencesSection";
|
||||
import MemberSection from "./Settings/MemberSection";
|
||||
@ -76,7 +76,7 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
export default function showSettingDialog(): void {
|
||||
showDialog(
|
||||
generateDialog(
|
||||
{
|
||||
className: "setting-dialog",
|
||||
useAppContext: true,
|
||||
|
@ -5,6 +5,7 @@ import { useAppSelector } from "../../store";
|
||||
import * as api from "../../helpers/api";
|
||||
import toastHelper from "../Toast";
|
||||
import "../../less/settings/member-section.less";
|
||||
import { showCommonDialog } from "../Dialog/CommonDialog";
|
||||
|
||||
interface Props {}
|
||||
|
||||
@ -85,12 +86,18 @@ const PreferencesSection: React.FC<Props> = () => {
|
||||
fetchUserList();
|
||||
};
|
||||
|
||||
// TODO: show a dialog to confirm delete user.
|
||||
const handleDeleteUserClick = async (user: User) => {
|
||||
await userService.deleteUser({
|
||||
id: user.id,
|
||||
const handleDeleteUserClick = (user: User) => {
|
||||
showCommonDialog({
|
||||
title: `Delete ${user.name}`,
|
||||
content: "❗️Are you sure you want to delete?",
|
||||
style: "warning",
|
||||
onConfirm: async () => {
|
||||
await userService.deleteUser({
|
||||
id: user.id,
|
||||
});
|
||||
fetchUserList();
|
||||
},
|
||||
});
|
||||
fetchUserList();
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -3,8 +3,8 @@ import { useAppSelector } from "../../store";
|
||||
import { userService } from "../../services";
|
||||
import { validate, ValidatorConfig } from "../../helpers/validator";
|
||||
import toastHelper from "../Toast";
|
||||
import { showCommonDialog } from "../Dialog/CommonDialog";
|
||||
import showChangePasswordDialog from "../ChangePasswordDialog";
|
||||
import showConfirmResetOpenIdDialog from "../ConfirmResetOpenIdDialog";
|
||||
import "../../less/settings/my-account-section.less";
|
||||
|
||||
const validateConfig: ValidatorConfig = {
|
||||
@ -53,7 +53,11 @@ const MyAccountSection: React.FC<Props> = () => {
|
||||
};
|
||||
|
||||
const handleResetOpenIdBtnClick = async () => {
|
||||
showConfirmResetOpenIdDialog();
|
||||
showCommonDialog({
|
||||
title: "Reset Open API",
|
||||
content: "❗️The existing API will be invalidated and a new one will be generated, are you sure you want to reset?",
|
||||
style: "warning",
|
||||
});
|
||||
};
|
||||
|
||||
const handlePreventDefault = (e: React.MouseEvent) => {
|
||||
|
@ -3,7 +3,7 @@ import { userService } from "../services";
|
||||
import toImage from "../labs/html2image";
|
||||
import { ANIMATION_DURATION, IMAGE_URL_REG } from "../helpers/consts";
|
||||
import * as utils from "../helpers/utils";
|
||||
import { showDialog } from "./Dialog";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import toastHelper from "./Toast";
|
||||
import { formatMemoContent } from "./Memo";
|
||||
@ -107,7 +107,7 @@ const ShareMemoImageDialog: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
export default function showShareMemoImageDialog(memo: Memo): void {
|
||||
showDialog(
|
||||
generateDialog(
|
||||
{
|
||||
className: "share-memo-image-dialog",
|
||||
},
|
||||
|
40
web/src/less/base-dialog.less
Normal file
40
web/src/less/base-dialog.less
Normal file
@ -0,0 +1,40 @@
|
||||
@import "./mixin.less";
|
||||
|
||||
.dialog-wrapper {
|
||||
@apply fixed top-0 left-0 flex flex-col justify-start items-center w-full h-full pt-16 z-100 overflow-x-hidden overflow-y-scroll bg-transparent transition-all;
|
||||
.hide-scroll-bar();
|
||||
|
||||
&.showup {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
&.showoff {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> .dialog-container {
|
||||
@apply flex flex-col justify-start items-start bg-white p-4 rounded-lg;
|
||||
|
||||
> .dialog-header-container {
|
||||
@apply flex flex-row justify-between items-center w-full mb-4;
|
||||
|
||||
> .title-text {
|
||||
> .icon-text {
|
||||
@apply mr-2 text-base;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply flex flex-col justify-center items-center w-6 h-6 rounded hover:bg-gray-100 hover:shadow;
|
||||
}
|
||||
}
|
||||
|
||||
> .dialog-content-container {
|
||||
@apply flex flex-col justify-start items-start w-full;
|
||||
}
|
||||
|
||||
> .dialog-footer-container {
|
||||
@apply flex flex-row justify-end items-center w-full mt-4;
|
||||
}
|
||||
}
|
||||
}
|
31
web/src/less/common-dialog.less
Normal file
31
web/src/less/common-dialog.less
Normal file
@ -0,0 +1,31 @@
|
||||
@import "./mixin.less";
|
||||
|
||||
.common-dialog {
|
||||
> .dialog-container {
|
||||
@apply w-80;
|
||||
|
||||
> .dialog-content-container {
|
||||
@apply flex flex-col justify-start items-start;
|
||||
|
||||
> .content-text {
|
||||
@apply pt-2;
|
||||
}
|
||||
|
||||
> .btns-container {
|
||||
@apply flex flex-row justify-end items-center w-full mt-3;
|
||||
|
||||
> .btn {
|
||||
@apply text-sm py-1 px-3 mr-2 rounded-md hover:opacity-80;
|
||||
|
||||
&.confirm-btn {
|
||||
@apply bg-red-100 border border-solid border-blue-600 text-blue-600;
|
||||
|
||||
&.warning {
|
||||
@apply border-red-600 text-red-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
@import "./mixin.less";
|
||||
|
||||
.confirm-reset-openid-dialog {
|
||||
> .dialog-container {
|
||||
@apply w-80;
|
||||
|
||||
> .dialog-content-container {
|
||||
.flex(column, flex-start, flex-start);
|
||||
|
||||
> .warn-text {
|
||||
@apply pt-2;
|
||||
}
|
||||
|
||||
> .btns-container {
|
||||
.flex(row, flex-end, center);
|
||||
@apply w-full mt-3;
|
||||
|
||||
> .btn {
|
||||
@apply text-sm py-1 px-3 mr-2 rounded-md;
|
||||
|
||||
&:hover {
|
||||
@apply opacity-80;
|
||||
}
|
||||
|
||||
&.confirm-btn {
|
||||
@apply bg-red-100 border border-solid border-red-600 text-red-600;
|
||||
|
||||
&.loading {
|
||||
@apply opacity-80 cursor-wait;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
@import "./mixin.less";
|
||||
|
||||
.dialog-wrapper {
|
||||
.flex(column, flex-start, center);
|
||||
@apply fixed top-0 left-0 w-full h-full pt-16 z-100 overflow-x-hidden overflow-y-scroll bg-transparent transition-all;
|
||||
.hide-scroll-bar();
|
||||
|
||||
&.showup {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
&.showoff {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> .dialog-container {
|
||||
.flex(column, flex-start, flex-start);
|
||||
@apply bg-white p-4 rounded-lg;
|
||||
|
||||
> .dialog-header-container {
|
||||
.flex(row, space-between, center);
|
||||
@apply w-full mb-4;
|
||||
|
||||
> .title-text {
|
||||
> .icon-text {
|
||||
@apply mr-2 text-base;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
.flex(column, center, center);
|
||||
@apply w-6 h-6 rounded hover:bg-gray-100 hover:shadow;
|
||||
}
|
||||
}
|
||||
|
||||
> .dialog-content-container {
|
||||
.flex(column, flex-start, flex-start);
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
> .dialog-footer-container {
|
||||
.flex(row, flex-end, center);
|
||||
@apply w-full mt-4;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,8 +6,20 @@
|
||||
> .dialog-container {
|
||||
@apply w-full p-0 bg-transparent flex flex-col justify-start items-center;
|
||||
|
||||
> .visibility-selector-container {
|
||||
@apply z-10 w-128 mb-2 flex flex-row justify-start items-center;
|
||||
> .card-header-container {
|
||||
@apply z-10 w-128 max-w-full flex flex-row justify-start items-center mb-2;
|
||||
|
||||
> .visibility-selector-container {
|
||||
@apply bg-white px-2 py-1 rounded-lg flex flex-row justify-start items-center;
|
||||
|
||||
> .visibility-selector {
|
||||
@apply w-32;
|
||||
|
||||
> .current-value-container {
|
||||
@apply border-none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .memo-card-container {
|
||||
|
Loading…
Reference in New Issue
Block a user