chore: Refactor data import (#3434)

* Complete refactor of import

* feat: Notion data import (#3442)
This commit is contained in:
Tom Moor
2022-04-23 10:07:35 -07:00
committed by GitHub
parent bdcfaae025
commit 33ce49cc33
45 changed files with 2217 additions and 1066 deletions

View File

@@ -0,0 +1,127 @@
import { observer } from "mobx-react";
import { NewDocumentIcon } from "outline-icons";
import * as React from "react";
import Dropzone from "react-dropzone";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import Flex from "~/components/Flex";
import LoadingIndicator from "~/components/LoadingIndicator";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import { uploadFile } from "~/utils/files";
type Props = {
children: JSX.Element;
format?: string;
disabled?: boolean;
activeClassName?: string;
onSubmit: () => void;
};
function DropToImport({ disabled, onSubmit, children, format }: Props) {
const { t } = useTranslation();
const { collections } = useStores();
const { showToast } = useToasts();
const [isImporting, setImporting] = React.useState(false);
const handleFiles = React.useCallback(
async (files) => {
if (files.length > 1) {
showToast(t("Please choose a single file to import"), {
type: "error",
});
return;
}
const file = files[0];
setImporting(true);
try {
const attachment = await uploadFile(file, {
name: file.name,
});
await collections.import(attachment.id, format);
onSubmit();
showToast(
t("Your import is being processed, you can safely leave this page"),
{
type: "success",
timeout: 6000,
}
);
} catch (err) {
showToast(err.message);
} finally {
setImporting(false);
}
},
[t, onSubmit, collections, format, showToast]
);
const handleRejection = React.useCallback(() => {
showToast(t("File not supported please upload a valid ZIP file"), {
type: "error",
});
}, [t, showToast]);
if (disabled) {
return children;
}
return (
<>
{isImporting && <LoadingIndicator />}
<Dropzone
accept="application/zip"
onDropAccepted={handleFiles}
onDropRejected={handleRejection}
disabled={isImporting}
>
{({ getRootProps, getInputProps, isDragActive }) => (
<DropzoneContainer
{...getRootProps()}
$disabled={isImporting}
$isDragActive={isDragActive}
tabIndex={-1}
>
<input {...getInputProps()} />
<Flex align="center" gap={4} column>
<Icon size={32} color="#fff" />
{children}
</Flex>
</DropzoneContainer>
)}
</Dropzone>
</>
);
}
const Icon = styled(NewDocumentIcon)`
padding: 4px;
border-radius: 50%;
background: ${(props) => props.theme.brand.blue};
color: white;
`;
const DropzoneContainer = styled.div<{
$disabled: boolean;
$isDragActive: boolean;
}>`
background: ${(props) =>
props.$isDragActive
? props.theme.secondaryBackground
: props.theme.background};
border-radius: 8px;
border: 1px dashed ${(props) => props.theme.divider};
padding: 52px;
text-align: center;
font-size: 15px;
cursor: pointer;
opacity: ${(props) => (props.$disabled ? 0.5 : 1)};
&:hover {
background: ${(props) => props.theme.secondaryBackground};
}
`;
export default observer(DropToImport);

View File

@@ -0,0 +1,53 @@
import { QuestionMarkIcon } from "outline-icons";
import * as React from "react";
import {
useDisclosureState,
Disclosure,
DisclosureContent,
} from "reakit/Disclosure";
import styled, { useTheme } from "styled-components";
import Button from "~/components/Button";
import Text from "~/components/Text";
type Props = {
title: React.ReactNode;
};
const HelpDisclosure: React.FC<Props> = ({ title, children }) => {
const disclosure = useDisclosureState({ animated: true });
const theme = useTheme();
return (
<div>
<Disclosure {...disclosure}>
{(props) => (
<Button
icon={<QuestionMarkIcon color={theme.text} />}
neutral
borderOnHover
{...props}
>
{title}
</Button>
)}
</Disclosure>
<HelpContent {...disclosure}>
<Text type="secondary">
<br />
{children}
</Text>
</HelpContent>
</div>
);
};
const HelpContent = styled(DisclosureContent)`
transition: opacity 250ms ease-in-out;
opacity: 0;
&[data-enter] {
opacity: 1;
}
`;
export default HelpDisclosure;

View File

@@ -0,0 +1,35 @@
import { observer } from "mobx-react";
import * as React from "react";
import { Trans } from "react-i18next";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import DropToImport from "./DropToImport";
import HelpDisclosure from "./HelpDisclosure";
function ImportNotionDialog() {
const { dialogs } = useStores();
return (
<Flex column>
<Text type="secondary">
<DropToImport onSubmit={dialogs.closeAllModals} format="notion">
<Trans>
Drag and drop the zip file from Notion's HTML export option, or
click to upload
</Trans>
</DropToImport>
</Text>
<HelpDisclosure title={<Trans>Where do I find the file?</Trans>}>
<Trans
defaults="In Notion, click <em>Settings & Members</em> in the left sidebar and open Settings. Look for the Export section, and click <em>Export all workspace content</em>. Choose <em>HTML</em> as the format for the best data compatability."
components={{
em: <strong />,
}}
/>
</HelpDisclosure>
</Flex>
);
}
export default observer(ImportNotionDialog);

View File

@@ -0,0 +1,38 @@
import { observer } from "mobx-react";
import * as React from "react";
import { Trans } from "react-i18next";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import useStores from "~/hooks/useStores";
import DropToImport from "./DropToImport";
import HelpDisclosure from "./HelpDisclosure";
function ImportOutlineDialog() {
const { dialogs } = useStores();
return (
<Flex column>
<Text type="secondary">
<DropToImport
onSubmit={dialogs.closeAllModals}
format="outline-markdown"
>
<Trans>
Drag and drop the zip file from Outline's export option, or click to
upload
</Trans>
</DropToImport>
</Text>
<HelpDisclosure title={<Trans>How does this work?</Trans>}>
<Trans
defaults="You can import a zip file that was previously exported from an Outline installation collections, documents, and images will be imported. In Outline, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>."
components={{
em: <strong />,
}}
/>
</HelpDisclosure>
</Flex>
);
}
export default observer(ImportOutlineDialog);