feat: improve UI of all dialogs

This commit is contained in:
thecodrr
2020-09-27 15:35:11 +05:00
parent 1781115ab2
commit 27c0163781
19 changed files with 447 additions and 297 deletions

View File

@@ -6,7 +6,7 @@ import { store as editorStore } from "../stores/editor-store";
import { store as trashStore } from "../stores/trash-store"; import { store as trashStore } from "../stores/trash-store";
import { db } from "./index"; import { db } from "./index";
import { showMoveNoteDialog } from "../components/dialogs/movenotedialog"; import { showMoveNoteDialog } from "../components/dialogs/movenotedialog";
import { confirm } from "../components/dialogs/confirm"; import { confirm, showDeleteConfirmation } from "../components/dialogs/confirm";
function createOption(key, icon, onClick) { function createOption(key, icon, onClick) {
return { return {
@@ -26,12 +26,10 @@ function createOptions(options = []) {
const DeleteOption = createOption("deleteOption", Icon.Trash, async function ( const DeleteOption = createOption("deleteOption", Icon.Trash, async function (
state state
) { ) {
if (
!(await confirm(Icon.Trash, "Delete", "Are you sure you want to proceed?"))
)
return;
const item = state.selectedItems[0]; const item = state.selectedItems[0];
if (!(await showDeleteConfirmation(item.type, true))) return;
var isAnyNoteOpened = false; var isAnyNoteOpened = false;
const items = state.selectedItems.map((item) => { const items = state.selectedItems.map((item) => {
if (item.id === editorStore.get().session.id) isAnyNoteOpened = true; if (item.id === editorStore.get().session.id) isAnyNoteOpened = true;

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Flex, Box, Text, Button as RebassButton } from "rebass"; import { Flex, Box, Button as RebassButton } from "rebass";
import { Input } from "@rebass/forms"; import { Input } from "@rebass/forms";
import * as Icon from "../icons"; import * as Icon from "../icons";
import Dialog, { showDialog } from "./dialog"; import Dialog, { showDialog } from "./dialog";
@@ -86,10 +86,13 @@ class AddNotebookDialog extends React.Component {
return ( return (
<Dialog <Dialog
isOpen={props.isOpen} isOpen={props.isOpen}
title="Notebook" title={props.edit ? "Edit this Notebook" : "Create a Notebook"}
description={
props.edit ? "" : "Notebooks are the best way to organize your notes."
}
icon={Icon.Notebook} icon={Icon.Notebook}
positiveButton={{ positiveButton={{
text: props.edit ? "Edit" : "Add", text: "Create notebook",
onClick: () => { onClick: () => {
props.onDone({ props.onDone({
title: this.title, title: this.title,
@@ -104,7 +107,7 @@ class AddNotebookDialog extends React.Component {
}} }}
negativeButton={{ text: "Cancel", onClick: props.close }} negativeButton={{ text: "Cancel", onClick: props.close }}
> >
<Box my={1}> <Box>
<Input <Input
autoFocus autoFocus
onChange={(e) => (this.title = e.target.value)} onChange={(e) => (this.title = e.target.value)}
@@ -112,19 +115,16 @@ class AddNotebookDialog extends React.Component {
defaultValue={this.title} defaultValue={this.title}
/> />
<Input <Input
sx={{ marginTop: 1 }} sx={{ marginTop: 2 }}
onChange={(e) => (this.description = e.target.value)} onChange={(e) => (this.description = e.target.value)}
placeholder="Enter description (optional)" placeholder="Enter description (optional)"
defaultValue={this.description} defaultValue={this.description}
/> />
<Text variant="body" fontWeight="bold" my={1}>
Topics (optional):
</Text>
<Box <Box
mt={2}
sx={{ sx={{
maxHeight: this.MAX_AVAILABLE_HEIGHT, maxHeight: this.MAX_AVAILABLE_HEIGHT,
overflowY: "auto", overflowY: "auto",
marginBottom: 1,
}} }}
> >
{this.state.topics.map( {this.state.topics.map(
@@ -164,7 +164,7 @@ class AddNotebookDialog extends React.Component {
/> />
<RebassButton <RebassButton
variant="tertiary" variant="tertiary"
sx={{ marginLeft: 1 }} sx={{ marginLeft: 2 }}
px={2} px={2}
py={1} py={1}
onClick={() => this.performActionOnTopic(index)} onClick={() => this.performActionOnTopic(index)}

View File

@@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { Box, Text } from "rebass"; import { Box, Text } from "rebass";
import Dialog, { showDialog } from "./dialog"; import Dialog, { showDialog } from "./dialog";
import * as Icon from "../icons";
function Confirm(props) { function Confirm(props) {
return ( return (
@@ -8,14 +9,15 @@ function Confirm(props) {
isOpen={true} isOpen={true}
title={props.title} title={props.title}
icon={props.icon} icon={props.icon}
description={props.subtitle}
positiveButton={{ positiveButton={{
text: "Yes", text: props.yesText,
onClick: props.onYes, onClick: props.onYes,
}} }}
negativeButton={{ text: "No", onClick: props.onNo }} negativeButton={{ text: props.noText, onClick: props.onNo }}
> >
<Box my={1}> <Box my={2}>
<Text textAlign="center" variant="body"> <Text as="span" variant="body" fontSize={18}>
{props.message} {props.message}
</Text> </Text>
</Box> </Box>
@@ -23,14 +25,67 @@ function Confirm(props) {
); );
} }
export function confirm(icon, title, message) { export function confirm(icon, { title, subtitle, message, yesText, noText }) {
return showDialog((perform) => ( return showDialog((perform) => (
<Confirm <Confirm
title={title} title={title}
subtitle={subtitle}
message={message} message={message}
yesText={yesText}
noText={noText}
icon={icon} icon={icon}
onNo={() => perform(false)} onNo={() => perform(false)}
onYes={() => perform(true)} onYes={() => perform(true)}
/> />
)); ));
} }
/**
*
* @param {"note"|"notebook"} type
*/
export function showDeleteConfirmation(type, multi = false) {
let noun = type === "note" ? "Note" : "Notebook";
if (multi) noun += "s";
let lowerCaseNoun = noun.toLowerCase();
let [firstPronoun, secondPronoun] = multi
? ["these", "they"]
: ["this", "it"];
return confirm(Icon.Trash, {
title: `Delete ${noun}`,
subtitle: `Are you sure you want to delete ${firstPronoun} ${lowerCaseNoun}?`,
message: (
<Text as="span">
The {lowerCaseNoun} will be{" "}
<Text as="span" color="primary">
kept in your Trash for 7 days
</Text>{" "}
after which {secondPronoun} will be permanently removed.
</Text>
),
yesText: `Delete ${lowerCaseNoun}`,
noText: "Cancel",
});
}
export function showMultiDeleteConfirmation(type) {
let noun = type === "note" ? "Notes" : "Notebooks";
return confirm(Icon.Trash, {
title: `Delete these ${noun}`,
subtitle: `Are you sure you want to delete these ${type}s?`,
message: (
<Text as="span">
These {type}s will be{" "}
<Text as="span" color="primary">
kept in your Trash for 7 days
</Text>{" "}
after which they will be permanently removed.
</Text>
),
yesText: `Delete these ${type}s`,
noText: "Cancel",
});
}

View File

@@ -5,13 +5,13 @@ import ThemeProvider from "../theme-provider";
import * as Icon from "../icons"; import * as Icon from "../icons";
import Modal from "react-modal"; import Modal from "react-modal";
import useMobile from "../../utils/use-mobile"; import useMobile from "../../utils/use-mobile";
import { useTheme } from "emotion-theming";
function Dialog(props) { function Dialog(props) {
const isMobile = useMobile(); const isMobile = useMobile();
const theme = useTheme();
return ( return (
<ThemeProvider>
{(theme) => (
<Modal <Modal
isOpen={props.isOpen || false} isOpen={props.isOpen || false}
shouldCloseOnOverlayClick={true} shouldCloseOnOverlayClick={true}
@@ -28,10 +28,9 @@ function Dialog(props) {
borderRadius: theme.radii["default"], borderRadius: theme.radii["default"],
backgroundColor: theme.colors.background, backgroundColor: theme.colors.background,
color: theme.colors.text, color: theme.colors.text,
//boxShadow: theme.shadows["3"], boxShadow: "4px 5px 18px 2px #00000038",
width: isMobile ? "80%" : "25%", width: isMobile ? "80%" : "30%",
paddingRight: 20, padding: 0,
paddingLeft: 20,
overflowY: "hidden", overflowY: "hidden",
}, },
overlay: { overlay: {
@@ -40,21 +39,31 @@ function Dialog(props) {
}, },
}} }}
> >
<Flex flexDirection="column"> <Flex p={30} flexDirection="column">
<Flex variant="rowCenter" pb={2}> <Flex
variant="columnCenter"
pb={2}
mb={3}
sx={{ borderBottom: "1px solid", borderColor: "border" }}
>
<props.icon size={props.iconSize || 38} color="primary" /> <props.icon size={props.iconSize || 38} color="primary" />
<Text variant="heading" color="primary" mx={1}> <Text variant="heading" textAlign="center" color="text" mx={1} mt={1}>
{props.title} {props.title}
</Text> </Text>
<Text variant="body" textAlign="center" color="gray" mx={1} mt={1}>
{props.description}
</Text>
</Flex> </Flex>
{props.children} {props.children}
<Flex variant="rowCenter" mt={3}> <Flex
sx={{ justifyContent: props.buttonsAlignment || "flex-end" }}
mt={3}
>
{props.positiveButton && ( {props.positiveButton && (
<RebassButton <RebassButton
variant="primary" variant="primary"
sx={{ opacity: props.positiveButton.disabled ? 0.7 : 1 }} sx={{ opacity: props.positiveButton.disabled ? 0.7 : 1 }}
mx={1} mx={1}
width={"50%"}
disabled={props.positiveButton.disabled || false} disabled={props.positiveButton.disabled || false}
onClick={ onClick={
!props.positiveButton.disabled !props.positiveButton.disabled
@@ -69,21 +78,18 @@ function Dialog(props) {
)} )}
</RebassButton> </RebassButton>
)} )}
{props.negativeButton && ( {props.negativeButton && (
<RebassButton <RebassButton
variant="secondary" variant="secondary"
width={"50%"}
onClick={props.negativeButton.onClick} onClick={props.negativeButton.onClick}
> >
{props.negativeButton.text || "Cancel"} {props.negativeButton.text || "Cancel"}
</RebassButton> </RebassButton>
)} )}
</Flex> </Flex>
{props.footer}
</Flex> </Flex>
</Modal> </Modal>
)}
</ThemeProvider>
); );
} }
export default Dialog; export default Dialog;
@@ -97,7 +103,7 @@ export function showDialog(dialog) {
if (root) { if (root) {
return new Promise((resolve) => { return new Promise((resolve) => {
const PropDialog = dialog(perform.bind(this, resolve)); const PropDialog = dialog(perform.bind(this, resolve));
ReactDOM.render(PropDialog, root); ReactDOM.render(<ThemeProvider>{PropDialog}</ThemeProvider>, root);
}); });
} }
return Promise.reject("No element with id 'dialogContainer'"); return Promise.reject("No element with id 'dialogContainer'");

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Flex, Button, Text, Box } from "rebass"; import { Flex, Button, Text } from "rebass";
import { db } from "../../common"; import { db } from "../../common";
import download from "../../utils/download"; import download from "../../utils/download";
import { showToast } from "../../utils/toast"; import { showToast } from "../../utils/toast";
@@ -12,38 +12,33 @@ function ExportDialog(props) {
isOpen={true} isOpen={true}
title={props.title} title={props.title}
icon={props.icon} icon={props.icon}
negativeButton={{ onClick: props.onClose, text: "Cancel" }} description="You can export your note to Markdown, HTML, or Text."
buttonsAlignment="center"
negativeButton={{
onClick: props.onClose,
text: "I don't want to export anymore",
}}
> >
<Box> <Flex justifyContent="center" alignItems="center">
<Text variant="body" mb={2}> <Button mr={2} onClick={() => props.exportNote("html")}>
Please choose a format to export the note into: <Flex variant="rowCenter">
</Text> <Icon.HTML color="static" />
<Flex my={1} justifyContent="center" alignItems="center"> <Text ml={1}>HTML</Text>
<Button </Flex>
variant="tertiary"
mr={2}
onClick={() => props.exportNote("html")}
>
<Icon.HTML size={100} color="dimPrimary" /> HTML
</Button> </Button>
<Button <Button mr={2} onClick={() => props.exportNote("md")}>
variant="tertiary" <Flex variant="rowCenter">
mr={2} <Icon.Markdown color="static" />
onClick={() => props.exportNote("md")} <Text ml={1}>Markdown</Text>
> </Flex>
<Icon.Markdown size={100} color="dimPrimary" />
Markdown
</Button> </Button>
<Button <Button mr={2} onClick={() => props.exportNote("txt")}>
variant="tertiary" <Flex variant="rowCenter">
mr={2} <Icon.Text color="static" />
onClick={() => props.exportNote("txt")} <Text ml={1}>Text</Text>
> </Flex>
<Icon.Text size={100} color="dimPrimary" />
Text
</Button> </Button>
</Flex> </Flex>
</Box>
</Dialog> </Dialog>
); );
} }
@@ -51,7 +46,7 @@ function ExportDialog(props) {
export function showExportDialog(noteId) { export function showExportDialog(noteId) {
return showDialog((perform) => ( return showDialog((perform) => (
<ExportDialog <ExportDialog
title={"Export Note"} title={"Export your Note"}
icon={Icon.Export} icon={Icon.Export}
onClose={() => perform(false)} onClose={() => perform(false)}
exportNote={async (format) => { exportNote={async (format) => {

View File

@@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Box, Button, Text } from "rebass"; import { Flex, Button, Text } from "rebass";
import Input from "../inputs"; import Input from "../inputs";
import * as Icon from "../icons"; import * as Icon from "../icons";
import Dialog, { showDialog } from "./dialog"; import Dialog, { showDialog } from "./dialog";
@@ -18,19 +18,38 @@ function LoginDialog(props) {
return ( return (
<Dialog <Dialog
isOpen={true} isOpen={true}
title={"Login"} title={"Sign in to Your Account"}
description={"Signing in allows you to sync your notes across devices."}
icon={Icon.Login} icon={Icon.Login}
onCloseClick={onClose} onCloseClick={onClose}
negativeButton={{ onClick: onClose }} negativeButton={{ text: "I don't want to", onClick: onClose }}
positiveButton={{ positiveButton={{
text: "Login", text: "Sign me in",
loading: isLoggingIn, loading: isLoggingIn,
disabled: isLoggingIn, disabled: isLoggingIn,
onClick: () => submit(setError, form, login, onClose), onClick: () => submit(setError, form, login, onClose),
}} }}
buttonsAlignment="center"
footer={
<>
<Text textAlign="center" color="gray" mt={3}>
Don't have an account?
</Text>
<Button
mt={3}
variant="anchor"
justifySelf="center"
alignSelf="center"
fontSize="body"
onClick={showSignUpDialog}
> >
<Box Sign up here
mt={1} </Button>
</>
}
>
<Flex
variant="columnFill"
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter") submit(setError, form, login, onClose); if (e.key === "Enter") submit(setError, form, login, onClose);
}} }}
@@ -39,11 +58,11 @@ function LoginDialog(props) {
<Input autoFocus name="username" title="Username" /> <Input autoFocus name="username" title="Username" />
<PasswordInput /> <PasswordInput />
</Dropper> </Dropper>
<Button variant="anchor" onClick={showSignUpDialog}>
Create a New Account
</Button>
{error && <Text variant="error">{error}</Text>} {error && <Text variant="error">{error}</Text>}
</Box> {/* <Button variant="anchor" onClick={showSignUpDialog}>
I don't have an account
</Button> */}
</Flex>
</Dialog> </Dialog>
); );
} }

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Flex, Box, Text } from "rebass"; import { Flex, Box, Text, Button } from "rebass";
import { Input } from "@rebass/forms"; import { Input } from "@rebass/forms";
import * as Icon from "../icons"; import * as Icon from "../icons";
import { db } from "../../common"; import { db } from "../../common";
@@ -14,73 +14,32 @@ class MoveDialog extends React.Component {
selectedNotebook; selectedNotebook;
selectedTopic; selectedTopic;
state = { state = {
items: db.notebooks.all, currentOpenedIndex: -1,
type: "notebooks", notebooks: db.notebooks.all,
title: "Notebooks",
mode: "read",
}; };
render() { render() {
const { items, type, title, mode } = this.state; const { notebooks, currentOpenedIndex } = this.state;
const props = this.props; const props = this.props;
return ( return (
<Dialog <Dialog
isOpen={true} isOpen={true}
title={ title={"Add Note to Notebook"}
type === "notes" description={"Organize your notes by adding them to notebooks."}
? "Move Note" icon={Icon.Move}
: "Select " + toTitleCase(type.substring(0, type.length - 1)) buttonsAlignment="center"
} negativeButton={{
icon={ text: "Get me out of here",
type === "notes" onClick: props.onClose,
? Icon.Move
: type === "topics"
? Icon.Topic
: Icon.Notebook
}
positiveButton={{
text: "Move",
onClick: async () => {
try {
const notebook = {
id: this.selectedNotebook.id,
topic: this.selectedTopic,
};
const note = db.notes.note(props.noteIds[0]).data;
await db.notes.move(notebook, ...props.noteIds);
showNotesMovedToast(note, props.noteIds, notebook);
props.onMove();
} catch (e) {
showToast("error", e.message);
console.error(e);
} finally {
props.onClose();
}
},
disabled: type !== "notes",
}} }}
negativeButton={{ text: "Cancel", onClick: props.onClose }}
> >
<Box> <Box>
<Flex alignContent="center" justifyContent="space-between" my={1}> <Flex alignItems="center" justifyContent="space-between" mb={2}>
<Flex> <Text variant="title">Notebooks</Text>
<Text <Button
onClick={() => { variant="anchor"
let item = this.history.pop(); fontSize="body"
this.setState({ ...item }); /* onClick={() => {
}}
sx={{
display: this.history.length ? "block" : "none",
":hover": { color: "primary" },
marginRight: 2,
}}
>
<Icon.ArrowLeft />
</Text>
<Text variant="title">{title}</Text>
</Flex>
<Text
onClick={() => {
if (mode === "write") { if (mode === "write") {
this.setState({ mode: "read" }); this.setState({ mode: "read" });
return; return;
@@ -89,16 +48,12 @@ class MoveDialog extends React.Component {
setTimeout(() => { setTimeout(() => {
this._inputRef.focus(); this._inputRef.focus();
}, 0); }, 0);
}} }} */
sx={{
display: type === "notes" ? "none" : "block",
":hover": { color: "primary" },
}}
> >
{mode === "read" ? <Icon.Plus /> : <Icon.Minus />} Create
</Text> </Button>
</Flex> </Flex>
<Input {/* <Input
ref={(ref) => (this._inputRef = ref)} ref={(ref) => (this._inputRef = ref)}
sx={{ display: mode === "write" ? "block" : "none" }} sx={{ display: mode === "write" ? "block" : "none" }}
my={1} my={1}
@@ -123,7 +78,7 @@ class MoveDialog extends React.Component {
this.setState({ mode: "read" }); this.setState({ mode: "read" });
} }
}} }}
/> /> */}
<Box <Box
sx={{ sx={{
borderWidth: 1, borderWidth: 1,
@@ -133,20 +88,11 @@ class MoveDialog extends React.Component {
overflowY: "auto", overflowY: "auto",
}} }}
> >
{items.length ? ( {notebooks.map((notebook, index) => (
items.map((item) => {
return (
<Flex <Flex
key={item.title + item.dateCreated} variant="columnFill"
sx={{ key={notebook.id}
borderWidth: 1, /* onClick={() => {
padding: 2,
borderBottomColor: "border",
borderBottomStyle: "solid",
cursor: "pointer",
":hover": { borderBottomColor: "primary" },
}}
onClick={() => {
this.history.push({ this.history.push({
title, title,
items, items,
@@ -169,31 +115,59 @@ class MoveDialog extends React.Component {
}); });
this.selectedTopic = item.title; this.selectedTopic = item.title;
} }
}} */
>
<Item
icon={Icon.Notebook}
title={notebook.title}
totalNotes={notebook.totalNotes}
onClick={() =>
this.setState({
currentOpenedIndex:
this.state.currentOpenedIndex === index ? -1 : index,
})
}
/>
<Flex
variant="columnFill"
style={{
display: currentOpenedIndex === index ? "flex" : "none",
}} }}
> >
<Text sx={{ width: "80%" }} fontSize="body"> {notebook.topics.map((topic) => (
{item.title} <Item
key={topic.id}
onClick={async () => {
try {
const nb = {
id: notebook.id,
topic: topic.id,
};
const note = db.notes.note(props.noteIds[0]).data;
await db.notes.move(nb, ...props.noteIds);
showNotesMovedToast(note, props.noteIds, nb);
props.onMove();
} catch (e) {
showToast("error", e.message);
console.error(e);
} finally {
props.onClose();
}
}}
indent={1}
icon={Icon.Topic}
title={topic.title}
totalNotes={topic.totalNotes}
action={
<Text color="primary" fontSize="body">
Move here
</Text> </Text>
{item.totalNotes !== undefined && ( }
<Text />
sx={{ width: "20%", textAlign: "right" }} ))}
fontSize="body"
>
{item.totalNotes + " Notes"}
</Text>
)}
</Flex> </Flex>
); </Flex>
}) ))}
) : (
<Text
py={2}
px={2}
sx={{ textAlign: "center", fontStyle: "italic" }}
>
Nothing here
</Text>
)}
</Box> </Box>
</Box> </Box>
</Dialog> </Dialog>
@@ -210,3 +184,35 @@ export function showMoveNoteDialog(noteIds) {
/> />
)); ));
} }
function Item(props) {
const { icon: Icon, indent = 0, title, totalNotes, onClick, action } = props;
return (
<Flex
p={2}
justifyContent="space-between"
alignItems="center"
pl={!indent ? 2 : indent * 30}
sx={{
borderWidth: 1,
borderBottomColor: "border",
borderBottomStyle: "solid",
cursor: "pointer",
":hover": { borderBottomColor: "primary" },
}}
onClick={onClick}
>
<Flex alignItems="center" justifyContent="center">
<Icon />
<Text as="span" ml={1} fontSize="body">
{title}
</Text>
</Flex>
{action || (
<Text sx={{ textAlign: "right" }} fontSize="body">
{totalNotes + " Notes"}
</Text>
)}
</Flex>
);
}

View File

@@ -20,6 +20,7 @@ function PasswordDialog(props) {
<Dialog <Dialog
isOpen={true} isOpen={true}
title={props.title} title={props.title}
description={props.subtitle}
icon={props.icon} icon={props.icon}
positiveButton={{ positiveButton={{
text: props.positiveButtonText, text: props.positiveButtonText,
@@ -59,21 +60,24 @@ function getDialogData(type) {
switch (type) { switch (type) {
case "create_vault": case "create_vault":
return { return {
title: "Set Up Your Vault", title: "Create Your Vault",
subtitle: "Your vault will encrypt everything locally.",
icon: Icon.Vault, icon: Icon.Vault,
positiveButtonText: "Done", positiveButtonText: "Create my vault",
}; };
case "unlock_vault": case "unlock_vault":
return { return {
title: "Unlock Vault", title: "Unlock your Vault",
subtitle: "Your vault will remain unlocked for 30 minutes.",
icon: Icon.Unlock, icon: Icon.Unlock,
positiveButtonText: "Unlock", positiveButtonText: "Unlock my vault",
}; };
case "unlock_note": case "unlock_note":
return { return {
title: "Unlock Note", title: "Unlock your Note",
subtitle: "Unlocking will make this note openly available.",
icon: Icon.Unlock, icon: Icon.Unlock,
positiveButtonText: "Unlock", positiveButtonText: "Unlock this note",
}; };
default: default:
return; return;
@@ -81,10 +85,11 @@ function getDialogData(type) {
} }
export function showPasswordDialog(type, validate) { export function showPasswordDialog(type, validate) {
const { title, icon, positiveButtonText } = getDialogData(type); const { title, subtitle, icon, positiveButtonText } = getDialogData(type);
return showDialog((perform) => ( return showDialog((perform) => (
<PasswordDialog <PasswordDialog
title={title} title={title}
subtitle={subtitle}
icon={icon} icon={icon}
positiveButtonText={positiveButtonText} positiveButtonText={positiveButtonText}
onCancel={() => perform(false)} onCancel={() => perform(false)}

View File

@@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Text, Box } from "rebass"; import { Text, Box, Button } from "rebass";
import Input from "../inputs"; import Input from "../inputs";
import * as Icon from "../icons"; import * as Icon from "../icons";
import Dialog, { showDialog } from "./dialog"; import Dialog, { showDialog } from "./dialog";
@@ -8,6 +8,7 @@ import EmailInput from "../inputs/email";
import PasswordInput from "../inputs/password"; import PasswordInput from "../inputs/password";
import Dropper from "../dropper"; import Dropper from "../dropper";
import { useStore } from "../../stores/user-store"; import { useStore } from "../../stores/user-store";
import { showLogInDialog } from "./logindialog";
const form = { error: true }; const form = { error: true };
function SignUpDialog(props) { function SignUpDialog(props) {
@@ -19,19 +20,35 @@ function SignUpDialog(props) {
return ( return (
<Dialog <Dialog
isOpen={true} isOpen={true}
title={"Sign Up"} title={"Create a new Account"}
icon={Icon.User} description={"Sign up for a 14-day free trial (no credit card)."}
icon={Icon.Signup}
onCloseClick={onClose} onCloseClick={onClose}
negativeButton={{ onClick: onClose }} negativeButton={{ text: "I don't want to", onClick: onClose }}
buttonsAlignment="center"
positiveButton={{ positiveButton={{
text: "Sign Up", text: "Create my account",
loading: isSigningIn, loading: isSigningIn,
disabled: isSigningIn, disabled: isSigningIn,
onClick: () => submit(setError, form, signup, onClose), onClick: () => submit(setError, form, signup, onClose),
}} }}
footer={
<>
<Text textAlign="center" color="gray" mt={3}>
Already have an account?
</Text>
<Button
variant="anchor"
mt={2}
fontSize="body"
onClick={showLogInDialog}
>
Sign in here.
</Button>
</>
}
> >
<Box <Box
mt={1}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter") submit(setError, form, signup, onClose); if (e.key === "Enter") submit(setError, form, signup, onClose);
}} }}

View File

@@ -13,9 +13,10 @@ function TopicDialog(props) {
<Dialog <Dialog
isOpen={true} isOpen={true}
title={props.title} title={props.title}
description="You can create as many topics as you want."
icon={props.icon} icon={props.icon}
positiveButton={{ positiveButton={{
text: "Add", text: "Create topic",
onClick: () => { onClick: () => {
props.onYes(ref.current.value); props.onYes(ref.current.value);
}, },
@@ -37,7 +38,7 @@ function TopicDialog(props) {
export function showTopicDialog(notebook) { export function showTopicDialog(notebook) {
return showDialog((perform) => ( return showDialog((perform) => (
<TopicDialog <TopicDialog
title={"Topic"} title={"Create a Topic"}
icon={Icon.Topic} icon={Icon.Topic}
onNo={() => { onNo={() => {
perform(false); perform(false);

View File

@@ -38,7 +38,7 @@ export const Plus = createIcon(Icons.mdiPlus);
export const Minus = createIcon(Icons.mdiMinus); export const Minus = createIcon(Icons.mdiMinus);
export const Notebook = createIcon(Icons.mdiBookOutline); export const Notebook = createIcon(Icons.mdiBookOutline);
export const ArrowLeft = createIcon(Icons.mdiArrowLeft); export const ArrowLeft = createIcon(Icons.mdiArrowLeft);
export const Move = createIcon(Icons.mdiArrowAll); export const Move = createIcon(Icons.mdiBookPlusMultipleOutline);
export const Topic = createIcon(Icons.mdiFormatTitle); export const Topic = createIcon(Icons.mdiFormatTitle);
export const Alert = createIcon(Icons.mdiAlert); export const Alert = createIcon(Icons.mdiAlert);
export const Vault = createIcon(Icons.mdiShieldOutline); export const Vault = createIcon(Icons.mdiShieldOutline);
@@ -54,6 +54,7 @@ export const Trash = createIcon(Icons.mdiTrashCanOutline);
export const Search = createIcon(Icons.mdiMagnify); export const Search = createIcon(Icons.mdiMagnify);
export const Menu = createIcon(Icons.mdiMenu); export const Menu = createIcon(Icons.mdiMenu);
export const Login = createIcon(Icons.mdiLoginVariant); export const Login = createIcon(Icons.mdiLoginVariant);
export const Signup = createIcon(Icons.mdiAccountPlusOutline);
export const Logout = createIcon(Icons.mdiLogoutVariant); export const Logout = createIcon(Icons.mdiLogoutVariant);
export const FocusMode = createIcon(Icons.mdiFullscreen); export const FocusMode = createIcon(Icons.mdiFullscreen);
export const NormalMode = createIcon(Icons.mdiFullscreenExit); export const NormalMode = createIcon(Icons.mdiFullscreenExit);
@@ -62,7 +63,7 @@ export const Home = createIcon(Icons.mdiHomeOutline);
export const Restore = createIcon(Icons.mdiRecycle); export const Restore = createIcon(Icons.mdiRecycle);
export const Sync = createIcon(Icons.mdiSync); export const Sync = createIcon(Icons.mdiSync);
export const Loading = createIcon(Icons.mdiLoading, true); export const Loading = createIcon(Icons.mdiLoading, true);
export const Export = createIcon(Icons.mdiExport); export const Export = createIcon(Icons.mdiExportVariant);
export const AddToNotebook = createIcon(Icons.mdiBookPlusMultipleOutline); export const AddToNotebook = createIcon(Icons.mdiBookPlusMultipleOutline);
/** Properties Icons */ /** Properties Icons */

View File

@@ -3,7 +3,7 @@ import { Flex, Box, Text } from "rebass";
import * as Icon from "../icons"; import * as Icon from "../icons";
import TimeAgo from "timeago-react"; import TimeAgo from "timeago-react";
import ListItem from "../list-item"; import ListItem from "../list-item";
import { confirm } from "../dialogs/confirm"; import { confirm, showDeleteConfirmation } from "../dialogs/confirm";
import { showMoveNoteDialog } from "../dialogs/movenotedialog"; import { showMoveNoteDialog } from "../dialogs/movenotedialog";
import { store, useStore } from "../../stores/note-store"; import { store, useStore } from "../../stores/note-store";
import { store as editorStore } from "../../stores/editor-store"; import { store as editorStore } from "../../stores/editor-store";
@@ -65,17 +65,31 @@ function menuItems(note, context) {
}, },
{ {
visible: context?.type === "topic", visible: context?.type === "topic",
title: "Remove", title: "Remove from topic",
onClick: async () => { onClick: async () => {
confirm( confirm(Icon.Topic, {
Icon.Topic, title: "Remove Note from Topic",
"Remove from Topic", subtitle: "Are you sure you want to remove the note from this topic?",
"Are you sure you want to remove this note?" yesText: "Remove note",
).then(async (res) => { noText: "Cancel",
message: (
<Text as="span">
<Text as="span" color="primary">
This action does not delete the note.
</Text>{" "}
The note will only be removed from this notebook. You will still
be able to{" "}
<Text as="span" color="primary">
access it from Home and other places.
</Text>
</Text>
),
}).then(async (res) => {
if (res) { if (res) {
console.log(context);
await db.notebooks await db.notebooks
.notebook(context.notebook.id) .notebook(context.value.id)
.topics.topic(context.value) .topics.topic(context.value.topic)
.delete(note.id); .delete(note.id);
store.setContext(context); store.setContext(context);
} }
@@ -95,11 +109,7 @@ function menuItems(note, context) {
}); });
if (!res) return; if (!res) return;
} }
confirm( showDeleteConfirmation("note").then(async (res) => {
Icon.Trash,
"Delete",
"Are you sure you want to delete this note?"
).then(async (res) => {
if (res) { if (res) {
await store.delete(note.id).then(() => showItemDeletedToast(note)); await store.delete(note.id).then(() => showItemDeletedToast(note));
} }

View File

@@ -4,7 +4,7 @@ import ListItem from "../list-item";
import { store } from "../../stores/notebook-store"; import { store } from "../../stores/notebook-store";
import * as Icon from "../icons"; import * as Icon from "../icons";
import { showEditNoteDialog } from "../dialogs/addnotebookdialog"; import { showEditNoteDialog } from "../dialogs/addnotebookdialog";
import { confirm } from "../dialogs/confirm"; import { confirm, showDeleteConfirmation } from "../dialogs/confirm";
import { showItemDeletedToast, showUnpinnedToast } from "../../common/toasts"; import { showItemDeletedToast, showUnpinnedToast } from "../../common/toasts";
const pin = async (notebook, index) => { const pin = async (notebook, index) => {
await store.pin(notebook, index); await store.pin(notebook, index);
@@ -24,11 +24,7 @@ function menuItems(notebook, index) {
title: "Delete", title: "Delete",
color: "red", color: "red",
onClick: () => { onClick: () => {
confirm( showDeleteConfirmation("notebook").then(async (res) => {
Icon.Trash,
"Delete Notebook",
"Are you sure you want to delete this notebook?"
).then(async (res) => {
if (res) { if (res) {
await store await store
.delete(notebook.id, index) .delete(notebook.id, index)

View File

@@ -5,6 +5,7 @@ import { confirm } from "../dialogs/confirm";
import * as Icon from "../icons"; import * as Icon from "../icons";
import { db } from "../../common"; import { db } from "../../common";
import { store } from "../../stores/notebook-store"; import { store } from "../../stores/notebook-store";
import { Text } from "rebass";
const menuItems = (item) => [ const menuItems = (item) => [
{ {
@@ -19,11 +20,24 @@ const menuItems = (item) => [
visible: item.title !== "General", visible: item.title !== "General",
color: "red", color: "red",
onClick: () => { onClick: () => {
confirm( confirm(Icon.Trash, {
Icon.Trash, title: "Delete topic",
"Delete Topic", subtitle: "Are you sure you want to delete this topic?",
"Are you sure you want to delete this topic?" yesText: "Delete topic",
).then(async (res) => { noText: "Cancel",
message: (
<>
This action is{" "}
<Text as="span" color="error">
IRREVERSIBLE
</Text>
. Deleting this topic{" "}
<Text as="span" color="primary">
will not delete the notes contained in it.
</Text>
</>
),
}).then(async (res) => {
if (res) { if (res) {
await db.notebooks.notebook(item.notebookId).topics.delete(item.id); await db.notebooks.notebook(item.notebookId).topics.delete(item.id);
store.setSelectedNotebookTopics(item.notebookId); store.setSelectedNotebookTopics(item.notebookId);

View File

@@ -25,11 +25,24 @@ function menuItems(item, index) {
title: "Delete", title: "Delete",
color: "red", color: "red",
onClick: () => { onClick: () => {
confirm( confirm(Icon.Trash, {
Icon.Trash, title: `Permanently Delete ${toTitleCase(item.itemType)}`,
"Delete", subtitle: `Are you sure you want to permanently delete this ${item.itemType}?`,
`Are you sure you want to permanently delete this item?` yesText: `Delete ${item.itemType}`,
).then(async (res) => { noText: "Cancel",
message: (
<>
This action is{" "}
<Text as="span" color="error">
IRREVERSIBLE
</Text>
. You will{" "}
<Text as="span" color="primary">
not be able to recover this {item.itemType}.
</Text>
</>
),
}).then(async (res) => {
if (res) { if (res) {
await store.delete(item.id, index); await store.delete(item.id, index);
showPermanentDeleteToast(item, index); showPermanentDeleteToast(item, index);
@@ -54,7 +67,7 @@ function TrashItem({ item, index }) {
<Text as="span" mx={1}> <Text as="span" mx={1}>
</Text> </Text>
<Text color="primary">{toTitleCase(item.type)}</Text> <Text color="primary">{toTitleCase(item.itemType)}</Text>
</Flex> </Flex>
} }
menuData={item} menuData={item}

View File

@@ -7,7 +7,7 @@ class FontSizeFactory {
subtitle: 16 * scaleFactor, subtitle: 16 * scaleFactor,
body: 16 * scaleFactor, body: 16 * scaleFactor,
menu: 14 * scaleFactor, menu: 14 * scaleFactor,
subBody: 11 * scaleFactor, subBody: 12 * scaleFactor,
}; };
} }
} }

View File

@@ -36,7 +36,7 @@ const routes = {
<Notes <Notes
context={{ context={{
type: "topic", type: "topic",
value: { id: notebook, topic: topicItem.title }, value: { id: notebook, topic: topic },
}} }}
/> />
} }

View File

@@ -31,7 +31,7 @@ function Topics(props) {
context={{ notebookId }} context={{ notebookId }}
placeholder={Flex} placeholder={Flex}
button={{ button={{
content: "Add more topics", content: "Create a new topic",
onClick: async () => { onClick: async () => {
await showTopicDialog(notebookId); await showTopicDialog(notebookId);
}, },

View File

@@ -5,6 +5,7 @@ import { confirm } from "../components/dialogs/confirm";
import { useStore, store } from "../stores/trash-store"; import { useStore, store } from "../stores/trash-store";
import TrashPlaceholder from "../components/placeholders/trash-placeholder"; import TrashPlaceholder from "../components/placeholders/trash-placeholder";
import { showToast } from "../utils/toast"; import { showToast } from "../utils/toast";
import { Text } from "rebass";
function Trash() { function Trash() {
useEffect(() => store.refresh(), []); useEffect(() => store.refresh(), []);
@@ -19,11 +20,24 @@ function Trash() {
content: "Clear Trash", content: "Clear Trash",
icon: Icon.Trash, icon: Icon.Trash,
onClick: function () { onClick: function () {
confirm( confirm(Icon.Trash, {
Icon.Trash, title: "Clear Trash",
"Clear", subtitle: "Are you sure you want to clear all the trash?",
`This action is irreversible. Are you sure you want to proceed?` yesText: "Clear trash",
).then(async (res) => { noText: "Cancel",
message: (
<>
This action is{" "}
<Text as="span" color="error">
IRREVERSIBLE
</Text>
. You will{" "}
<Text as="span" color="primary">
not be able to recover any of these items.
</Text>
</>
),
}).then(async (res) => {
if (res) { if (res) {
try { try {
await clearTrash(); await clearTrash();