redesigned share menu
This commit is contained in:
@@ -15,10 +15,11 @@ import Spinner from "../Spinner/Spinner";
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks/store";
|
||||
import { useLocation, useParams } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
import { useDragAndDrop } from "../../hooks/utils";
|
||||
import { useDragAndDrop, useUtils } from "../../hooks/utils";
|
||||
import MultiSelectBar from "../MultiSelectBar/MultiSelectBar";
|
||||
import { useFolder, useFolders } from "../../hooks/folders";
|
||||
import { removeNavigationMap } from "../../reducers/selected";
|
||||
import AlertIcon from "../../icons/AlertIcon";
|
||||
|
||||
const DataForm = memo(
|
||||
({ scrollDivRef }: { scrollDivRef: React.RefObject<HTMLDivElement> }) => {
|
||||
@@ -42,6 +43,7 @@ const DataForm = memo(
|
||||
const navigationMap = useAppSelector((state) => {
|
||||
return state.selected.navigationMap[location.pathname];
|
||||
});
|
||||
const { isTrash } = useUtils();
|
||||
|
||||
const isLoading =
|
||||
isLoadingFiles ||
|
||||
@@ -114,6 +116,19 @@ const DataForm = memo(
|
||||
|
||||
<QuickAccess />
|
||||
|
||||
{isTrash && (
|
||||
<div
|
||||
className="bg-primary p-4 rounded-md text-sm
|
||||
text-white mt-4 flex items-center select-none"
|
||||
>
|
||||
<AlertIcon className="w-6 h-6 min-h-6 min-w-6 mr-2" />
|
||||
<span>
|
||||
Items in the trash may be automatically deleted depending on
|
||||
the servers settings
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Folders scrollDivRef={scrollDivRef} />
|
||||
|
||||
<Files />
|
||||
|
||||
@@ -24,7 +24,7 @@ const Header = () => {
|
||||
return (
|
||||
<header
|
||||
id="header"
|
||||
className="select-none border border-b fixed top-0 left-0 w-full bg-white z-10"
|
||||
className="select-none border-b fixed top-0 left-0 w-full bg-white z-10"
|
||||
>
|
||||
<div className="px-6 flex justify-between min-h-16 items-center py-3.5">
|
||||
<div className="items-center w-[260px] hidden desktopMode:flex">
|
||||
|
||||
@@ -231,7 +231,7 @@ const MoverPopup = () => {
|
||||
{folderList?.map((folder: FolderInterface) => (
|
||||
<div
|
||||
className={classNames(
|
||||
"p-2 border-b border-[#ebe9f9] rounded-md mt-1 flex flex-row items-center",
|
||||
"p-2 border-b border-[#ebe9f9] rounded-md flex flex-row items-center",
|
||||
{
|
||||
"bg-primary text-white hover:bg-primary-hover":
|
||||
selectedFolder?._id === folder._id,
|
||||
@@ -261,7 +261,7 @@ const MoverPopup = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<div className="mt-4 border border-gray-secondary rounded-md p-4">
|
||||
<p className="text-sm overflow-hidden text-ellipsis whitespace-nowrap select-none">
|
||||
{moveText}
|
||||
</p>
|
||||
|
||||
@@ -50,6 +50,7 @@ const SearchBar = memo(() => {
|
||||
|
||||
const onSearch = (e: any) => {
|
||||
e.preventDefault();
|
||||
setShowSuggestions(false);
|
||||
if (isMedia) {
|
||||
if (searchText.length) {
|
||||
navigate(`/search-media/${searchText}`);
|
||||
|
||||
@@ -3,11 +3,6 @@ import CloseIcon from "../../icons/CloseIcon";
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks/store";
|
||||
import { getFileColor, getFileExtension } from "../../utils/files";
|
||||
import bytes from "bytes";
|
||||
import {
|
||||
makeOneTimePublicPopup,
|
||||
makePublicPopup,
|
||||
removeLinkPopup,
|
||||
} from "../../popups/file";
|
||||
import {
|
||||
makeOneTimePublicAPI,
|
||||
makePublicAPI,
|
||||
@@ -21,11 +16,17 @@ import {
|
||||
} from "../../reducers/selected";
|
||||
import { toast } from "react-toastify";
|
||||
import dayjs from "dayjs";
|
||||
import LockIcon from "../../icons/LockIcon";
|
||||
import OneIcon from "../../icons/OneIcon";
|
||||
import PublicIcon from "../../icons/PublicIcon";
|
||||
|
||||
const SharePopup = memo(() => {
|
||||
const file = useAppSelector((state) => state.selected.shareModal.file)!;
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [shareLink, setShareLink] = useState("");
|
||||
const [shareType, setShareType] = useState<"private" | "public" | "one">(
|
||||
"private"
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
const { refetch: refetchFiles } = useFiles(false);
|
||||
const { refetch: refetchQuickFiles } = useQuickFiles(false);
|
||||
@@ -43,8 +44,6 @@ const SharePopup = memo(() => {
|
||||
|
||||
const makePublic = async () => {
|
||||
try {
|
||||
const result = await makePublicPopup();
|
||||
if (!result) return;
|
||||
setUpdating(true);
|
||||
const { file: updatedFile } = await toast.promise(
|
||||
makePublicAPI(file._id),
|
||||
@@ -74,8 +73,6 @@ const SharePopup = memo(() => {
|
||||
|
||||
const makeOneTimePublic = async () => {
|
||||
try {
|
||||
const result = await makeOneTimePublicPopup();
|
||||
if (!result) return;
|
||||
setUpdating(true);
|
||||
const { file: updatedFile } = await toast.promise(
|
||||
makeOneTimePublicAPI(file._id),
|
||||
@@ -105,8 +102,6 @@ const SharePopup = memo(() => {
|
||||
|
||||
const removeLink = async () => {
|
||||
try {
|
||||
const result = await removeLinkPopup();
|
||||
if (!result) return;
|
||||
setUpdating(true);
|
||||
const updatedFile = await toast.promise(removeLinkAPI(file._id), {
|
||||
pending: "Removing Link...",
|
||||
@@ -133,6 +128,7 @@ const SharePopup = memo(() => {
|
||||
};
|
||||
|
||||
const copyLink = () => {
|
||||
if (shareType === "private") return;
|
||||
navigator.clipboard.writeText(shareLink);
|
||||
toast.success("Link Copied");
|
||||
};
|
||||
@@ -146,11 +142,47 @@ const SharePopup = memo(() => {
|
||||
closeShareModal();
|
||||
};
|
||||
|
||||
const permissionText = (() => {
|
||||
if (shareType === "one") {
|
||||
return `This file will be available for download one time,
|
||||
after it is downloaded once the file will then automatically be marked as private.`;
|
||||
} else if (shareType === "public") {
|
||||
return "Anyone with the link can view and download this file";
|
||||
} else {
|
||||
return "Only you can view and download this file";
|
||||
}
|
||||
})();
|
||||
|
||||
const linkPreviewText = (() => {
|
||||
if (shareType === "private") {
|
||||
return "Document is private";
|
||||
} else {
|
||||
return shareLink;
|
||||
}
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
if (!file.metadata.link) return;
|
||||
const url = `${window.location.origin}/public-download/${file._id}/${file.metadata.link}`;
|
||||
setShareLink(url);
|
||||
}, [file._id, file.metadata.link]);
|
||||
setShareType(file.metadata.linkType ? file.metadata.linkType : "private");
|
||||
}, [file._id, file.metadata.link, file.metadata.linkType]);
|
||||
|
||||
const handleSelectChange = async (value: string) => {
|
||||
if (value === "private") {
|
||||
await removeLink();
|
||||
} else if (value === "one") {
|
||||
await makeOneTimePublic();
|
||||
} else if (value === "public") {
|
||||
await makePublic();
|
||||
}
|
||||
};
|
||||
|
||||
const selectOnChange = (e: any) => {
|
||||
const value = e.target.value;
|
||||
setShareType(value);
|
||||
handleSelectChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -183,90 +215,40 @@ const SharePopup = memo(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[90%] sm:w-[400px] p-4 bg-white rounded-md">
|
||||
<p className="text-lg mb-4 text-center">Share file</p>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Type
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{fileExtension}
|
||||
</span>
|
||||
<div className="w-[90%] sm:w-[500px] p-6 bg-white rounded-md">
|
||||
<p>Share file</p>
|
||||
<div className="bg-light-primary p-6 rounded-md mt-4 flex items-center space-x-2">
|
||||
<input
|
||||
className="rounded-md w-full text-xs h-10 p-2"
|
||||
value={linkPreviewText}
|
||||
/>
|
||||
<button
|
||||
className="bg-primary text-white hover:bg-primary-hover text-xs w-20 p-1 py-3 rounded-md"
|
||||
onClick={copyLink}
|
||||
>
|
||||
Copy link
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Size
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{fileSize}
|
||||
</span>
|
||||
<p className="mt-6">Permission</p>
|
||||
<div className="flex mt-6 items-center">
|
||||
{shareType === "private" && <LockIcon className="w-5 h-5 mr-2" />}
|
||||
{shareType === "one" && <OneIcon className="w-5 h-5 mr-2" />}
|
||||
{shareType === "public" && <PublicIcon className="w-5 h-5 mr-2" />}
|
||||
<select
|
||||
className="text-sm"
|
||||
value={shareType}
|
||||
onChange={selectOnChange}
|
||||
disabled={updating}
|
||||
>
|
||||
<option value="private">Private</option>
|
||||
<option value="public">Public</option>
|
||||
<option value="one">Temporary</option>
|
||||
</select>
|
||||
{updating && (
|
||||
<div className="border-t border-primary rounded-full animate-spin h-4 w-4 ml-2"></div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Created
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{formattedDate}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-between">
|
||||
<span className="text-[#637381] text-[13px] font-normal leading-[20px] min-w-[50px]">
|
||||
Access
|
||||
</span>
|
||||
<span className="text-[#212b36] text-[13px] font-normal leading-[20px]">
|
||||
{file.metadata.link ? "Public" : "Private"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{shareLink && (
|
||||
<div className="relative">
|
||||
<input
|
||||
placeholder="Share link"
|
||||
className="w-full h-[48px] pl-[12px] pr-[53px] text-black border border-[#637381] rounded-[5px] outline-none text-[15px] mt-4"
|
||||
value={shareLink}
|
||||
readOnly={true}
|
||||
/>
|
||||
<div className="absolute right-0 top-0 bottom-0 flex items-center justify-center">
|
||||
<a
|
||||
className="text-[#3c85ee] text-[15px] font-medium no-underline mr-2 mt-4 cursor-pointer"
|
||||
onClick={copyLink}
|
||||
>
|
||||
Copy
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!shareLink && (
|
||||
<div className="flex justify-between mt-4">
|
||||
<button
|
||||
className="py-2 px-4 inline-flex items-center justify-center border border-[#3c85ee] rounded-[4px] text-[#3c85ee] text-[15px] font-medium no-underline animate cursor-pointer hover:bg-[#f6f5fd] disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={makePublic}
|
||||
disabled={updating}
|
||||
>
|
||||
Share Indefinitely
|
||||
</button>
|
||||
<button
|
||||
className="py-2 px-4 inline-flex items-center justify-center border border-[#3c85ee] rounded-[4px] text-[#3c85ee] text-[15px] font-medium no-underline animate cursor-pointer hover:bg-[#f6f5fd] disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={makeOneTimePublic}
|
||||
disabled={updating}
|
||||
>
|
||||
Single-Use Link
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shareLink && (
|
||||
<div className="flex justify-center mt-4">
|
||||
<button
|
||||
className="py-2 px-4 inline-flex items-center justify-center border border-[#3c85ee] rounded-[4px] text-[#3c85ee] text-[15px] font-medium no-underline animate cursor-pointer hover:bg-[#f6f5fd] disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
onClick={removeLink}
|
||||
disabled={updating}
|
||||
>
|
||||
Make Private
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-xs mt-1.5 text-gray-500">{permissionText}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
type LockIconType = React.SVGAttributes<SVGSVGElement>;
|
||||
|
||||
const LockIcon: React.FC<LockIconType> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
fill="currentColor"
|
||||
>
|
||||
<title>lock-outline</title>
|
||||
<path d="M12,17C10.89,17 10,16.1 10,15C10,13.89 10.89,13 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10C4,8.89 4.89,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default LockIcon;
|
||||
@@ -0,0 +1,15 @@
|
||||
const OneIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
fill="currentColor"
|
||||
>
|
||||
<title>numeric-1-circle-outline</title>
|
||||
<path d="M10,7H14V17H12V9H10V7M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default OneIcon;
|
||||
@@ -0,0 +1,15 @@
|
||||
const PublicIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
fill="currentColor"
|
||||
>
|
||||
<title>earth</title>
|
||||
<path d="M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicIcon;
|
||||
@@ -14,6 +14,7 @@ module.exports = {
|
||||
"gray-primary": "#637381",
|
||||
"gray-secondary": "#e8eef2",
|
||||
"gray-third": "#ebe9f9",
|
||||
"light-primary": "rgba(60, 133, 238, 0.4)",
|
||||
},
|
||||
},
|
||||
screens: {
|
||||
|
||||
Reference in New Issue
Block a user