mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-24 04:00:59 +01:00
756 lines
22 KiB
TypeScript
756 lines
22 KiB
TypeScript
/*
|
|
This file is part of the Notesnook project (https://notesnook.com/)
|
|
|
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import ReactDOM from "react-dom";
|
|
import { Dialogs } from "../components/dialogs";
|
|
import ThemeProvider from "../components/theme-provider";
|
|
import qclone from "qclone";
|
|
import { store as notebookStore } from "../stores/notebook-store";
|
|
import { store as tagStore } from "../stores/tag-store";
|
|
import { store as appStore } from "../stores/app-store";
|
|
import { store as editorStore } from "../stores/editor-store";
|
|
import { store as noteStore } from "../stores/note-store";
|
|
import { db } from "./db";
|
|
import { showToast } from "../utils/toast";
|
|
import { Text } from "@theme-ui/components";
|
|
import * as Icon from "../components/icons";
|
|
import Config from "../utils/config";
|
|
import downloadUpdate from "../commands/download-update";
|
|
import installUpdate from "../commands/install-update";
|
|
import { AppVersion, getChangelog } from "../utils/version";
|
|
import { Period } from "../components/dialogs/buy-dialog/types";
|
|
import { FeatureKeys } from "../components/dialogs/feature-dialog";
|
|
import { AuthenticatorType } from "../components/dialogs/mfa/types";
|
|
import { Suspense } from "react";
|
|
import { Reminder } from "@notesnook/core/collections/reminders";
|
|
import { ConfirmDialogProps } from "../components/dialogs/confirm";
|
|
import { getFormattedDate } from "../utils/time";
|
|
|
|
type DialogTypes = typeof Dialogs;
|
|
type DialogIds = keyof DialogTypes;
|
|
export type Perform<T = boolean> = (result: T) => void;
|
|
type RenderDialog<TId extends DialogIds, TReturnType> = (
|
|
dialog: DialogTypes[TId],
|
|
perform: (result: TReturnType) => void
|
|
) => JSX.Element;
|
|
|
|
let openDialogs: Partial<Record<DialogIds, boolean>> = {};
|
|
function showDialog<TId extends DialogIds, TReturnType>(
|
|
id: TId,
|
|
render: RenderDialog<TId, TReturnType>
|
|
): Promise<TReturnType> {
|
|
return new Promise((resolve) => {
|
|
if (openDialogs[id]) return false;
|
|
|
|
const container = document.createElement("div");
|
|
container.id = id;
|
|
|
|
const perform = (result: TReturnType) => {
|
|
openDialogs[id] = false;
|
|
ReactDOM.unmountComponentAtNode(container);
|
|
container.remove();
|
|
resolve(result);
|
|
};
|
|
const PropDialog = () => render(Dialogs[id], perform);
|
|
ReactDOM.render(
|
|
<ThemeProvider>
|
|
<Suspense fallback={<div />}>
|
|
<PropDialog />
|
|
</Suspense>
|
|
</ThemeProvider>,
|
|
container,
|
|
() => (openDialogs[id] = true)
|
|
);
|
|
});
|
|
}
|
|
|
|
export function closeOpenedDialog() {
|
|
openDialogs = {};
|
|
const dialogs = document.querySelectorAll(
|
|
".ReactModalPortal,[data-react-modal-body-trap]"
|
|
);
|
|
dialogs.forEach((elem) => elem.remove());
|
|
}
|
|
|
|
export function showAddTagsDialog(noteIds: string[]) {
|
|
return showDialog("AddTagsDialog", (Dialog, perform) => (
|
|
<Dialog onClose={(res) => perform(res)} noteIds={noteIds} />
|
|
));
|
|
}
|
|
|
|
export function showAddNotebookDialog() {
|
|
return showDialog("AddNotebookDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
isOpen={true}
|
|
onDone={async (nb: Record<string, unknown>) => {
|
|
// add the notebook to db
|
|
const notebook = await db.notebooks?.add({ ...nb });
|
|
if (!notebook) return perform(false);
|
|
|
|
notebookStore.refresh();
|
|
|
|
showToast("success", "Notebook added successfully!");
|
|
perform(true);
|
|
}}
|
|
onClose={() => {
|
|
perform(false);
|
|
}}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showEditNotebookDialog(notebookId: string) {
|
|
const notebook = db.notebooks?.notebook(notebookId)?.data;
|
|
if (!notebook) return false;
|
|
return showDialog("AddNotebookDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
isOpen={true}
|
|
notebook={notebook}
|
|
edit={true}
|
|
onDone={async (nb: Record<string, unknown>, deletedTopics: string[]) => {
|
|
// we remove the topics from notebook
|
|
// beforehand so we can add them manually, later
|
|
const topics = qclone(nb.topics);
|
|
nb.topics = [];
|
|
|
|
const notebookId = await db.notebooks?.add(nb);
|
|
|
|
// add or delete topics as required
|
|
const notebookTopics = db.notebooks?.notebook(notebookId).topics;
|
|
if (notebookTopics) {
|
|
await notebookTopics.add(...topics);
|
|
await notebookTopics.delete(...deletedTopics);
|
|
}
|
|
|
|
notebookStore.refresh();
|
|
noteStore.refresh();
|
|
appStore.refreshNavItems();
|
|
|
|
showToast("success", "Notebook edited successfully!");
|
|
perform(true);
|
|
}}
|
|
onClose={() => {
|
|
perform(false);
|
|
}}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showBuyDialog(plan?: Period, couponCode?: string) {
|
|
return showDialog("BuyDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
plan={plan}
|
|
couponCode={couponCode}
|
|
onClose={() => perform(false)}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function confirm<TCheckId extends string>(
|
|
props: Omit<ConfirmDialogProps<TCheckId>, "onClose">
|
|
) {
|
|
return showDialog<"Confirm", false | Record<TCheckId, boolean>>(
|
|
"Confirm",
|
|
(Dialog, perform) => (
|
|
<Dialog {...props} onClose={(result) => perform(result)} />
|
|
)
|
|
);
|
|
}
|
|
|
|
export function showPromptDialog(props: {
|
|
title: string;
|
|
description?: string;
|
|
defaultValue?: string;
|
|
}) {
|
|
return showDialog<"Prompt", string | null>("Prompt", (Dialog, perform) => (
|
|
<Dialog
|
|
{...props}
|
|
onClose={() => perform(null)}
|
|
onSave={(text) => {
|
|
perform(text);
|
|
}}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showEmailChangeDialog() {
|
|
return showDialog<"EmailChangeDialog", string | null>(
|
|
"EmailChangeDialog",
|
|
(Dialog, perform) => <Dialog onClose={() => perform(null)} />
|
|
);
|
|
}
|
|
|
|
export function showLanguageSelectorDialog() {
|
|
return showDialog<"LanguageSelectorDialog", string | null>(
|
|
"LanguageSelectorDialog",
|
|
(Dialog, perform) => <Dialog onClose={() => perform(null)} />
|
|
);
|
|
}
|
|
|
|
export function showToolbarConfigDialog() {
|
|
return showDialog<"ToolbarConfigDialog", string | null>(
|
|
"ToolbarConfigDialog",
|
|
(Dialog, perform) => <Dialog onClose={() => perform(null)} />
|
|
);
|
|
}
|
|
|
|
export function showError(title: string, message: string) {
|
|
return confirm({ title, message, positiveButtonText: "Okay" });
|
|
}
|
|
|
|
export function showMultiDeleteConfirmation(length: number) {
|
|
return confirm({
|
|
title: `Delete ${length} items?`,
|
|
message: `These items will be **kept in your Trash for ${
|
|
db.settings?.getTrashCleanupInterval() || 7
|
|
} days** after which they will be permanently deleted.`,
|
|
positiveButtonText: "Yes",
|
|
negativeButtonText: "No"
|
|
});
|
|
}
|
|
|
|
export function showMultiPermanentDeleteConfirmation(length: number) {
|
|
return confirm({
|
|
title: `Permanently delete ${length} items?`,
|
|
message:
|
|
"These items will be **permanently deleted**. This is IRREVERSIBLE.",
|
|
positiveButtonText: "Yes",
|
|
negativeButtonText: "No"
|
|
});
|
|
}
|
|
|
|
export function showLogoutConfirmation() {
|
|
return confirm({
|
|
title: `Logout?`,
|
|
message:
|
|
"Logging out will delete all local data and reset the app. Make sure you have synced your data before logging out.",
|
|
positiveButtonText: "Yes",
|
|
negativeButtonText: "No"
|
|
});
|
|
}
|
|
|
|
export function showClearSessionsConfirmation() {
|
|
return confirm({
|
|
title: `Logout from other devices?`,
|
|
message:
|
|
"All other logged-in devices will be forced to logout stopping sync. Use with care lest you lose important notes.",
|
|
positiveButtonText: "Yes",
|
|
negativeButtonText: "No"
|
|
});
|
|
}
|
|
|
|
export function showAccountLoggedOutNotice(reason?: string) {
|
|
return confirm({
|
|
title: "You were logged out",
|
|
message: reason,
|
|
negativeButtonText: "Okay"
|
|
});
|
|
}
|
|
|
|
export function showAppUpdatedNotice(
|
|
version: AppVersion & { changelog?: string }
|
|
) {
|
|
return confirm({
|
|
title: `Welcome to v${version.formatted}`,
|
|
message: `## Changelog:
|
|
|
|
\`\`\`
|
|
${version.changelog || "No change log."}
|
|
\`\`\`
|
|
`,
|
|
positiveButtonText: `Continue`
|
|
});
|
|
}
|
|
|
|
export function showEmailVerificationDialog() {
|
|
return showDialog("EmailVerificationDialog", (Dialog, perform) => (
|
|
<Dialog onCancel={() => perform(false)} />
|
|
));
|
|
}
|
|
|
|
export function showMigrationDialog() {
|
|
return showDialog("MigrationDialog", (Dialog, perform) => (
|
|
<Dialog onClose={() => perform(false)} />
|
|
));
|
|
}
|
|
|
|
type LoadingDialogProps<T> = {
|
|
title: string;
|
|
message?: string;
|
|
subtitle: string;
|
|
action: () => T | Promise<T>;
|
|
};
|
|
export function showLoadingDialog<T>(dialogData: LoadingDialogProps<T>) {
|
|
const { title, message, subtitle, action } = dialogData;
|
|
return showDialog<"LoadingDialog", T | boolean>(
|
|
"LoadingDialog",
|
|
(Dialog, perform) => (
|
|
<Dialog
|
|
title={title}
|
|
description={subtitle}
|
|
message={message}
|
|
action={action}
|
|
onClose={(e) => perform(e as T | boolean)}
|
|
/>
|
|
)
|
|
);
|
|
}
|
|
|
|
type ProgressDialogProps = {
|
|
title: string;
|
|
subtitle: string;
|
|
action: (...args: never[]) => void;
|
|
};
|
|
export function showProgressDialog<T>(dialogData: ProgressDialogProps) {
|
|
const { title, subtitle, action } = dialogData;
|
|
return showDialog<"ProgressDialog", T>(
|
|
"ProgressDialog",
|
|
(Dialog, perform) => (
|
|
<Dialog
|
|
title={title}
|
|
subtitle={subtitle}
|
|
action={action}
|
|
onDone={(e: T) => perform(e)}
|
|
/>
|
|
)
|
|
);
|
|
}
|
|
|
|
export function showMoveNoteDialog(noteIds: string[]) {
|
|
return showDialog("MoveDialog", (Dialog, perform) => (
|
|
<Dialog noteIds={noteIds} onClose={(res: boolean) => perform(res)} />
|
|
));
|
|
}
|
|
|
|
export function showBillingHistoryDialog() {
|
|
return showDialog("BillingHistoryDialog", (Dialog, perform) => (
|
|
<Dialog onClose={(res: boolean) => perform(res)} />
|
|
));
|
|
}
|
|
|
|
function getDialogData(type: string) {
|
|
switch (type) {
|
|
case "create_vault":
|
|
return {
|
|
title: "Create your vault",
|
|
subtitle: "A vault stores your notes in a password-encrypted storage.",
|
|
positiveButtonText: "Create vault"
|
|
};
|
|
case "clear_vault":
|
|
return {
|
|
title: "Clear your vault",
|
|
subtitle:
|
|
"Enter vault password to unlock and remove all notes from the vault.",
|
|
positiveButtonText: "Clear vault"
|
|
};
|
|
case "delete_vault":
|
|
return {
|
|
title: "Delete your vault",
|
|
subtitle: "Enter your account password to delete your vault.",
|
|
positiveButtonText: "Delete vault",
|
|
checks: [
|
|
{ key: "deleteAllLockedNotes", title: "Delete all locked notes?" }
|
|
]
|
|
};
|
|
case "lock_note":
|
|
return {
|
|
title: "Lock note",
|
|
subtitle: "Please open your vault to encrypt & lock this note.",
|
|
positiveButtonText: "Lock note"
|
|
};
|
|
case "unlock_note":
|
|
return {
|
|
title: "Unlock note",
|
|
subtitle: "Your note will be unencrypted and removed from the vault.",
|
|
positiveButtonText: "Unlock note"
|
|
};
|
|
case "unlock_and_delete_note":
|
|
return {
|
|
title: "Delete note",
|
|
subtitle: "Please unlock this note to move it to trash.",
|
|
positiveButtonText: "Unlock & delete"
|
|
};
|
|
case "change_password":
|
|
return {
|
|
title: "Change vault password",
|
|
subtitle:
|
|
"All locked notes will be re-encrypted with the new password.",
|
|
positiveButtonText: "Change password"
|
|
};
|
|
case "ask_vault_password":
|
|
return {
|
|
title: "Unlock vault",
|
|
subtitle: "Please enter your vault password to continue.",
|
|
positiveButtonText: "Unlock"
|
|
};
|
|
case "ask_backup_password":
|
|
return {
|
|
title: "Encrypted backup",
|
|
subtitle:
|
|
"Please enter the password to decrypt and restore this backup.",
|
|
positiveButtonText: "Restore"
|
|
};
|
|
case "change_account_password":
|
|
return {
|
|
title: "Change account password",
|
|
subtitle: (
|
|
<>
|
|
All your data will be re-encrypted and synced with the new password.
|
|
<Text as="div" mt={1} p={1} bg="errorBg" sx={{ color: "error" }}>
|
|
<Text as="p" my={0} sx={{ color: "inherit" }}>
|
|
It is recommended that you <b>log out from all other devices</b>{" "}
|
|
before continuing.
|
|
</Text>
|
|
<Text as="p" my={0} mt={1} sx={{ color: "inherit" }}>
|
|
If this process is interrupted, there is a high chance of data
|
|
corruption so{" "}
|
|
<b>please do NOT shut down your device or close your browser</b>{" "}
|
|
until this process completes.
|
|
</Text>
|
|
</Text>
|
|
</>
|
|
),
|
|
positiveButtonText: "Change password"
|
|
};
|
|
case "verify_account":
|
|
return {
|
|
title: "Verify it's you",
|
|
subtitle: "Enter your account password to proceed.",
|
|
positiveButtonText: "Verify"
|
|
};
|
|
case "delete_account":
|
|
return {
|
|
title: "Delete your account",
|
|
subtitle: (
|
|
<Text as="span" sx={{ color: "error" }}>
|
|
All your data will be permanently deleted with{" "}
|
|
<b>no way of recovery</b>. Proceed with caution.
|
|
</Text>
|
|
),
|
|
positiveButtonText: "Delete Account"
|
|
};
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
export function showPasswordDialog(
|
|
type: string,
|
|
validate: (password: string) => boolean
|
|
) {
|
|
const { title, subtitle, positiveButtonText, checks } = getDialogData(type);
|
|
return showDialog("PasswordDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
type={type}
|
|
title={title}
|
|
subtitle={subtitle}
|
|
checks={checks}
|
|
positiveButtonText={positiveButtonText}
|
|
validate={validate}
|
|
onClose={() => perform(false)}
|
|
onDone={() => perform(true)}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showRecoveryKeyDialog() {
|
|
return showDialog("RecoveryKeyDialog", (Dialog, perform) => (
|
|
<Dialog onDone={() => perform(true)} />
|
|
));
|
|
}
|
|
|
|
export function showCreateTopicDialog() {
|
|
return showDialog("ItemDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
title={"Create topic"}
|
|
subtitle={"You can create as many topics as you want."}
|
|
onClose={() => {
|
|
perform(false);
|
|
}}
|
|
onAction={async (topic: Record<string, unknown>) => {
|
|
if (!topic) return;
|
|
const notebook = notebookStore.get().selectedNotebook;
|
|
await db.notebooks?.notebook(notebook.id).topics.add(topic);
|
|
notebookStore.setSelectedNotebook(notebook.id);
|
|
showToast("success", "Topic created!");
|
|
perform(true);
|
|
}}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showEditTopicDialog(notebookId: string, topicId: string) {
|
|
const topic = db.notebooks?.notebook(notebookId)?.topics?.topic(topicId)
|
|
?._topic as Record<string, unknown> | undefined;
|
|
if (!topic) return;
|
|
return showDialog("ItemDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
title={"Edit topic"}
|
|
subtitle={`You are editing "${topic.title}" topic.`}
|
|
defaultValue={topic.title}
|
|
icon={Icon.Topic}
|
|
item={topic}
|
|
onClose={() => perform(false)}
|
|
onAction={async (t: string) => {
|
|
await db.notebooks
|
|
?.notebook(topic.notebookId as string)
|
|
.topics.add({ ...topic, title: t });
|
|
notebookStore.setSelectedNotebook(topic.notebookId);
|
|
appStore.refreshNavItems();
|
|
showToast("success", "Topic edited!");
|
|
perform(true);
|
|
}}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showCreateTagDialog() {
|
|
return showDialog("ItemDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
title={"Create tag"}
|
|
subtitle={"You can create as many tags as you want."}
|
|
onClose={() => {
|
|
perform(false);
|
|
}}
|
|
onAction={async (title: string) => {
|
|
if (!title) return showToast("error", "Tag title cannot be empty.");
|
|
try {
|
|
await db.tags?.add(title);
|
|
showToast("success", "Tag created!");
|
|
tagStore.refresh();
|
|
perform(true);
|
|
} catch (e) {
|
|
if (e instanceof Error) showToast("error", e.message);
|
|
}
|
|
}}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showEditTagDialog(tagId: string) {
|
|
const tag = db.tags?.tag(tagId);
|
|
if (!tag) return;
|
|
return showDialog("ItemDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
title={"Edit tag"}
|
|
subtitle={`You are editing #${db.tags?.alias(tag.id)}.`}
|
|
defaultValue={db.tags?.alias(tag.id)}
|
|
item={tag}
|
|
onClose={() => perform(false)}
|
|
onAction={async (title: string) => {
|
|
if (!title) return;
|
|
await db.tags?.rename(tagId, title);
|
|
showToast("success", "Tag edited!");
|
|
tagStore.refresh();
|
|
editorStore.refreshTags();
|
|
noteStore.refresh();
|
|
appStore.refreshNavItems();
|
|
perform(true);
|
|
}}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showRenameColorDialog(colorId: string) {
|
|
const color = db.colors?.tag(colorId);
|
|
if (!color) return;
|
|
return showDialog("ItemDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
title={"Rename color"}
|
|
subtitle={`You are renaming color ${db.colors?.alias(color.id)}.`}
|
|
item={color}
|
|
defaultValue={db.colors?.alias(color.id)}
|
|
onClose={() => perform(false)}
|
|
onAction={async (title: string) => {
|
|
if (!title) return;
|
|
await db.colors?.rename(colorId, title);
|
|
showToast("success", "Color renamed!");
|
|
appStore.refreshNavItems();
|
|
perform(true);
|
|
}}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showFeatureDialog(featureName: FeatureKeys) {
|
|
return showDialog("FeatureDialog", (Dialog, perform) => (
|
|
<Dialog featureName={featureName} onClose={(res) => perform(res)} />
|
|
));
|
|
}
|
|
|
|
export function showReminderDialog(reminderKey: string) {
|
|
if (Config.get(reminderKey, false)) return;
|
|
|
|
return showDialog("ReminderDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
reminderKey={reminderKey}
|
|
onClose={(res: boolean) => {
|
|
Config.set(reminderKey, true);
|
|
perform(res);
|
|
}}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showReminderPreviewDialog(reminder: Reminder) {
|
|
return showDialog("ReminderPreviewDialog", (Dialog, perform) => (
|
|
<Dialog reminder={reminder} onClose={perform} />
|
|
));
|
|
}
|
|
|
|
export function showTrackingDetailsDialog() {
|
|
return showDialog("TrackingDetailsDialog", (Dialog, perform) => (
|
|
<Dialog onClose={(res: boolean) => perform(res)} />
|
|
));
|
|
}
|
|
|
|
export function showAddReminderDialog(noteId?: string) {
|
|
return showDialog("AddReminderDialog", (Dialog, perform) => (
|
|
<Dialog onClose={(res: boolean) => perform(res)} noteId={noteId} />
|
|
));
|
|
}
|
|
|
|
export function showEditReminderDialog(reminderId: string) {
|
|
return showDialog("AddReminderDialog", (Dialog, perform) => (
|
|
<Dialog onClose={(res: boolean) => perform(res)} reminderId={reminderId} />
|
|
));
|
|
}
|
|
|
|
export function showAnnouncementDialog(announcement: { id: string }) {
|
|
return showDialog("AnnouncementDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
announcement={announcement}
|
|
onClose={(res: boolean) => perform(res)}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showIssueDialog() {
|
|
return showDialog("IssueDialog", (Dialog, perform) => (
|
|
<Dialog
|
|
onClose={(res: boolean) => {
|
|
perform(res);
|
|
}}
|
|
/>
|
|
));
|
|
}
|
|
|
|
export function showImportDialog() {
|
|
return showDialog("ImportDialog", (Dialog, perform) => (
|
|
<Dialog onClose={(res: boolean) => perform(res)} />
|
|
));
|
|
}
|
|
|
|
export function showMultifactorDialog(primaryMethod?: AuthenticatorType) {
|
|
return showDialog("MultifactorDialog", (Dialog, perform) => (
|
|
<Dialog onClose={(res) => perform(res)} primaryMethod={primaryMethod} />
|
|
));
|
|
}
|
|
|
|
export function show2FARecoveryCodesDialog(primaryMethod: AuthenticatorType) {
|
|
return showDialog("RecoveryCodesDialog", (Dialog, perform) => (
|
|
<Dialog onClose={(res) => perform(res)} primaryMethod={primaryMethod} />
|
|
));
|
|
}
|
|
|
|
export function showAttachmentsDialog() {
|
|
return showDialog("AttachmentsDialog", (Dialog, perform) => (
|
|
<Dialog onClose={(res: boolean) => perform(res)} />
|
|
));
|
|
}
|
|
|
|
export function showOnboardingDialog(type: string) {
|
|
if (!type) return;
|
|
return showDialog("OnboardingDialog", (Dialog, perform) => (
|
|
<Dialog type={type} onClose={(res: boolean) => perform(res)} />
|
|
));
|
|
}
|
|
|
|
export async function showInvalidSystemTimeDialog({
|
|
serverTime,
|
|
localTime
|
|
}: {
|
|
serverTime: number;
|
|
localTime: number;
|
|
}) {
|
|
const result = await confirm({
|
|
title: "Your system clock is out of sync",
|
|
subtitle:
|
|
"Please correct your system date & time and reload the app to avoid syncing issues.",
|
|
message: `Server time: ${getFormattedDate(serverTime)}
|
|
Local time: ${getFormattedDate(localTime)}
|
|
Please sync your system time with [https://time.is](https://time.is).`,
|
|
positiveButtonText: "Reload app"
|
|
});
|
|
if (result) window.location.reload();
|
|
}
|
|
|
|
export async function showUpdateAvailableNotice({
|
|
version
|
|
}: {
|
|
version: string;
|
|
}) {
|
|
const changelog = await getChangelog(version);
|
|
|
|
return showUpdateDialog({
|
|
title: `New version available`,
|
|
subtitle: `v${version} is available for download`,
|
|
changelog,
|
|
action: { text: `Update now`, onClick: () => downloadUpdate() }
|
|
});
|
|
}
|
|
|
|
export async function showUpdateReadyNotice({ version }: { version: string }) {
|
|
const changelog = await getChangelog(version);
|
|
return await showUpdateDialog({
|
|
title: `Update ready for installation`,
|
|
subtitle: `v${version} is ready to be installed.`,
|
|
changelog,
|
|
action: {
|
|
text: "Install now",
|
|
onClick: () => installUpdate()
|
|
}
|
|
});
|
|
}
|
|
|
|
type UpdateDialogProps = {
|
|
title: string;
|
|
subtitle: string;
|
|
changelog: string;
|
|
action: {
|
|
text: string;
|
|
onClick: () => void;
|
|
};
|
|
};
|
|
async function showUpdateDialog({
|
|
title,
|
|
subtitle,
|
|
changelog,
|
|
action
|
|
}: UpdateDialogProps) {
|
|
const result = await confirm({
|
|
title,
|
|
subtitle,
|
|
message: changelog,
|
|
width: 500,
|
|
positiveButtonText: action.text
|
|
});
|
|
if (result && action.onClick) action.onClick();
|
|
}
|