fix: Separate toasts storage to own MobX store (#2339)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@ -12,13 +12,15 @@ import LoadingIndicator from "components/LoadingIndicator";
|
||||
import NudeButton from "components/NudeButton";
|
||||
import useDebouncedCallback from "hooks/useDebouncedCallback";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
collection: Collection,
|
||||
|};
|
||||
|
||||
function CollectionDescription({ collection }: Props) {
|
||||
const { collections, ui, policies } = useStores();
|
||||
const { collections, policies } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, setExpanded] = React.useState(false);
|
||||
const [isEditing, setEditing] = React.useState(false);
|
||||
@ -53,7 +55,7 @@ function CollectionDescription({ collection }: Props) {
|
||||
});
|
||||
setDirty(false);
|
||||
} catch (err) {
|
||||
ui.showToast(
|
||||
showToast(
|
||||
t("Sorry, an error occurred saving the collection", {
|
||||
type: "error",
|
||||
})
|
||||
|
@ -10,6 +10,7 @@ import ErrorBoundary from "components/ErrorBoundary";
|
||||
import Tooltip from "components/Tooltip";
|
||||
import embeds from "../embeds";
|
||||
import useMediaQuery from "hooks/useMediaQuery";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import { type Theme } from "types";
|
||||
import { isModKey } from "utils/keyboard";
|
||||
import { uploadFile } from "utils/uploadFile";
|
||||
@ -58,8 +59,9 @@ type PropsWithRef = Props & {
|
||||
};
|
||||
|
||||
function Editor(props: PropsWithRef) {
|
||||
const { id, ui, shareId, history } = props;
|
||||
const { id, shareId, history } = props;
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToasts();
|
||||
const isPrinting = useMediaQuery("print");
|
||||
|
||||
const onUploadImage = React.useCallback(
|
||||
@ -106,11 +108,9 @@ function Editor(props: PropsWithRef) {
|
||||
|
||||
const onShowToast = React.useCallback(
|
||||
(message: string) => {
|
||||
if (ui) {
|
||||
ui.showToast(message);
|
||||
}
|
||||
showToast(message);
|
||||
},
|
||||
[ui]
|
||||
[showToast]
|
||||
);
|
||||
|
||||
const dictionary = React.useMemo(() => {
|
||||
|
@ -14,6 +14,8 @@ import Header from "./Header";
|
||||
import PlaceholderCollections from "./PlaceholderCollections";
|
||||
import SidebarLink from "./SidebarLink";
|
||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
onCreateCollection: () => void,
|
||||
};
|
||||
@ -22,6 +24,7 @@ function Collections({ onCreateCollection }: Props) {
|
||||
const [isFetching, setFetching] = React.useState(false);
|
||||
const [fetchError, setFetchError] = React.useState();
|
||||
const { ui, policies, documents, collections } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const isPreloaded: boolean = !!collections.orderedData.length;
|
||||
const { t } = useTranslation();
|
||||
const team = useCurrentTeam();
|
||||
@ -38,7 +41,7 @@ function Collections({ onCreateCollection }: Props) {
|
||||
setFetching(true);
|
||||
await collections.fetchPage({ limit: 100 });
|
||||
} catch (error) {
|
||||
ui.showToast(
|
||||
showToast(
|
||||
t("Collections could not be loaded, please reload the app"),
|
||||
{
|
||||
type: "error",
|
||||
@ -51,7 +54,7 @@ function Collections({ onCreateCollection }: Props) {
|
||||
}
|
||||
}
|
||||
load();
|
||||
}, [collections, isFetching, ui, fetchError, t]);
|
||||
}, [collections, isFetching, showToast, fetchError, t]);
|
||||
|
||||
const [{ isCollectionDropping }, dropToReorderCollection] = useDrop({
|
||||
accept: "collection",
|
||||
|
@ -7,6 +7,7 @@ import styled, { css } from "styled-components";
|
||||
import LoadingIndicator from "components/LoadingIndicator";
|
||||
import useImportDocument from "hooks/useImportDocument";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
children: React.Node,
|
||||
@ -18,7 +19,8 @@ type Props = {|
|
||||
|
||||
function DropToImport({ disabled, children, collectionId, documentId }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { ui, documents, policies } = useStores();
|
||||
const { documents, policies } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { handleFiles, isImporting } = useImportDocument(
|
||||
collectionId,
|
||||
documentId
|
||||
@ -27,11 +29,11 @@ function DropToImport({ disabled, children, collectionId, documentId }: Props) {
|
||||
const can = policies.abilities(collectionId);
|
||||
|
||||
const handleRejection = React.useCallback(() => {
|
||||
ui.showToast(
|
||||
showToast(
|
||||
t("Document not supported – try Markdown, Plain text, HTML, or Word"),
|
||||
{ type: "error" }
|
||||
);
|
||||
}, [t, ui]);
|
||||
}, [t, showToast]);
|
||||
|
||||
if (disabled || !can.update) {
|
||||
return children;
|
||||
|
@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
onSubmit: (title: string) => Promise<void>,
|
||||
@ -13,8 +13,7 @@ function EditableTitle({ title, onSubmit, canUpdate }: Props) {
|
||||
const [isEditing, setIsEditing] = React.useState(false);
|
||||
const [originalValue, setOriginalValue] = React.useState(title);
|
||||
const [value, setValue] = React.useState(title);
|
||||
const { ui } = useStores();
|
||||
|
||||
const { showToast } = useToasts();
|
||||
React.useEffect(() => {
|
||||
setValue(title);
|
||||
}, [title]);
|
||||
@ -52,13 +51,13 @@ function EditableTitle({ title, onSubmit, canUpdate }: Props) {
|
||||
setOriginalValue(value);
|
||||
} catch (error) {
|
||||
setValue(originalValue);
|
||||
ui.showToast(error.message, {
|
||||
showToast(error.message, {
|
||||
type: "error",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}, [ui, originalValue, value, onSubmit]);
|
||||
}, [originalValue, showToast, value, onSubmit]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -11,7 +11,7 @@ import DocumentsStore from "stores/DocumentsStore";
|
||||
import GroupsStore from "stores/GroupsStore";
|
||||
import MembershipsStore from "stores/MembershipsStore";
|
||||
import PoliciesStore from "stores/PoliciesStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import ToastsStore from "stores/ToastsStore";
|
||||
import ViewsStore from "stores/ViewsStore";
|
||||
import { getVisibilityListener, getPageVisible } from "utils/pageVisibility";
|
||||
|
||||
@ -27,7 +27,7 @@ type Props = {
|
||||
policies: PoliciesStore,
|
||||
views: ViewsStore,
|
||||
auth: AuthStore,
|
||||
ui: UiStore,
|
||||
toasts: ToastsStore,
|
||||
};
|
||||
|
||||
@observer
|
||||
@ -72,7 +72,7 @@ class SocketProvider extends React.Component<Props> {
|
||||
|
||||
const {
|
||||
auth,
|
||||
ui,
|
||||
toasts,
|
||||
documents,
|
||||
collections,
|
||||
groups,
|
||||
@ -113,7 +113,7 @@ class SocketProvider extends React.Component<Props> {
|
||||
|
||||
this.socket.on("unauthorized", (err) => {
|
||||
this.socket.authenticated = false;
|
||||
ui.showToast(err.message, {
|
||||
toasts.showToast(err.message, {
|
||||
type: "error",
|
||||
});
|
||||
throw err;
|
||||
@ -338,7 +338,7 @@ class SocketProvider extends React.Component<Props> {
|
||||
|
||||
export default inject(
|
||||
"auth",
|
||||
"ui",
|
||||
"toasts",
|
||||
"documents",
|
||||
"collections",
|
||||
"groups",
|
||||
|
@ -6,15 +6,15 @@ import Toast from "components/Toast";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
function Toasts() {
|
||||
const { ui } = useStores();
|
||||
const { toasts } = useStores();
|
||||
|
||||
return (
|
||||
<List>
|
||||
{ui.orderedToasts.map((toast) => (
|
||||
{toasts.orderedData.map((toast) => (
|
||||
<Toast
|
||||
key={toast.id}
|
||||
toast={toast}
|
||||
onRequestClose={() => ui.removeToast(toast.id)}
|
||||
onRequestClose={() => toasts.hideToast(toast.id)}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
|
@ -4,6 +4,7 @@ import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
let importingLock = false;
|
||||
|
||||
@ -11,7 +12,8 @@ export default function useImportDocument(
|
||||
collectionId: string,
|
||||
documentId?: string
|
||||
): {| handleFiles: (files: File[]) => Promise<void>, isImporting: boolean |} {
|
||||
const { documents, ui } = useStores();
|
||||
const { documents } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const [isImporting, setImporting] = React.useState(false);
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
@ -51,7 +53,7 @@ export default function useImportDocument(
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
ui.showToast(`${t("Could not import file")}. ${err.message}`, {
|
||||
showToast(`${t("Could not import file")}. ${err.message}`, {
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
@ -59,7 +61,7 @@ export default function useImportDocument(
|
||||
importingLock = false;
|
||||
}
|
||||
},
|
||||
[t, ui, documents, history, collectionId, documentId]
|
||||
[t, documents, history, showToast, collectionId, documentId]
|
||||
);
|
||||
|
||||
return {
|
||||
|
8
app/hooks/useToasts.js
Normal file
8
app/hooks/useToasts.js
Normal file
@ -0,0 +1,8 @@
|
||||
// @flow
|
||||
import useStores from "./useStores";
|
||||
|
||||
export default function useToasts() {
|
||||
const { toasts } = useStores();
|
||||
|
||||
return { showToast: toasts.showToast, hideToast: toasts.hideToast };
|
||||
}
|
@ -15,6 +15,7 @@ import OverflowMenuButton from "components/ContextMenu/OverflowMenuButton";
|
||||
import Template, { filterTemplateItems } from "components/ContextMenu/Template";
|
||||
import Modal from "components/Modal";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import getDataTransferFiles from "utils/getDataTransferFiles";
|
||||
import { newDocumentUrl } from "utils/routeHelpers";
|
||||
|
||||
@ -37,7 +38,8 @@ function CollectionMenu({
|
||||
}: Props) {
|
||||
const menu = useMenuState({ modal, placement });
|
||||
const [renderModals, setRenderModals] = React.useState(false);
|
||||
const { ui, documents, policies } = useStores();
|
||||
const { documents, policies } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
|
||||
@ -99,14 +101,14 @@ function CollectionMenu({
|
||||
});
|
||||
history.push(document.url);
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, {
|
||||
showToast(err.message, {
|
||||
type: "error",
|
||||
});
|
||||
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[history, ui, collection.id, documents]
|
||||
[history, showToast, collection.id, documents]
|
||||
);
|
||||
|
||||
const can = policies.abilities(collection.id);
|
||||
|
@ -18,6 +18,7 @@ import Template from "components/ContextMenu/Template";
|
||||
import Flex from "components/Flex";
|
||||
import Modal from "components/Modal";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import getDataTransferFiles from "utils/getDataTransferFiles";
|
||||
import {
|
||||
documentHistoryUrl,
|
||||
@ -51,7 +52,8 @@ function DocumentMenu({
|
||||
onOpen,
|
||||
onClose,
|
||||
}: Props) {
|
||||
const { policies, collections, ui, documents } = useStores();
|
||||
const { policies, collections, documents } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const menu = useMenuState({
|
||||
modal,
|
||||
unstable_preventOverflow: true,
|
||||
@ -83,33 +85,33 @@ function DocumentMenu({
|
||||
|
||||
// when duplicating, go straight to the duplicated document content
|
||||
history.push(duped.url);
|
||||
ui.showToast(t("Document duplicated"), { type: "success" });
|
||||
showToast(t("Document duplicated"), { type: "success" });
|
||||
},
|
||||
[ui, t, history, document]
|
||||
[t, history, showToast, document]
|
||||
);
|
||||
|
||||
const handleArchive = React.useCallback(
|
||||
async (ev: SyntheticEvent<>) => {
|
||||
await document.archive();
|
||||
ui.showToast(t("Document archived"), { type: "success" });
|
||||
showToast(t("Document archived"), { type: "success" });
|
||||
},
|
||||
[ui, t, document]
|
||||
[showToast, t, document]
|
||||
);
|
||||
|
||||
const handleRestore = React.useCallback(
|
||||
async (ev: SyntheticEvent<>, options?: { collectionId: string }) => {
|
||||
await document.restore(options);
|
||||
ui.showToast(t("Document restored"), { type: "success" });
|
||||
showToast(t("Document restored"), { type: "success" });
|
||||
},
|
||||
[ui, t, document]
|
||||
[showToast, t, document]
|
||||
);
|
||||
|
||||
const handleUnpublish = React.useCallback(
|
||||
async (ev: SyntheticEvent<>) => {
|
||||
await document.unpublish();
|
||||
ui.showToast(t("Document unpublished"), { type: "success" });
|
||||
showToast(t("Document unpublished"), { type: "success" });
|
||||
},
|
||||
[ui, t, document]
|
||||
[showToast, t, document]
|
||||
);
|
||||
|
||||
const handlePrint = React.useCallback((ev: SyntheticEvent<>) => {
|
||||
@ -181,14 +183,14 @@ function DocumentMenu({
|
||||
);
|
||||
history.push(importedDocument.url);
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, {
|
||||
showToast(err.message, {
|
||||
type: "error",
|
||||
});
|
||||
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[history, ui, collection, documents, document.id]
|
||||
[history, showToast, collection, documents, document.id]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -11,7 +11,7 @@ import MenuItem from "components/ContextMenu/MenuItem";
|
||||
import OverflowMenuButton from "components/ContextMenu/OverflowMenuButton";
|
||||
import Separator from "components/ContextMenu/Separator";
|
||||
import CopyToClipboard from "components/CopyToClipboard";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import { documentHistoryUrl } from "utils/routeHelpers";
|
||||
|
||||
type Props = {|
|
||||
@ -22,7 +22,7 @@ type Props = {|
|
||||
|};
|
||||
|
||||
function RevisionMenu({ document, revision, className, iconColor }: Props) {
|
||||
const { ui } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const menu = useMenuState({ modal: true });
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
@ -31,15 +31,15 @@ function RevisionMenu({ document, revision, className, iconColor }: Props) {
|
||||
async (ev: SyntheticEvent<>) => {
|
||||
ev.preventDefault();
|
||||
await document.restore({ revisionId: revision.id });
|
||||
ui.showToast(t("Document restored"), { type: "success" });
|
||||
showToast(t("Document restored"), { type: "success" });
|
||||
history.push(document.url);
|
||||
},
|
||||
[history, ui, t, document, revision]
|
||||
[history, showToast, t, document, revision]
|
||||
);
|
||||
|
||||
const handleCopy = React.useCallback(() => {
|
||||
ui.showToast(t("Link copied"), { type: "info" });
|
||||
}, [ui, t]);
|
||||
showToast(t("Link copied"), { type: "info" });
|
||||
}, [showToast, t]);
|
||||
|
||||
const url = `${window.location.origin}${documentHistoryUrl(
|
||||
document,
|
||||
|
@ -10,6 +10,7 @@ import MenuItem from "components/ContextMenu/MenuItem";
|
||||
import OverflowMenuButton from "components/ContextMenu/OverflowMenuButton";
|
||||
import CopyToClipboard from "components/CopyToClipboard";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
share: Share,
|
||||
@ -17,7 +18,8 @@ type Props = {
|
||||
|
||||
function ShareMenu({ share }: Props) {
|
||||
const menu = useMenuState({ modal: true });
|
||||
const { ui, shares, policies } = useStores();
|
||||
const { shares, policies } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const can = policies.abilities(share.id);
|
||||
@ -36,17 +38,17 @@ function ShareMenu({ share }: Props) {
|
||||
|
||||
try {
|
||||
await shares.revoke(share);
|
||||
ui.showToast(t("Share link revoked"), { type: "info" });
|
||||
showToast(t("Share link revoked"), { type: "info" });
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
showToast(err.message, { type: "error" });
|
||||
}
|
||||
},
|
||||
[t, shares, share, ui]
|
||||
[t, shares, share, showToast]
|
||||
);
|
||||
|
||||
const handleCopy = React.useCallback(() => {
|
||||
ui.showToast(t("Share link copied"), { type: "info" });
|
||||
}, [t, ui]);
|
||||
showToast(t("Share link copied"), { type: "info" });
|
||||
}, [t, showToast]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -6,6 +6,7 @@ import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import Input from "components/Input";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
onSubmit: () => void,
|
||||
@ -14,7 +15,8 @@ type Props = {|
|
||||
function APITokenNew({ onSubmit }: Props) {
|
||||
const [name, setName] = React.useState("");
|
||||
const [isSaving, setIsSaving] = React.useState(false);
|
||||
const { apiKeys, ui } = useStores();
|
||||
const { apiKeys } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = React.useCallback(async () => {
|
||||
@ -22,14 +24,14 @@ function APITokenNew({ onSubmit }: Props) {
|
||||
|
||||
try {
|
||||
await apiKeys.create({ name });
|
||||
ui.showToast(t("API token created", { type: "success" }));
|
||||
showToast(t("API token created", { type: "success" }));
|
||||
onSubmit();
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}, [t, ui, name, onSubmit, apiKeys]);
|
||||
}, [t, showToast, name, onSubmit, apiKeys]);
|
||||
|
||||
const handleNameChange = React.useCallback((event) => {
|
||||
setName(event.target.value);
|
||||
|
@ -43,6 +43,7 @@ import useBoolean from "hooks/useBoolean";
|
||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||
import useImportDocument from "hooks/useImportDocument";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import CollectionMenu from "menus/CollectionMenu";
|
||||
import { newDocumentUrl, collectionUrl } from "utils/routeHelpers";
|
||||
|
||||
@ -52,6 +53,7 @@ function CollectionScene() {
|
||||
const match = useRouteMatch();
|
||||
const { t } = useTranslation();
|
||||
const { documents, policies, collections, ui } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const team = useCurrentTeam();
|
||||
const [isFetching, setFetching] = React.useState();
|
||||
const [error, setError] = React.useState();
|
||||
@ -108,11 +110,11 @@ function CollectionScene() {
|
||||
}, [collections, isFetching, collection, error, id, can]);
|
||||
|
||||
const handleRejection = React.useCallback(() => {
|
||||
ui.showToast(
|
||||
showToast(
|
||||
t("Document not supported – try Markdown, Plain text, HTML, or Word"),
|
||||
{ type: "error" }
|
||||
);
|
||||
}, [t, ui]);
|
||||
}, [t, showToast]);
|
||||
|
||||
if (!collection && error) {
|
||||
return <Search notFound />;
|
||||
|
@ -7,7 +7,7 @@ import Collection from "models/Collection";
|
||||
import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import { homeUrl } from "utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
@ -17,7 +17,7 @@ type Props = {
|
||||
|
||||
function CollectionDelete({ collection, onSubmit }: Props) {
|
||||
const [isDeleting, setIsDeleting] = React.useState();
|
||||
const { ui } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -31,12 +31,12 @@ function CollectionDelete({ collection, onSubmit }: Props) {
|
||||
history.push(homeUrl());
|
||||
onSubmit();
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
},
|
||||
[ui, onSubmit, collection, history]
|
||||
[showToast, onSubmit, collection, history]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -4,7 +4,7 @@ import { inject, observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { withTranslation, Trans, type TFunction } from "react-i18next";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import ToastsStore from "stores/ToastsStore";
|
||||
import Collection from "models/Collection";
|
||||
import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
@ -16,7 +16,7 @@ import Switch from "components/Switch";
|
||||
|
||||
type Props = {
|
||||
collection: Collection,
|
||||
ui: UiStore,
|
||||
toasts: ToastsStore,
|
||||
auth: AuthStore,
|
||||
onSubmit: () => void,
|
||||
t: TFunction,
|
||||
@ -46,11 +46,11 @@ class CollectionEdit extends React.Component<Props> {
|
||||
sort: this.sort,
|
||||
});
|
||||
this.props.onSubmit();
|
||||
this.props.ui.showToast(t("The collection was updated"), {
|
||||
this.props.toasts.showToast(t("The collection was updated"), {
|
||||
type: "success",
|
||||
});
|
||||
} catch (err) {
|
||||
this.props.ui.showToast(err.message, { type: "error" });
|
||||
this.props.toasts.showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
@ -148,5 +148,5 @@ class CollectionEdit extends React.Component<Props> {
|
||||
}
|
||||
|
||||
export default withTranslation()<CollectionEdit>(
|
||||
inject("ui", "auth")(CollectionEdit)
|
||||
inject("toasts", "auth")(CollectionEdit)
|
||||
);
|
||||
|
@ -7,7 +7,7 @@ import { withTranslation, type TFunction, Trans } from "react-i18next";
|
||||
import { withRouter, type RouterHistory } from "react-router-dom";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import CollectionsStore from "stores/CollectionsStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import ToastsStore from "stores/ToastsStore";
|
||||
import Collection from "models/Collection";
|
||||
import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
@ -20,7 +20,7 @@ import Switch from "components/Switch";
|
||||
type Props = {
|
||||
history: RouterHistory,
|
||||
auth: AuthStore,
|
||||
ui: UiStore,
|
||||
toasts: ToastsStore,
|
||||
collections: CollectionsStore,
|
||||
onSubmit: () => void,
|
||||
t: TFunction,
|
||||
@ -55,7 +55,7 @@ class CollectionNew extends React.Component<Props> {
|
||||
this.props.onSubmit();
|
||||
this.props.history.push(collection.url);
|
||||
} catch (err) {
|
||||
this.props.ui.showToast(err.message, { type: "error" });
|
||||
this.props.toasts.showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
@ -169,5 +169,5 @@ class CollectionNew extends React.Component<Props> {
|
||||
}
|
||||
|
||||
export default withTranslation()<CollectionNew>(
|
||||
inject("collections", "ui", "auth")(withRouter(CollectionNew))
|
||||
inject("collections", "toasts", "auth")(withRouter(CollectionNew))
|
||||
);
|
||||
|
@ -8,7 +8,7 @@ import styled from "styled-components";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import CollectionGroupMembershipsStore from "stores/CollectionGroupMembershipsStore";
|
||||
import GroupsStore from "stores/GroupsStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import ToastsStore from "stores/ToastsStore";
|
||||
import Collection from "models/Collection";
|
||||
import Group from "models/Group";
|
||||
import GroupNew from "scenes/GroupNew";
|
||||
@ -23,7 +23,7 @@ import Modal from "components/Modal";
|
||||
import PaginatedList from "components/PaginatedList";
|
||||
|
||||
type Props = {
|
||||
ui: UiStore,
|
||||
toasts: ToastsStore,
|
||||
auth: AuthStore,
|
||||
collection: Collection,
|
||||
collectionGroupMemberships: CollectionGroupMembershipsStore,
|
||||
@ -65,14 +65,14 @@ class AddGroupsToCollection extends React.Component<Props> {
|
||||
groupId: group.id,
|
||||
permission: "read_write",
|
||||
});
|
||||
this.props.ui.showToast(
|
||||
this.props.toasts.showToast(
|
||||
t("{{ groupName }} was added to the collection", {
|
||||
groupName: group.name,
|
||||
}),
|
||||
{ type: "success" }
|
||||
);
|
||||
} catch (err) {
|
||||
this.props.ui.showToast(t("Could not add user"), { type: "error" });
|
||||
this.props.toasts.showToast(t("Could not add user"), { type: "error" });
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
@ -147,6 +147,6 @@ export default withTranslation()<AddGroupsToCollection>(
|
||||
"auth",
|
||||
"groups",
|
||||
"collectionGroupMemberships",
|
||||
"ui"
|
||||
"toasts"
|
||||
)(AddGroupsToCollection)
|
||||
);
|
||||
|
@ -6,7 +6,7 @@ import * as React from "react";
|
||||
import { withTranslation, type TFunction } from "react-i18next";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import MembershipsStore from "stores/MembershipsStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import ToastsStore from "stores/ToastsStore";
|
||||
import UsersStore from "stores/UsersStore";
|
||||
import Collection from "models/Collection";
|
||||
import User from "models/User";
|
||||
@ -21,7 +21,7 @@ import PaginatedList from "components/PaginatedList";
|
||||
import MemberListItem from "./components/MemberListItem";
|
||||
|
||||
type Props = {
|
||||
ui: UiStore,
|
||||
toasts: ToastsStore,
|
||||
auth: AuthStore,
|
||||
collection: Collection,
|
||||
memberships: MembershipsStore,
|
||||
@ -62,14 +62,14 @@ class AddPeopleToCollection extends React.Component<Props> {
|
||||
userId: user.id,
|
||||
permission: "read_write",
|
||||
});
|
||||
this.props.ui.showToast(
|
||||
this.props.toasts.showToast(
|
||||
t("{{ userName }} was added to the collection", {
|
||||
userName: user.name,
|
||||
}),
|
||||
{ type: "success" }
|
||||
);
|
||||
} catch (err) {
|
||||
this.props.ui.showToast(t("Could not add user"), { type: "error" });
|
||||
this.props.toasts.showToast(t("Could not add user"), { type: "error" });
|
||||
}
|
||||
};
|
||||
|
||||
@ -130,5 +130,5 @@ class AddPeopleToCollection extends React.Component<Props> {
|
||||
}
|
||||
|
||||
export default withTranslation()<AddPeopleToCollection>(
|
||||
inject("auth", "users", "memberships", "ui")(AddPeopleToCollection)
|
||||
inject("auth", "users", "memberships", "toasts")(AddPeopleToCollection)
|
||||
);
|
||||
|
@ -20,6 +20,7 @@ import MemberListItem from "./components/MemberListItem";
|
||||
import useBoolean from "hooks/useBoolean";
|
||||
import useCurrentUser from "hooks/useCurrentUser";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
collection: Collection,
|
||||
@ -29,12 +30,12 @@ function CollectionPermissions({ collection }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const user = useCurrentUser();
|
||||
const {
|
||||
ui,
|
||||
memberships,
|
||||
collectionGroupMemberships,
|
||||
users,
|
||||
groups,
|
||||
} = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const [
|
||||
addGroupModalOpen,
|
||||
handleAddGroupModalOpen,
|
||||
@ -53,7 +54,7 @@ function CollectionPermissions({ collection }: Props) {
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
});
|
||||
ui.showToast(
|
||||
showToast(
|
||||
t(`{{ userName }} was removed from the collection`, {
|
||||
userName: user.name,
|
||||
}),
|
||||
@ -62,10 +63,10 @@ function CollectionPermissions({ collection }: Props) {
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
ui.showToast(t("Could not remove user"), { type: "error" });
|
||||
showToast(t("Could not remove user"), { type: "error" });
|
||||
}
|
||||
},
|
||||
[memberships, ui, collection, t]
|
||||
[memberships, showToast, collection, t]
|
||||
);
|
||||
|
||||
const handleUpdateUser = React.useCallback(
|
||||
@ -76,17 +77,17 @@ function CollectionPermissions({ collection }: Props) {
|
||||
userId: user.id,
|
||||
permission,
|
||||
});
|
||||
ui.showToast(
|
||||
showToast(
|
||||
t(`{{ userName }} permissions were updated`, { userName: user.name }),
|
||||
{
|
||||
type: "success",
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
ui.showToast(t("Could not update user"), { type: "error" });
|
||||
showToast(t("Could not update user"), { type: "error" });
|
||||
}
|
||||
},
|
||||
[memberships, ui, collection, t]
|
||||
[memberships, showToast, collection, t]
|
||||
);
|
||||
|
||||
const handleRemoveGroup = React.useCallback(
|
||||
@ -96,7 +97,7 @@ function CollectionPermissions({ collection }: Props) {
|
||||
collectionId: collection.id,
|
||||
groupId: group.id,
|
||||
});
|
||||
ui.showToast(
|
||||
showToast(
|
||||
t(`The {{ groupName }} group was removed from the collection`, {
|
||||
groupName: group.name,
|
||||
}),
|
||||
@ -105,10 +106,10 @@ function CollectionPermissions({ collection }: Props) {
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
ui.showToast(t("Could not remove group"), { type: "error" });
|
||||
showToast(t("Could not remove group"), { type: "error" });
|
||||
}
|
||||
},
|
||||
[collectionGroupMemberships, ui, collection, t]
|
||||
[collectionGroupMemberships, showToast, collection, t]
|
||||
);
|
||||
|
||||
const handleUpdateGroup = React.useCallback(
|
||||
@ -119,7 +120,7 @@ function CollectionPermissions({ collection }: Props) {
|
||||
groupId: group.id,
|
||||
permission,
|
||||
});
|
||||
ui.showToast(
|
||||
showToast(
|
||||
t(`{{ groupName }} permissions were updated`, {
|
||||
groupName: group.name,
|
||||
}),
|
||||
@ -128,24 +129,24 @@ function CollectionPermissions({ collection }: Props) {
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
ui.showToast(t("Could not update user"), { type: "error" });
|
||||
showToast(t("Could not update user"), { type: "error" });
|
||||
}
|
||||
},
|
||||
[collectionGroupMemberships, ui, collection, t]
|
||||
[collectionGroupMemberships, showToast, collection, t]
|
||||
);
|
||||
|
||||
const handleChangePermission = React.useCallback(
|
||||
async (ev) => {
|
||||
try {
|
||||
await collection.save({ permission: ev.target.value });
|
||||
ui.showToast(t("Default access permissions were updated"), {
|
||||
showToast(t("Default access permissions were updated"), {
|
||||
type: "success",
|
||||
});
|
||||
} catch (err) {
|
||||
ui.showToast(t("Could not update permissions"), { type: "error" });
|
||||
showToast(t("Could not update permissions"), { type: "error" });
|
||||
}
|
||||
},
|
||||
[collection, ui, t]
|
||||
[collection, showToast, t]
|
||||
);
|
||||
|
||||
const fetchOptions = React.useMemo(() => ({ id: collection.id }), [
|
||||
|
@ -11,6 +11,7 @@ import type { RouterHistory, Match } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import ToastsStore from "stores/ToastsStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import Document from "models/Document";
|
||||
import Revision from "models/Revision";
|
||||
@ -59,6 +60,7 @@ type Props = {
|
||||
theme: Theme,
|
||||
auth: AuthStore,
|
||||
ui: UiStore,
|
||||
toasts: ToastsStore,
|
||||
t: TFunction,
|
||||
};
|
||||
|
||||
@ -89,8 +91,10 @@ class DocumentScene extends React.Component<Props> {
|
||||
}
|
||||
} else if (prevProps.document.revision !== this.lastRevision) {
|
||||
if (auth.user && document.updatedBy.id !== auth.user.id) {
|
||||
this.props.ui.showToast(
|
||||
t(`Document updated by ${document.updatedBy.name}`),
|
||||
this.props.toasts.showToast(
|
||||
t(`Document updated by {{userName}}`, {
|
||||
userName: document.updatedBy.name,
|
||||
}),
|
||||
{
|
||||
timeout: 30 * 1000,
|
||||
type: "warning",
|
||||
@ -230,7 +234,7 @@ class DocumentScene extends React.Component<Props> {
|
||||
this.props.ui.setActiveDocument(savedDocument);
|
||||
}
|
||||
} catch (err) {
|
||||
this.props.ui.showToast(err.message, { type: "error" });
|
||||
this.props.toasts.showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
this.isPublishing = false;
|
||||
@ -525,6 +529,6 @@ const MaxWidth = styled(Flex)`
|
||||
|
||||
export default withRouter(
|
||||
withTranslation()<DocumentScene>(
|
||||
inject("ui", "auth", "policies", "revisions")(DocumentScene)
|
||||
inject("ui", "auth", "policies", "revisions", "toasts")(DocumentScene)
|
||||
)
|
||||
);
|
||||
|
@ -16,6 +16,7 @@ import Input from "components/Input";
|
||||
import Notice from "components/Notice";
|
||||
import Switch from "components/Switch";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
document: Document,
|
||||
@ -26,7 +27,8 @@ type Props = {|
|
||||
|
||||
function SharePopover({ document, share, sharedParent, onSubmit }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { policies, shares, ui } = useStores();
|
||||
const { policies, shares } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const [isCopied, setIsCopied] = React.useState(false);
|
||||
const timeout = React.useRef<?TimeoutID>();
|
||||
const can = policies.abilities(share ? share.id : "");
|
||||
@ -46,10 +48,10 @@ function SharePopover({ document, share, sharedParent, onSubmit }: Props) {
|
||||
try {
|
||||
await share.save({ published: event.currentTarget.checked });
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
showToast(err.message, { type: "error" });
|
||||
}
|
||||
},
|
||||
[document.id, shares, ui]
|
||||
[document.id, shares, showToast]
|
||||
);
|
||||
|
||||
const handleChildDocumentsChange = React.useCallback(
|
||||
@ -62,10 +64,10 @@ function SharePopover({ document, share, sharedParent, onSubmit }: Props) {
|
||||
includeChildDocuments: event.currentTarget.checked,
|
||||
});
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
showToast(err.message, { type: "error" });
|
||||
}
|
||||
},
|
||||
[document.id, shares, ui]
|
||||
[document.id, shares, showToast]
|
||||
);
|
||||
|
||||
const handleCopied = React.useCallback(() => {
|
||||
@ -75,9 +77,9 @@ function SharePopover({ document, share, sharedParent, onSubmit }: Props) {
|
||||
setIsCopied(false);
|
||||
onSubmit();
|
||||
|
||||
ui.showToast(t("Share link copied"), { type: "info" });
|
||||
showToast(t("Share link copied"), { type: "info" });
|
||||
}, 250);
|
||||
}, [t, onSubmit, ui]);
|
||||
}, [t, onSubmit, showToast]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -8,6 +8,7 @@ import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import { collectionUrl, documentUrl } from "utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
@ -21,7 +22,7 @@ function DocumentDelete({ document, onSubmit }: Props) {
|
||||
const history = useHistory();
|
||||
const [isDeleting, setDeleting] = React.useState(false);
|
||||
const [isArchiving, setArchiving] = React.useState(false);
|
||||
const { showToast } = ui;
|
||||
const { showToast } = useToasts();
|
||||
const canArchive = !document.isDraft && !document.isArchived;
|
||||
const collection = collections.get(document.collectionId);
|
||||
|
||||
|
@ -15,6 +15,7 @@ import { Outline } from "components/Input";
|
||||
import Labeled from "components/Labeled";
|
||||
import PathToDocument from "components/PathToDocument";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
document: Document,
|
||||
@ -23,7 +24,8 @@ type Props = {|
|
||||
|
||||
function DocumentMove({ document, onRequestClose }: Props) {
|
||||
const [searchTerm, setSearchTerm] = useState();
|
||||
const { ui, collections, documents } = useStores();
|
||||
const { collections, documents } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const searchIndex = useMemo(() => {
|
||||
@ -77,7 +79,7 @@ function DocumentMove({ document, onRequestClose }: Props) {
|
||||
}, [document, collections, searchTerm, searchIndex]);
|
||||
|
||||
const handleSuccess = () => {
|
||||
ui.showToast(t("Document moved"), { type: "info" });
|
||||
showToast(t("Document moved"), { type: "info" });
|
||||
onRequestClose();
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@ import CenteredContent from "components/CenteredContent";
|
||||
import Flex from "components/Flex";
|
||||
import PlaceholderDocument from "components/PlaceholderDocument";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import { editDocumentUrl } from "utils/routeHelpers";
|
||||
|
||||
function DocumentNew() {
|
||||
@ -16,7 +17,8 @@ function DocumentNew() {
|
||||
const location = useLocation();
|
||||
const match = useRouteMatch();
|
||||
const { t } = useTranslation();
|
||||
const { documents, ui, collections } = useStores();
|
||||
const { documents, collections } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const id = match.params.id || "";
|
||||
|
||||
useEffect(() => {
|
||||
@ -36,7 +38,7 @@ function DocumentNew() {
|
||||
|
||||
history.replace(editDocumentUrl(document));
|
||||
} catch (err) {
|
||||
ui.showToast(t("Couldn’t create the document, try again?"), {
|
||||
showToast(t("Couldn’t create the document, try again?"), {
|
||||
type: "error",
|
||||
});
|
||||
history.goBack();
|
||||
|
@ -8,6 +8,7 @@ import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
document: Document,
|
||||
@ -17,8 +18,8 @@ type Props = {|
|
||||
function DocumentPermanentDelete({ document, onSubmit }: Props) {
|
||||
const [isDeleting, setIsDeleting] = React.useState(false);
|
||||
const { t } = useTranslation();
|
||||
const { ui, documents } = useStores();
|
||||
const { showToast } = ui;
|
||||
const { documents } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const history = useHistory();
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
|
@ -8,7 +8,7 @@ import Document from "models/Document";
|
||||
import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import { documentUrl } from "utils/routeHelpers";
|
||||
|
||||
type Props = {
|
||||
@ -19,7 +19,7 @@ type Props = {
|
||||
function DocumentTemplatize({ document, onSubmit }: Props) {
|
||||
const [isSaving, setIsSaving] = useState();
|
||||
const history = useHistory();
|
||||
const { ui } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
@ -30,17 +30,17 @@ function DocumentTemplatize({ document, onSubmit }: Props) {
|
||||
try {
|
||||
const template = await document.templatize();
|
||||
history.push(documentUrl(template));
|
||||
ui.showToast(t("Template created, go ahead and customize it"), {
|
||||
showToast(t("Template created, go ahead and customize it"), {
|
||||
type: "info",
|
||||
});
|
||||
onSubmit();
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
},
|
||||
[document, ui, history, onSubmit, t]
|
||||
[document, showToast, history, onSubmit, t]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -8,7 +8,7 @@ import Group from "models/Group";
|
||||
import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
group: Group,
|
||||
@ -16,8 +16,8 @@ type Props = {|
|
||||
|};
|
||||
|
||||
function GroupDelete({ group, onSubmit }: Props) {
|
||||
const { ui } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToasts();
|
||||
const history = useHistory();
|
||||
const [isDeleting, setIsDeleting] = React.useState();
|
||||
|
||||
@ -30,7 +30,7 @@ function GroupDelete({ group, onSubmit }: Props) {
|
||||
history.push(groupSettings());
|
||||
onSubmit();
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import Input from "components/Input";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
@ -15,7 +15,7 @@ type Props = {
|
||||
};
|
||||
|
||||
function GroupEdit({ group, onSubmit }: Props) {
|
||||
const { ui } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
const [name, setName] = React.useState(group.name);
|
||||
const [isSaving, setIsSaving] = React.useState();
|
||||
@ -29,12 +29,12 @@ function GroupEdit({ group, onSubmit }: Props) {
|
||||
await group.save({ name: name });
|
||||
onSubmit();
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
},
|
||||
[group, onSubmit, ui, name]
|
||||
[group, onSubmit, showToast, name]
|
||||
);
|
||||
|
||||
const handleNameChange = React.useCallback((ev: SyntheticInputEvent<*>) => {
|
||||
|
@ -6,7 +6,7 @@ import * as React from "react";
|
||||
import { withTranslation, type TFunction } from "react-i18next";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import GroupMembershipsStore from "stores/GroupMembershipsStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import ToastsStore from "stores/ToastsStore";
|
||||
import UsersStore from "stores/UsersStore";
|
||||
import Group from "models/Group";
|
||||
import User from "models/User";
|
||||
@ -21,7 +21,7 @@ import PaginatedList from "components/PaginatedList";
|
||||
import GroupMemberListItem from "./components/GroupMemberListItem";
|
||||
|
||||
type Props = {
|
||||
ui: UiStore,
|
||||
toasts: ToastsStore,
|
||||
auth: AuthStore,
|
||||
group: Group,
|
||||
groupMemberships: GroupMembershipsStore,
|
||||
@ -62,12 +62,12 @@ class AddPeopleToGroup extends React.Component<Props> {
|
||||
groupId: this.props.group.id,
|
||||
userId: user.id,
|
||||
});
|
||||
this.props.ui.showToast(
|
||||
this.props.toasts.showToast(
|
||||
t(`{{userName}} was added to the group`, { userName: user.name }),
|
||||
{ type: "success" }
|
||||
);
|
||||
} catch (err) {
|
||||
this.props.ui.showToast(t("Could not add user"), { type: "error" });
|
||||
this.props.toasts.showToast(t("Could not add user"), { type: "error" });
|
||||
}
|
||||
};
|
||||
|
||||
@ -128,5 +128,5 @@ class AddPeopleToGroup extends React.Component<Props> {
|
||||
}
|
||||
|
||||
export default withTranslation()<AddPeopleToGroup>(
|
||||
inject("auth", "users", "groupMemberships", "ui")(AddPeopleToGroup)
|
||||
inject("auth", "users", "groupMemberships", "toasts")(AddPeopleToGroup)
|
||||
);
|
||||
|
@ -15,6 +15,7 @@ import Subheading from "components/Subheading";
|
||||
import AddPeopleToGroup from "./AddPeopleToGroup";
|
||||
import GroupMemberListItem from "./components/GroupMemberListItem";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
group: Group,
|
||||
@ -22,7 +23,8 @@ type Props = {
|
||||
|
||||
function GroupMembers({ group }: Props) {
|
||||
const [addModalOpen, setAddModalOpen] = React.useState();
|
||||
const { users, groupMemberships, policies, ui } = useStores();
|
||||
const { users, groupMemberships, policies } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
const can = policies.abilities(group.id);
|
||||
|
||||
@ -36,12 +38,12 @@ function GroupMembers({ group }: Props) {
|
||||
groupId: group.id,
|
||||
userId: user.id,
|
||||
});
|
||||
ui.showToast(
|
||||
showToast(
|
||||
t(`{{userName}} was removed from the group`, { userName: user.name }),
|
||||
{ type: "success" }
|
||||
);
|
||||
} catch (err) {
|
||||
ui.showToast(t("Could not remove user"), { type: "error" });
|
||||
showToast(t("Could not remove user"), { type: "error" });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -10,14 +10,16 @@ import HelpText from "components/HelpText";
|
||||
import Input from "components/Input";
|
||||
import Modal from "components/Modal";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {
|
||||
onSubmit: () => void,
|
||||
};
|
||||
|
||||
function GroupNew({ onSubmit }: Props) {
|
||||
const { ui, groups } = useStores();
|
||||
const { groups } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToasts();
|
||||
const [name, setName] = React.useState();
|
||||
const [isSaving, setIsSaving] = React.useState();
|
||||
const [group, setGroup] = React.useState();
|
||||
@ -35,7 +37,7 @@ function GroupNew({ onSubmit }: Props) {
|
||||
try {
|
||||
setGroup(await group.save());
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import Tooltip from "components/Tooltip";
|
||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||
import useCurrentUser from "hooks/useCurrentUser";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
const MAX_INVITES = 20;
|
||||
|
||||
@ -36,7 +37,8 @@ function Invite({ onSubmit }: Props) {
|
||||
{ email: "", name: "" },
|
||||
]);
|
||||
|
||||
const { users, policies, ui } = useStores();
|
||||
const { users, policies } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const user = useCurrentUser();
|
||||
const team = useCurrentTeam();
|
||||
const { t } = useTranslation();
|
||||
@ -52,14 +54,14 @@ function Invite({ onSubmit }: Props) {
|
||||
try {
|
||||
await users.invite(invites);
|
||||
onSubmit();
|
||||
ui.showToast(t("We sent out your invites!"), { type: "success" });
|
||||
showToast(t("We sent out your invites!"), { type: "success" });
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
showToast(err.message, { type: "error" });
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
},
|
||||
[onSubmit, ui, invites, t, users]
|
||||
[onSubmit, showToast, invites, t, users]
|
||||
);
|
||||
|
||||
const handleChange = React.useCallback((ev, index) => {
|
||||
@ -72,7 +74,7 @@ function Invite({ onSubmit }: Props) {
|
||||
|
||||
const handleAdd = React.useCallback(() => {
|
||||
if (invites.length >= MAX_INVITES) {
|
||||
ui.showToast(
|
||||
showToast(
|
||||
t("Sorry, you can only send {{MAX_INVITES}} invites at a time", {
|
||||
MAX_INVITES,
|
||||
}),
|
||||
@ -85,7 +87,7 @@ function Invite({ onSubmit }: Props) {
|
||||
newInvites.push({ email: "", name: "" });
|
||||
return newInvites;
|
||||
});
|
||||
}, [ui, invites, t]);
|
||||
}, [showToast, invites, t]);
|
||||
|
||||
const handleRemove = React.useCallback(
|
||||
(ev: SyntheticEvent<>, index: number) => {
|
||||
@ -102,10 +104,10 @@ function Invite({ onSubmit }: Props) {
|
||||
|
||||
const handleCopy = React.useCallback(() => {
|
||||
setLinkCopied(true);
|
||||
ui.showToast(t("Share link copied"), {
|
||||
showToast(t("Share link copied"), {
|
||||
type: "success",
|
||||
});
|
||||
}, [ui, t]);
|
||||
}, [showToast, t]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
|
@ -15,9 +15,11 @@ import ImageUpload from "./components/ImageUpload";
|
||||
import env from "env";
|
||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
function Details() {
|
||||
const { auth, ui } = useStores();
|
||||
const { auth } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const team = useCurrentTeam();
|
||||
const { t } = useTranslation();
|
||||
const form = useRef<?HTMLFormElement>();
|
||||
@ -37,12 +39,12 @@ function Details() {
|
||||
avatarUrl,
|
||||
subdomain,
|
||||
});
|
||||
ui.showToast(t("Settings saved"), { type: "success" });
|
||||
showToast(t("Settings saved"), { type: "success" });
|
||||
} catch (err) {
|
||||
ui.showToast(err.message, { type: "error" });
|
||||
showToast(err.message, { type: "error" });
|
||||
}
|
||||
},
|
||||
[auth, ui, name, avatarUrl, subdomain, t]
|
||||
[auth, showToast, name, avatarUrl, subdomain, t]
|
||||
);
|
||||
|
||||
const handleNameChange = React.useCallback((ev: SyntheticInputEvent<*>) => {
|
||||
@ -66,9 +68,9 @@ function Details() {
|
||||
|
||||
const handleAvatarError = React.useCallback(
|
||||
(error: ?string) => {
|
||||
ui.showToast(error || t("Unable to upload new logo"));
|
||||
showToast(error || t("Unable to upload new logo"));
|
||||
},
|
||||
[ui, t]
|
||||
[showToast, t]
|
||||
);
|
||||
|
||||
const isValid = form.current && form.current.checkValidity();
|
||||
|
@ -14,6 +14,7 @@ import Notice from "components/Notice";
|
||||
import Scene from "components/Scene";
|
||||
import useCurrentUser from "hooks/useCurrentUser";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
import getDataTransferFiles from "utils/getDataTransferFiles";
|
||||
import { uploadFile } from "utils/uploadFile";
|
||||
|
||||
@ -21,8 +22,8 @@ function ImportExport() {
|
||||
const { t } = useTranslation();
|
||||
const user = useCurrentUser();
|
||||
const fileRef = React.useRef();
|
||||
const { ui, collections } = useStores();
|
||||
const { showToast } = ui;
|
||||
const { collections } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const [isLoading, setLoading] = React.useState(false);
|
||||
const [isImporting, setImporting] = React.useState(false);
|
||||
const [isImported, setImported] = React.useState(false);
|
||||
|
@ -14,9 +14,11 @@ import Subheading from "components/Subheading";
|
||||
import NotificationListItem from "./components/NotificationListItem";
|
||||
import useCurrentUser from "hooks/useCurrentUser";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
function Notifications() {
|
||||
const { notificationSettings, ui } = useStores();
|
||||
const { notificationSettings } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const user = useCurrentUser();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -64,7 +66,7 @@ function Notifications() {
|
||||
}, [notificationSettings]);
|
||||
|
||||
const showSuccessMessage = debounce(() => {
|
||||
ui.showToast(t("Notifications saved"), { type: "success" });
|
||||
showToast(t("Notifications saved"), { type: "success" });
|
||||
}, 500);
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
|
@ -7,7 +7,7 @@ import { Trans, withTranslation, type TFunction } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { languageOptions } from "shared/i18n";
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import ToastsStore from "stores/ToastsStore";
|
||||
import UserDelete from "scenes/UserDelete";
|
||||
import Button from "components/Button";
|
||||
import Flex from "components/Flex";
|
||||
@ -20,7 +20,7 @@ import ImageUpload from "./components/ImageUpload";
|
||||
|
||||
type Props = {
|
||||
auth: AuthStore,
|
||||
ui: UiStore,
|
||||
toasts: ToastsStore,
|
||||
t: TFunction,
|
||||
};
|
||||
|
||||
@ -55,7 +55,7 @@ class Profile extends React.Component<Props> {
|
||||
language: this.language,
|
||||
});
|
||||
|
||||
this.props.ui.showToast(t("Profile saved"), { type: "success" });
|
||||
this.props.toasts.showToast(t("Profile saved"), { type: "success" });
|
||||
};
|
||||
|
||||
handleNameChange = (ev: SyntheticInputEvent<*>) => {
|
||||
@ -69,12 +69,14 @@ class Profile extends React.Component<Props> {
|
||||
await this.props.auth.updateUser({
|
||||
avatarUrl: this.avatarUrl,
|
||||
});
|
||||
this.props.ui.showToast(t("Profile picture updated"), { type: "success" });
|
||||
this.props.toasts.showToast(t("Profile picture updated"), {
|
||||
type: "success",
|
||||
});
|
||||
};
|
||||
|
||||
handleAvatarError = (error: ?string) => {
|
||||
const { t } = this.props;
|
||||
this.props.ui.showToast(
|
||||
this.props.toasts.showToast(
|
||||
error || t("Unable to upload new profile picture"),
|
||||
{ type: "error" }
|
||||
);
|
||||
@ -213,4 +215,4 @@ const Avatar = styled.img`
|
||||
${avatarStyles};
|
||||
`;
|
||||
|
||||
export default withTranslation()<Profile>(inject("auth", "ui")(Profile));
|
||||
export default withTranslation()<Profile>(inject("auth", "toasts")(Profile));
|
||||
|
@ -11,17 +11,19 @@ import HelpText from "components/HelpText";
|
||||
import Scene from "components/Scene";
|
||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
function Security() {
|
||||
const { auth, ui } = useStores();
|
||||
const { auth } = useStores();
|
||||
const team = useCurrentTeam();
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToasts();
|
||||
const [sharing, setSharing] = useState(team.documentEmbeds);
|
||||
const [documentEmbeds, setDocumentEmbeds] = useState(team.guestSignin);
|
||||
const [guestSignin, setGuestSignin] = useState(team.sharing);
|
||||
|
||||
const showSuccessMessage = debounce(() => {
|
||||
ui.showToast(t("Settings saved"), { type: "success" });
|
||||
showToast(t("Settings saved"), { type: "success" });
|
||||
}, 500);
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
|
@ -7,6 +7,7 @@ import Flex from "components/Flex";
|
||||
import HelpText from "components/HelpText";
|
||||
import Modal from "components/Modal";
|
||||
import useStores from "hooks/useStores";
|
||||
import useToasts from "hooks/useToasts";
|
||||
|
||||
type Props = {|
|
||||
onRequestClose: () => void,
|
||||
@ -14,7 +15,8 @@ type Props = {|
|
||||
|
||||
function UserDelete({ onRequestClose }: Props) {
|
||||
const [isDeleting, setIsDeleting] = React.useState();
|
||||
const { auth, ui } = useStores();
|
||||
const { auth } = useStores();
|
||||
const { showToast } = useToasts();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
@ -26,12 +28,12 @@ function UserDelete({ onRequestClose }: Props) {
|
||||
await auth.deleteUser();
|
||||
auth.logout();
|
||||
} catch (error) {
|
||||
ui.showToast(error.message, { type: "error" });
|
||||
showToast(error.message, { type: "error" });
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
},
|
||||
[auth, ui]
|
||||
[auth, showToast]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -13,6 +13,7 @@ import NotificationSettingsStore from "./NotificationSettingsStore";
|
||||
import PoliciesStore from "./PoliciesStore";
|
||||
import RevisionsStore from "./RevisionsStore";
|
||||
import SharesStore from "./SharesStore";
|
||||
import ToastsStore from "./ToastsStore";
|
||||
import UiStore from "./UiStore";
|
||||
import UsersStore from "./UsersStore";
|
||||
import ViewsStore from "./ViewsStore";
|
||||
@ -35,6 +36,7 @@ export default class RootStore {
|
||||
ui: UiStore;
|
||||
users: UsersStore;
|
||||
views: ViewsStore;
|
||||
toasts: ToastsStore;
|
||||
|
||||
constructor() {
|
||||
this.apiKeys = new ApiKeysStore(this);
|
||||
@ -54,6 +56,7 @@ export default class RootStore {
|
||||
this.ui = new UiStore();
|
||||
this.users = new UsersStore(this);
|
||||
this.views = new ViewsStore(this);
|
||||
this.toasts = new ToastsStore();
|
||||
}
|
||||
|
||||
logout() {
|
||||
|
52
app/stores/ToastsStore.js
Normal file
52
app/stores/ToastsStore.js
Normal file
@ -0,0 +1,52 @@
|
||||
// @flow
|
||||
import { orderBy } from "lodash";
|
||||
import { observable, action, computed } from "mobx";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import type { Toast, ToastOptions } from "types";
|
||||
|
||||
export default class ToastsStore {
|
||||
@observable toasts: Map<string, Toast> = new Map();
|
||||
lastToastId: string;
|
||||
|
||||
@action
|
||||
showToast = (
|
||||
message: string,
|
||||
options: ToastOptions = {
|
||||
type: "info",
|
||||
}
|
||||
) => {
|
||||
if (!message) return;
|
||||
|
||||
const lastToast = this.toasts.get(this.lastToastId);
|
||||
if (lastToast && lastToast.message === message) {
|
||||
this.toasts.set(this.lastToastId, {
|
||||
...lastToast,
|
||||
reoccurring: lastToast.reoccurring ? ++lastToast.reoccurring : 1,
|
||||
});
|
||||
return this.lastToastId;
|
||||
}
|
||||
|
||||
const id = uuidv4();
|
||||
const createdAt = new Date().toISOString();
|
||||
this.toasts.set(id, {
|
||||
id,
|
||||
message,
|
||||
createdAt,
|
||||
type: options.type,
|
||||
timeout: options.timeout,
|
||||
action: options.action,
|
||||
});
|
||||
this.lastToastId = id;
|
||||
return id;
|
||||
};
|
||||
|
||||
@action
|
||||
hideToast = (id: string) => {
|
||||
this.toasts.delete(id);
|
||||
};
|
||||
|
||||
@computed
|
||||
get orderedData(): Toast[] {
|
||||
return orderBy(Array.from(this.toasts.values()), "createdAt", "desc");
|
||||
}
|
||||
}
|
@ -2,27 +2,27 @@
|
||||
import stores from '.';
|
||||
|
||||
// Actions
|
||||
describe('UiStore', () => {
|
||||
describe('ToastsStore', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = stores.ui;
|
||||
store = stores.toasts;
|
||||
});
|
||||
|
||||
test('#add should add messages', () => {
|
||||
expect(store.orderedToasts.length).toBe(0);
|
||||
expect(store.orderedData.length).toBe(0);
|
||||
store.showToast('first error');
|
||||
store.showToast('second error');
|
||||
expect(store.orderedToasts.length).toBe(2);
|
||||
expect(store.orderedData.length).toBe(2);
|
||||
});
|
||||
|
||||
test('#remove should remove messages', () => {
|
||||
store.toasts.clear();
|
||||
const id = store.showToast('first error');
|
||||
store.showToast('second error');
|
||||
expect(store.orderedToasts.length).toBe(2);
|
||||
store.removeToast(id);
|
||||
expect(store.orderedToasts.length).toBe(1);
|
||||
expect(store.orderedToasts[0].message).toBe('second error');
|
||||
expect(store.orderedData.length).toBe(2);
|
||||
store.hideToast(id);
|
||||
expect(store.orderedData.length).toBe(1);
|
||||
expect(store.orderedData[0].message).toBe('second error');
|
||||
});
|
||||
});
|
@ -1,11 +1,8 @@
|
||||
// @flow
|
||||
import { orderBy } from "lodash";
|
||||
import { observable, action, autorun, computed } from "mobx";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { action, autorun, computed, observable } from "mobx";
|
||||
import { light as defaultTheme } from "shared/styles/theme";
|
||||
import Collection from "models/Collection";
|
||||
import Document from "models/Document";
|
||||
import type { Toast } from "types";
|
||||
|
||||
const UI_STORE = "UI_STORE";
|
||||
|
||||
@ -27,8 +24,6 @@ class UiStore {
|
||||
@observable sidebarWidth: number;
|
||||
@observable sidebarCollapsed: boolean = false;
|
||||
@observable sidebarIsResizing: boolean = false;
|
||||
@observable toasts: Map<string, Toast> = new Map();
|
||||
lastToastId: string;
|
||||
|
||||
constructor() {
|
||||
// Rehydrate
|
||||
@ -173,50 +168,6 @@ class UiStore {
|
||||
this.mobileSidebarVisible = false;
|
||||
};
|
||||
|
||||
@action
|
||||
showToast = (
|
||||
message: string,
|
||||
options: {
|
||||
type: "warning" | "error" | "info" | "success",
|
||||
timeout?: number,
|
||||
action?: {
|
||||
text: string,
|
||||
onClick: () => void,
|
||||
},
|
||||
} = {
|
||||
type: "info",
|
||||
}
|
||||
) => {
|
||||
if (!message) return;
|
||||
|
||||
const lastToast = this.toasts.get(this.lastToastId);
|
||||
if (lastToast && lastToast.message === message) {
|
||||
this.toasts.set(this.lastToastId, {
|
||||
...lastToast,
|
||||
reoccurring: lastToast.reoccurring ? ++lastToast.reoccurring : 1,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const id = uuidv4();
|
||||
const createdAt = new Date().toISOString();
|
||||
this.toasts.set(id, {
|
||||
id,
|
||||
message,
|
||||
createdAt,
|
||||
type: options.type,
|
||||
timeout: options.timeout,
|
||||
action: options.action,
|
||||
});
|
||||
this.lastToastId = id;
|
||||
return id;
|
||||
};
|
||||
|
||||
@action
|
||||
removeToast = (id: string) => {
|
||||
this.toasts.delete(id);
|
||||
};
|
||||
|
||||
@computed
|
||||
get resolvedTheme(): "dark" | "light" {
|
||||
if (this.theme === "system") {
|
||||
@ -226,11 +177,6 @@ class UiStore {
|
||||
return this.theme;
|
||||
}
|
||||
|
||||
@computed
|
||||
get orderedToasts(): Toast[] {
|
||||
return orderBy(Array.from(this.toasts.values()), "createdAt", "desc");
|
||||
}
|
||||
|
||||
@computed
|
||||
get asJson(): string {
|
||||
return JSON.stringify({
|
||||
|
@ -99,3 +99,12 @@ export type MenuItem =
|
||||
visible?: boolean,
|
||||
title: React.Node,
|
||||
|};
|
||||
|
||||
export type ToastOptions = {|
|
||||
type: "warning" | "error" | "info" | "success",
|
||||
timeout?: number,
|
||||
action?: {
|
||||
text: string,
|
||||
onClick: () => void,
|
||||
},
|
||||
|};
|
||||
|
Reference in New Issue
Block a user