diff --git a/packages/dashboard-v2/src/components/FileList/FileList.js b/packages/dashboard-v2/src/components/FileList/FileList.js
index 6342b970..ad6087ed 100644
--- a/packages/dashboard-v2/src/components/FileList/FileList.js
+++ b/packages/dashboard-v2/src/components/FileList/FileList.js
@@ -1,29 +1,40 @@
-import * as React from "react";
+import { useState } from "react";
import useSWR from "swr";
import { useMedia } from "react-use";
import theme from "../../lib/theme";
import { ContainerLoadingIndicator } from "../LoadingIndicator";
-import { Select, SelectOption } from "../Select";
-import { Switch } from "../Switch";
-import { TextInputIcon } from "../TextInputIcon/TextInputIcon";
-import { SearchIcon } from "../Icons";
import FileTable from "./FileTable";
import useFormattedFilesData from "./useFormattedFilesData";
+import { MobileFileList } from "./MobileFileList";
+import { Pagination } from "./Pagination";
+
+const PAGE_SIZE = 10;
const FileList = ({ type }) => {
const isMediumScreenOrLarger = useMedia(`(min-width: ${theme.screens.md})`);
- const { data, error } = useSWR(`user/${type}?pageSize=10`);
+ const [offset, setOffset] = useState(0);
+ const baseUrl = `user/${type}?pageSize=${PAGE_SIZE}`;
+ const {
+ data,
+ error,
+ mutate: refreshList,
+ } = useSWR(`${baseUrl}&offset=${offset}`, {
+ revalidateOnMount: true,
+ });
const items = useFormattedFilesData(data?.items || []);
+ const count = data?.count || 0;
- const setFilter = (name, value) => console.log("filter", name, "set to", value);
+ // Next page preloading
+ const hasMoreRecords = data ? data.offset + data.pageSize < data.count : false;
+ const nextPageOffset = hasMoreRecords ? data.offset + data.pageSize : offset;
+ useSWR(`${baseUrl}&offset=${nextPageOffset}`);
if (!items.length) {
return (
- {/* TODO: proper error message */}
{!data && !error &&
}
{!data && error &&
An error occurred while loading this data.
}
{data &&
No {type} found.
}
@@ -32,42 +43,14 @@ const FileList = ({ type }) => {
}
return (
-
-
-
}
- onChange={console.log.bind(console)}
- />
-
-
setFilter("showSmallFiles", value)} className="mr-8">
-
- Show small files
-
-
-
- File type:
-
-
-
- Sort:
-
-
-
-
- {/* TODO: mobile view (it's not tabular) */}
- {isMediumScreenOrLarger ?
: "Mobile view"}
-
+ <>
+ {isMediumScreenOrLarger ? (
+
+ ) : (
+
+ )}
+
+ >
);
};
diff --git a/packages/dashboard-v2/src/components/FileList/FileTable.js b/packages/dashboard-v2/src/components/FileList/FileTable.js
index c2f133d6..88477648 100644
--- a/packages/dashboard-v2/src/components/FileList/FileTable.js
+++ b/packages/dashboard-v2/src/components/FileList/FileTable.js
@@ -2,110 +2,78 @@ import { CogIcon, ShareIcon } from "../Icons";
import { PopoverMenu } from "../PopoverMenu/PopoverMenu";
import { Table, TableBody, TableCell, TableHead, TableHeadCell, TableRow } from "../Table";
import { CopyButton } from "../CopyButton";
+import { useSkylinkOptions } from "./useSkylinkOptions";
+import { useSkylinkSharing } from "./useSkylinkSharing";
-const buildShareMenu = (item) => {
- return [
- {
- label: "Facebook",
- callback: () => {
- console.info("share to Facebook", item);
- },
- },
- {
- label: "Twitter",
- callback: () => {
- console.info("share to Twitter", item);
- },
- },
- {
- label: "Discord",
- callback: () => {
- console.info("share to Discord", item);
- },
- },
- ];
-};
+const SkylinkOptionsMenu = ({ skylink, onUpdated }) => {
+ const { inProgress, options } = useSkylinkOptions({ skylink, onUpdated });
-const buildOptionsMenu = (item) => {
- return [
- {
- label: "Preview",
- callback: () => {
- console.info("preview", item);
- },
- },
- {
- label: "Download",
- callback: () => {
- console.info("download", item);
- },
- },
- {
- label: "Unpin",
- callback: () => {
- console.info("unpin", item);
- },
- },
- {
- label: "Report",
- callback: () => {
- console.info("report", item);
- },
- },
- ];
-};
-
-export default function FileTable({ items }) {
return (
-
-
-
- Name
- Type
-
- Size
-
- Uploaded
- Skylink
- Activity
-
-
-
- {items.map((item) => {
- const { id, name, type, size, date, skylink } = item;
+
+
+
+ );
+};
- return (
-
- {name}
- {type}
-
- {size}
-
- {date}
-
-
-
- {skylink}
-
-
-
-
-
-
- );
- })}
-
-
+const SkylinkSharingMenu = ({ skylink }) => {
+ const { options } = useSkylinkSharing(skylink);
+
+ return (
+
+
+
+ );
+};
+
+export default function FileTable({ items, onUpdated }) {
+ return (
+
+
+
+
+ Name
+ Type
+
+ Size
+
+ Uploaded
+ Skylink
+ Activity
+
+
+
+ {items.map((item) => {
+ const { id, name, type, size, date, skylink } = item;
+
+ return (
+
+ {name}
+ {type}
+
+ {size}
+
+ {date}
+
+
+
+ {skylink}
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+
+
);
}
diff --git a/packages/dashboard-v2/src/components/FileList/MobileFileList.js b/packages/dashboard-v2/src/components/FileList/MobileFileList.js
new file mode 100644
index 00000000..bd11aa10
--- /dev/null
+++ b/packages/dashboard-v2/src/components/FileList/MobileFileList.js
@@ -0,0 +1,84 @@
+import { useState } from "react";
+import cn from "classnames";
+
+import { ChevronDownIcon } from "../Icons";
+import { useSkylinkSharing } from "./useSkylinkSharing";
+import { ContainerLoadingIndicator } from "../LoadingIndicator";
+import { useSkylinkOptions } from "./useSkylinkOptions";
+
+const SharingMenu = ({ skylink }) => {
+ const { options } = useSkylinkSharing(skylink);
+
+ return (
+
+ {options.map(({ label, callback }, index) => (
+
+ ))}
+
+ );
+};
+
+const OptionsMenu = ({ skylink, onUpdated }) => {
+ const { inProgress, options } = useSkylinkOptions({ skylink, onUpdated });
+
+ return (
+
+
+ {options.map(({ label, callback }, index) => (
+
+ ))}
+
+ {inProgress && (
+
+ )}
+
+ );
+};
+
+const ListItem = ({ item, onUpdated }) => {
+ const { name, type, size, date, skylink } = item;
+ const [open, setOpen] = useState(false);
+
+ const toggle = () => setOpen((open) => !open);
+
+ return (
+
+
+
+
{name}
+
+ {type}
+ {size}
+ {date}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const MobileFileList = ({ items, onUpdated }) => {
+ return (
+
+ {items.map((item) => (
+
+ ))}
+
+ );
+};
diff --git a/packages/dashboard-v2/src/components/FileList/Pagination.js b/packages/dashboard-v2/src/components/FileList/Pagination.js
new file mode 100644
index 00000000..248c03a3
--- /dev/null
+++ b/packages/dashboard-v2/src/components/FileList/Pagination.js
@@ -0,0 +1,32 @@
+import { Button } from "../Button";
+
+export const Pagination = ({ count, offset, setOffset, pageSize }) => {
+ const start = count ? offset + 1 : 0;
+ const end = offset + pageSize > count ? count : offset + pageSize;
+
+ const showPaginationButtons = offset > 0 || count > end;
+
+ return (
+
+ );
+};
diff --git a/packages/dashboard-v2/src/components/FileList/useSkylinkOptions.js b/packages/dashboard-v2/src/components/FileList/useSkylinkOptions.js
new file mode 100644
index 00000000..ad116cd4
--- /dev/null
+++ b/packages/dashboard-v2/src/components/FileList/useSkylinkOptions.js
@@ -0,0 +1,35 @@
+import { useMemo, useState } from "react";
+
+import accountsService from "../../services/accountsService";
+import skynetClient from "../../services/skynetClient";
+
+export const useSkylinkOptions = ({ skylink, onUpdated }) => {
+ const [inProgress, setInProgress] = useState(false);
+
+ const options = useMemo(
+ () => [
+ {
+ label: "Preview",
+ callback: async () => window.open(await skynetClient.getSkylinkUrl(skylink)),
+ },
+ {
+ label: "Download",
+ callback: () => skynetClient.downloadFile(skylink),
+ },
+ {
+ label: "Unpin",
+ callback: async () => {
+ setInProgress(true);
+ await accountsService.delete(`user/uploads/${skylink}`);
+ await onUpdated(); // No need to setInProgress(false), since at this point this hook should already be unmounted
+ },
+ },
+ ],
+ [skylink, onUpdated]
+ );
+
+ return {
+ inProgress,
+ options,
+ };
+};
diff --git a/packages/dashboard-v2/src/components/FileList/useSkylinkSharing.js b/packages/dashboard-v2/src/components/FileList/useSkylinkSharing.js
new file mode 100644
index 00000000..0b09302b
--- /dev/null
+++ b/packages/dashboard-v2/src/components/FileList/useSkylinkSharing.js
@@ -0,0 +1,40 @@
+import { useEffect, useMemo, useState } from "react";
+import copy from "copy-text-to-clipboard";
+
+import skynetClient from "../../services/skynetClient";
+
+const COPY_LINK_LABEL = "Copy link";
+
+export const useSkylinkSharing = (skylink) => {
+ const [copied, setCopied] = useState(false);
+ const [copyLabel, setCopyLabel] = useState(COPY_LINK_LABEL);
+
+ useEffect(() => {
+ if (copied) {
+ setCopyLabel("Copied!");
+
+ const timeout = setTimeout(() => setCopied(false), 1500);
+
+ return () => clearTimeout(timeout);
+ } else {
+ setCopyLabel(COPY_LINK_LABEL);
+ }
+ }, [copied]);
+
+ const options = useMemo(
+ () => [
+ {
+ label: copyLabel,
+ callback: async () => {
+ setCopied(true);
+ copy(await skynetClient.getSkylinkUrl(skylink));
+ },
+ },
+ ],
+ [skylink, copyLabel]
+ );
+
+ return {
+ options,
+ };
+};
diff --git a/packages/dashboard-v2/src/components/PopoverMenu/PopoverMenu.js b/packages/dashboard-v2/src/components/PopoverMenu/PopoverMenu.js
index 1826cd92..dd5a8597 100644
--- a/packages/dashboard-v2/src/components/PopoverMenu/PopoverMenu.js
+++ b/packages/dashboard-v2/src/components/PopoverMenu/PopoverMenu.js
@@ -2,6 +2,9 @@ import { Children, cloneElement, useRef, useState } from "react";
import PropTypes from "prop-types";
import { useClickAway } from "react-use";
import styled, { css, keyframes } from "styled-components";
+import cn from "classnames";
+
+import { ContainerLoadingIndicator } from "../LoadingIndicator";
const dropDown = keyframes`
0% {
@@ -41,15 +44,15 @@ const Option = styled.li.attrs({
hover:before:content-['']`,
})``;
-export const PopoverMenu = ({ options, children, openClassName, ...props }) => {
+export const PopoverMenu = ({ options, children, openClassName, inProgress, ...props }) => {
const containerRef = useRef();
const [open, setOpen] = useState(false);
useClickAway(containerRef, () => setOpen(false));
- const handleChoice = (callback) => () => {
+ const handleChoice = (callback) => async () => {
+ await callback();
setOpen(false);
- callback();
};
return (
@@ -62,11 +65,16 @@ export const PopoverMenu = ({ options, children, openClassName, ...props }) => {
)}
{open && (
- {options.map(({ label, callback }) => (
-
- ))}
+
+ {options.map(({ label, callback }) => (
+
+ ))}
+ {inProgress && (
+
+ )}
+
)}
@@ -87,4 +95,9 @@ PopoverMenu.propTypes = {
callback: PropTypes.func.isRequired,
})
).isRequired,
+
+ /**
+ * If true, a loading icon will be displayed to signal an async action is taking place.
+ */
+ inProgress: PropTypes.bool,
};
diff --git a/packages/dashboard-v2/src/pages/files.js b/packages/dashboard-v2/src/pages/files.js
index be856d4a..b927c09f 100644
--- a/packages/dashboard-v2/src/pages/files.js
+++ b/packages/dashboard-v2/src/pages/files.js
@@ -12,7 +12,7 @@ const FilesPage = () => {
Files
-
+
>
diff --git a/packages/dashboard-v2/tailwind.config.js b/packages/dashboard-v2/tailwind.config.js
index 636cd40e..141d689e 100644
--- a/packages/dashboard-v2/tailwind.config.js
+++ b/packages/dashboard-v2/tailwind.config.js
@@ -29,6 +29,7 @@ module.exports = {
textColor: (theme) => ({ ...theme("colors"), ...colors }),
placeholderColor: (theme) => ({ ...theme("colors"), ...colors }),
outlineColor: (theme) => ({ ...theme("colors"), ...colors }),
+ divideColor: (theme) => ({ ...theme("colors"), ...colors }),
extend: {
fontFamily: {
sans: ["Sora", ...defaultTheme.fontFamily.sans],