Allow setting permission of collections during import (#6799)
* Allow setting permission of collections during import closes #6767 * Remove unused column
This commit is contained in:
@@ -6,9 +6,13 @@ import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import { AttachmentPreset } from "@shared/types";
|
||||
import { AttachmentPreset, CollectionPermission } from "@shared/types";
|
||||
import { bytesToHumanReadable } from "@shared/utils/files";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import InputSelectPermission from "~/components/InputSelectPermission";
|
||||
import LoadingIndicator from "~/components/LoadingIndicator";
|
||||
import Text from "~/components/Text";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { uploadFile } from "~/utils/files";
|
||||
|
||||
@@ -23,38 +27,43 @@ type Props = {
|
||||
function DropToImport({ disabled, onSubmit, children, format }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { collections } = useStores();
|
||||
const [file, setFile] = React.useState<File | null>(null);
|
||||
const [isImporting, setImporting] = React.useState(false);
|
||||
const [permission, setPermission] =
|
||||
React.useState<CollectionPermission | null>(CollectionPermission.ReadWrite);
|
||||
|
||||
const handleFiles = React.useCallback(
|
||||
async (files) => {
|
||||
if (files.length > 1) {
|
||||
toast.error(t("Please choose a single file to import"));
|
||||
return;
|
||||
}
|
||||
const file = files[0];
|
||||
const handleFiles = (files: File[]) => {
|
||||
if (files.length > 1) {
|
||||
toast.error(t("Please choose a single file to import"));
|
||||
return;
|
||||
}
|
||||
setFile(files[0]);
|
||||
};
|
||||
|
||||
setImporting(true);
|
||||
const handleStartImport = async () => {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
setImporting(true);
|
||||
|
||||
try {
|
||||
const attachment = await uploadFile(file, {
|
||||
name: file.name,
|
||||
preset: AttachmentPreset.WorkspaceImport,
|
||||
});
|
||||
await collections.import(attachment.id, format);
|
||||
onSubmit();
|
||||
toast.message(file.name, {
|
||||
description: t(
|
||||
"Your import is being processed, you can safely leave this page"
|
||||
),
|
||||
});
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
},
|
||||
[t, onSubmit, collections, format]
|
||||
);
|
||||
try {
|
||||
const attachment = await uploadFile(file, {
|
||||
name: file.name,
|
||||
preset: AttachmentPreset.WorkspaceImport,
|
||||
});
|
||||
await collections.import(attachment.id, { format, permission });
|
||||
onSubmit();
|
||||
toast.message(file.name, {
|
||||
description: t(
|
||||
"Your import is being processed, you can safely leave this page"
|
||||
),
|
||||
});
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRejection = React.useCallback(() => {
|
||||
toast.error(t("File not supported – please upload a valid ZIP file"));
|
||||
@@ -65,30 +74,53 @@ function DropToImport({ disabled, onSubmit, children, format }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex gap={8} column>
|
||||
{isImporting && <LoadingIndicator />}
|
||||
<Dropzone
|
||||
accept="application/zip, application/x-zip-compressed"
|
||||
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>
|
||||
</>
|
||||
<Text as="p" type="secondary">
|
||||
<Dropzone
|
||||
accept="application/zip, application/x-zip-compressed"
|
||||
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" />
|
||||
{file
|
||||
? t(`${file.name} (${bytesToHumanReadable(file.size)})`)
|
||||
: children}
|
||||
</Flex>
|
||||
</DropzoneContainer>
|
||||
)}
|
||||
</Dropzone>
|
||||
</Text>
|
||||
<div>
|
||||
<InputSelectPermission
|
||||
value={permission}
|
||||
onChange={(value: CollectionPermission) => {
|
||||
setPermission(value);
|
||||
}}
|
||||
/>
|
||||
<Text as="span" type="secondary">
|
||||
{t(
|
||||
"Set the default permission level for collections created from the import"
|
||||
)}
|
||||
.
|
||||
</Text>
|
||||
</div>
|
||||
<Flex justify="flex-end">
|
||||
<Button disabled={!file} onClick={handleStartImport}>
|
||||
{t("Start import")}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,29 +19,33 @@ const HelpDisclosure: React.FC<Props> = ({ title, children }: Props) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<Disclosure {...disclosure}>
|
||||
{(props) => (
|
||||
<Button
|
||||
icon={<QuestionMarkIcon color={theme.text} />}
|
||||
<StyledButton
|
||||
icon={<QuestionMarkIcon color={theme.textSecondary} />}
|
||||
neutral
|
||||
aria-label={title}
|
||||
borderOnHover
|
||||
{...props}
|
||||
>
|
||||
{title}
|
||||
</Button>
|
||||
/>
|
||||
)}
|
||||
</Disclosure>
|
||||
<HelpContent {...disclosure}>
|
||||
<Text as="p" type="secondary">
|
||||
<br />
|
||||
{children}
|
||||
</Text>
|
||||
</HelpContent>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 50px;
|
||||
`;
|
||||
|
||||
const HelpContent = styled(DisclosureContent)`
|
||||
transition: opacity 250ms ease-in-out;
|
||||
opacity: 0;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import { FileOperationFormat } from "@shared/types";
|
||||
import Flex from "~/components/Flex";
|
||||
import Text from "~/components/Text";
|
||||
import env from "~/env";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import DropToImport from "./DropToImport";
|
||||
@@ -13,18 +11,7 @@ function ImportJSONDialog() {
|
||||
const appName = env.APP_NAME;
|
||||
|
||||
return (
|
||||
<Flex column>
|
||||
<Text as="p" type="secondary">
|
||||
<DropToImport
|
||||
onSubmit={dialogs.closeAllModals}
|
||||
format={FileOperationFormat.JSON}
|
||||
>
|
||||
<Trans>
|
||||
Drag and drop the zip file from the JSON export option in{" "}
|
||||
{{ appName }}, 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 the JSON option in another instance. In {{ appName }}, open <em>Export</em> in the Settings sidebar and click on <em>Export Data</em>."
|
||||
@@ -34,7 +21,16 @@ function ImportJSONDialog() {
|
||||
}}
|
||||
/>
|
||||
</HelpDisclosure>
|
||||
</Flex>
|
||||
<DropToImport
|
||||
onSubmit={dialogs.closeAllModals}
|
||||
format={FileOperationFormat.JSON}
|
||||
>
|
||||
<Trans>
|
||||
Drag and drop the zip file from the JSON export option in{" "}
|
||||
{{ appName }}, or click to upload
|
||||
</Trans>
|
||||
</DropToImport>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import { FileOperationFormat } from "@shared/types";
|
||||
import Flex from "~/components/Flex";
|
||||
import Text from "~/components/Text";
|
||||
import env from "~/env";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import DropToImport from "./DropToImport";
|
||||
@@ -13,18 +11,7 @@ function ImportMarkdownDialog() {
|
||||
const appName = env.APP_NAME;
|
||||
|
||||
return (
|
||||
<Flex column>
|
||||
<Text as="p" type="secondary">
|
||||
<DropToImport
|
||||
onSubmit={dialogs.closeAllModals}
|
||||
format={FileOperationFormat.MarkdownZip}
|
||||
>
|
||||
<Trans>
|
||||
Drag and drop the zip file from the Markdown export option in{" "}
|
||||
{{ appName }}, 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>."
|
||||
@@ -33,7 +20,16 @@ function ImportMarkdownDialog() {
|
||||
}}
|
||||
/>
|
||||
</HelpDisclosure>
|
||||
</Flex>
|
||||
<DropToImport
|
||||
onSubmit={dialogs.closeAllModals}
|
||||
format={FileOperationFormat.MarkdownZip}
|
||||
>
|
||||
<Trans>
|
||||
Drag and drop the zip file from the Markdown export option in{" "}
|
||||
{{ appName }}, or click to upload
|
||||
</Trans>
|
||||
</DropToImport>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { FileOperationFormat } from "@shared/types";
|
||||
import Flex from "~/components/Flex";
|
||||
import Text from "~/components/Text";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import DropToImport from "./DropToImport";
|
||||
import HelpDisclosure from "./HelpDisclosure";
|
||||
@@ -12,19 +10,7 @@ function ImportNotionDialog() {
|
||||
const { dialogs } = useStores();
|
||||
|
||||
return (
|
||||
<Flex column>
|
||||
<Text as="p" type="secondary">
|
||||
<DropToImport
|
||||
onSubmit={dialogs.closeAllModals}
|
||||
format={FileOperationFormat.Notion}
|
||||
>
|
||||
<>
|
||||
{t(
|
||||
`Drag and drop the zip file from Notion's HTML export option, or click to upload`
|
||||
)}
|
||||
</>
|
||||
</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."
|
||||
@@ -33,7 +19,17 @@ function ImportNotionDialog() {
|
||||
}}
|
||||
/>
|
||||
</HelpDisclosure>
|
||||
</Flex>
|
||||
<DropToImport
|
||||
onSubmit={dialogs.closeAllModals}
|
||||
format={FileOperationFormat.Notion}
|
||||
>
|
||||
<>
|
||||
{t(
|
||||
`Drag and drop the zip file from Notion's HTML export option, or click to upload`
|
||||
)}
|
||||
</>
|
||||
</DropToImport>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user