// @flow import invariant from "invariant"; import { observer } from "mobx-react"; import { CollectionIcon, DocumentIcon } from "outline-icons"; import * as React from "react"; import { useTranslation, Trans } from "react-i18next"; import { VisuallyHidden } from "reakit/VisuallyHidden"; import styled from "styled-components"; import { parseOutlineExport } from "shared/utils/zip"; import Button from "components/Button"; import Heading from "components/Heading"; import HelpText from "components/HelpText"; import Notice from "components/Notice"; import PaginatedList from "components/PaginatedList"; import Scene from "components/Scene"; import Subheading from "components/Subheading"; import FileOperationListItem from "./components/FileOperationListItem"; 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"; function ImportExport() { const { t } = useTranslation(); const user = useCurrentUser(); const fileRef = React.useRef(); const { fileOperations, collections } = useStores(); const { showToast } = useToasts(); const [isLoading, setLoading] = React.useState(false); const [isImporting, setImporting] = React.useState(false); const [isImported, setImported] = React.useState(false); const [isExporting, setExporting] = React.useState(false); const [file, setFile] = React.useState(); const [importDetails, setImportDetails] = React.useState(); const handleImport = React.useCallback( async (ev) => { setImported(undefined); setImporting(true); try { invariant(file, "File must exist to upload"); const attachment = await uploadFile(file, { name: file.name, }); await collections.import(attachment.id); showToast(t("Import started")); setImported(true); } catch (err) { showToast(err.message); } finally { if (fileRef.current) { fileRef.current.value = ""; } setImporting(false); setFile(undefined); setImportDetails(undefined); } }, [t, file, collections, showToast] ); const handleFilePicked = React.useCallback(async (ev) => { ev.preventDefault(); const files = getDataTransferFiles(ev); const file = files[0]; setFile(file); try { setImportDetails(await parseOutlineExport(file)); } catch (err) { setImportDetails([]); } }, []); const handlePickFile = React.useCallback( (ev) => { ev.preventDefault(); if (fileRef.current) { fileRef.current.click(); } }, [fileRef] ); const handleExport = React.useCallback( async (ev: SyntheticEvent<>) => { ev.preventDefault(); setLoading(true); try { await collections.export(); setExporting(true); showToast(t("Export in progress…")); } finally { setLoading(false); } }, [t, collections, showToast] ); const hasCollections = importDetails ? !!importDetails.filter((detail) => detail.type === "collection").length : false; const hasDocuments = importDetails ? !!importDetails.filter((detail) => detail.type === "document").length : false; const isImportable = hasCollections && hasDocuments; return ( } > {t("Import")} It is possible to import a zip file of folders and Markdown files previously exported from an Outline instance. Support will soon be added for importing from other services. {isImported && ( Your file has been uploaded and the import is currently being processed, you can safely leave this page while it completes. )} {file && !isImportable && ( }} /> )} {file && importDetails && isImportable ? ( <> }} /> {importDetails .filter((detail) => detail.type === "collection") .map((detail) => ( {detail.name} ))} ) : ( )} {t("Export")} }} />

Recent exports } renderItem={(item) => ( )} />
); } const List = styled.ul` padding: 0; margin: 8px 0 0; `; const ImportPreview = styled(Notice)` margin-bottom: 16px; `; const ImportPreviewItem = styled.li` display: flex; align-items: center; list-style: none; `; const CollectionName = styled.span` font-weight: 500; margin-left: 4px; `; export default observer(ImportExport);