Skip to content

Commit

Permalink
feat: Import improvements (#3064)
Browse files Browse the repository at this point in the history
* feat: Split and simplify import/export pages in prep for more options

* minor fixes

* File operations for imports

* test

* icons
  • Loading branch information
tommoor committed Feb 7, 2022
1 parent a4e9251 commit d643c94
Show file tree
Hide file tree
Showing 27 changed files with 620 additions and 453 deletions.
1 change: 1 addition & 0 deletions app/components/DocumentViews.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ function DocumentViews({ document, isOpen }: Props) {
subtitle={subtitle}
image={<Avatar key={item.id} src={item.avatarUrl} size={32} />}
border={false}
compact
small
/>
);
Expand Down
10 changes: 6 additions & 4 deletions app/components/List/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Props = {
title: React.ReactNode;
subtitle?: React.ReactNode;
actions?: React.ReactNode;
compact?: boolean;
border?: boolean;
small?: boolean;
};
Expand Down Expand Up @@ -49,6 +50,7 @@ const ListItem = (
<Wrapper
ref={ref}
$border={border}
$compact={compact}
activeStyle={{
background: theme.primary,
}}
Expand All @@ -62,16 +64,16 @@ const ListItem = (
}

return (
<Wrapper $border={border} {...rest}>
<Wrapper $compact={compact} $border={border} {...rest}>
{content(false)}
</Wrapper>
);
};

const Wrapper = styled.div<{ $border?: boolean }>`
const Wrapper = styled.div<{ $compact?: boolean; $border?: boolean }>`
display: flex;
padding: ${(props) => (props.$border === false ? 0 : "8px 0")};
margin: ${(props) => (props.$border === false ? "8px 0" : 0)};
margin: ${(props) => (props.$compact === false ? 0 : "8px 0")};
padding: ${(props) => (props.$compact === false ? "8px 0" : 0)};
border-bottom: 1px solid
${(props) =>
props.$border === false ? "transparent" : props.theme.divider};
Expand Down
16 changes: 12 additions & 4 deletions app/components/Sidebar/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { observer } from "mobx-react";
import {
DocumentIcon,
NewDocumentIcon,
EmailIcon,
ProfileIcon,
PadlockIcon,
Expand All @@ -11,6 +11,7 @@ import {
TeamIcon,
ExpandedIcon,
BeakerIcon,
DownloadIcon,
} from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
Expand Down Expand Up @@ -118,11 +119,18 @@ function SettingsSidebar() {
icon={<LinkIcon color="currentColor" />}
label={t("Share Links")}
/>
{can.manage && (
<SidebarLink
to="/settings/import"
icon={<NewDocumentIcon color="currentColor" />}
label={t("Import")}
/>
)}
{can.export && (
<SidebarLink
to="/settings/import-export"
icon={<DocumentIcon color="currentColor" />}
label={`${t("Import")} / ${t("Export")}`}
to="/settings/export"
icon={<DownloadIcon color="currentColor" />}
label={t("Export")}
/>
)}
</Section>
Expand Down
12 changes: 7 additions & 5 deletions app/components/SocketProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -328,15 +328,17 @@ class SocketProvider extends React.Component<Props> {
}
});

this.socket.on("fileOperations.update", async (event: any) => {
this.socket.on("fileOperations.create", async (event: any) => {
const user = auth.user;
let collection = null;
if (event.collectionId) {
collection = await collections.fetch(event.collectionId);
if (user) {
fileOperations.add({ ...event, user });
}
});

this.socket.on("fileOperations.update", async (event: any) => {
const user = auth.user;
if (user) {
fileOperations.add({ ...event, user, collection });
fileOperations.add({ ...event, user });
}
});

Expand Down
59 changes: 59 additions & 0 deletions app/components/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from "react";
import styled from "styled-components";

export default function Spinner(props: React.HTMLAttributes<HTMLOrSVGElement>) {
return (
<SVG
width="16px"
height="16px"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<Circle
fill="none"
strokeWidth="2"
strokeLinecap="round"
cx="8"
cy="8"
r="6"
></Circle>
</SVG>
);
}

const SVG = styled.svg`
@keyframes rotator {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(270deg);
}
}
animation: rotator 1.4s linear infinite;
margin: 4px;
`;

const Circle = styled.circle`
@keyframes dash {
0% {
stroke-dashoffset: 47;
}
50% {
stroke-dashoffset: 11;
transform: rotate(135deg);
}
100% {
stroke-dashoffset: 47;
transform: rotate(450deg);
}
}
stroke: ${(props) => props.theme.textSecondary};
stroke-dasharray: 46;
stroke-dashoffset: 0;
transform-origin: center;
animation: dash 1.4s ease-in-out infinite;
`;
9 changes: 6 additions & 3 deletions app/models/FileOperation.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { computed } from "mobx";
import BaseModal from "./BaseModel";
import Collection from "./Collection";
import User from "./User";

class FileOperation extends BaseModal {
id: string;

state: string;

collection: Collection | null | undefined;
name: string;

error: string | null;

collectionId: string | null;

size: number;

type: string;
type: "import" | "export";

user: User;

Expand Down
9 changes: 7 additions & 2 deletions app/routes/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as React from "react";
import { Switch, Redirect } from "react-router-dom";
import Details from "~/scenes/Settings/Details";
import Export from "~/scenes/Settings/Export";
import Features from "~/scenes/Settings/Features";
import Groups from "~/scenes/Settings/Groups";
import ImportExport from "~/scenes/Settings/ImportExport";
import Import from "~/scenes/Settings/Import";
import Notifications from "~/scenes/Settings/Notifications";
import People from "~/scenes/Settings/People";
import Profile from "~/scenes/Settings/Profile";
Expand Down Expand Up @@ -33,7 +34,11 @@ export default function SettingsRoutes() {
{isHosted && (
<Route exact path="/settings/integrations/zapier" component={Zapier} />
)}
<Route exact path="/settings/import-export" component={ImportExport} />
<Route exact path="/settings/import" component={Import} />
<Route exact path="/settings/export" component={Export} />

{/* old routes */}
<Redirect from="/settings/import-export" to="/settings/export" />
<Redirect from="/settings/people" to="/settings/members" />
</Switch>
);
Expand Down
105 changes: 105 additions & 0 deletions app/scenes/Settings/Export.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { observer } from "mobx-react";
import { DownloadIcon } from "outline-icons";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import FileOperation from "~/models/FileOperation";
import Button from "~/components/Button";
import Heading from "~/components/Heading";
import HelpText from "~/components/HelpText";
import PaginatedList from "~/components/PaginatedList";
import Scene from "~/components/Scene";
import Subheading from "~/components/Subheading";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import FileOperationListItem from "./components/FileOperationListItem";

function Export() {
const { t } = useTranslation();
const user = useCurrentUser();
const { fileOperations, collections } = useStores();
const { showToast } = useToasts();
const [isLoading, setLoading] = React.useState(false);
const [isExporting, setExporting] = React.useState(false);

const handleExport = React.useCallback(
async (ev: React.SyntheticEvent) => {
ev.preventDefault();
setLoading(true);

try {
await collections.export();
setExporting(true);
showToast(t("Export in progress…"));
} finally {
setLoading(false);
}
},
[t, collections, showToast]
);

const handleDelete = React.useCallback(
async (fileOperation: FileOperation) => {
try {
await fileOperations.delete(fileOperation);
showToast(t("Export deleted"));
} catch (err) {
showToast(err.message, {
type: "error",
});
}
},
[fileOperations, showToast, t]
);

return (
<Scene title={t("Export")} icon={<DownloadIcon color="currentColor" />}>
<Heading>{t("Export")}</Heading>
<HelpText>
<Trans
defaults="A full export might take some time, consider exporting a single document or collection. The exported data is a zip of your documents in Markdown format. You may leave this page once the export has started – we will email a link to <em>{{ userEmail }}</em> when it’s complete."
values={{
userEmail: user.email,
}}
components={{
em: <strong />,
}}
/>
</HelpText>
<Button
type="submit"
onClick={handleExport}
disabled={isLoading || isExporting}
primary
>
{isExporting
? t("Export Requested")
: isLoading
? `${t("Requesting Export")}…`
: t("Export Data")}
</Button>
<br />
<PaginatedList
items={fileOperations.exports}
fetch={fileOperations.fetchPage}
options={{
type: "export",
}}
heading={
<Subheading>
<Trans>Recent exports</Trans>
</Subheading>
}
renderItem={(item) => (
<FileOperationListItem
key={item.id}
fileOperation={item}
handleDelete={handleDelete}
/>
)}
/>
</Scene>
);
}

export default observer(Export);
Loading

0 comments on commit d643c94

Please sign in to comment.