adjustible archive formats + finalized settings page

This commit is contained in:
daniel31x13 2023-10-19 00:00:23 -04:00
parent 19467f243f
commit ca3ce7e3de
9 changed files with 189 additions and 85 deletions

View File

@ -239,6 +239,16 @@ export default function LinkDetails({ link, isOwnerOrMod }: Props) {
</div>
<div className="flex text-black dark:text-white gap-1">
<div
onClick={() => handleDownload("png")}
className="cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-2 rounded-md"
>
<FontAwesomeIcon
icon={faCloudArrowDown}
className="w-5 h-5 cursor-pointer text-sky-500 dark:text-sky-500"
/>
</div>
<Link
href={`/api/archives/${link.collectionId}/${link.id}.png`}
target="_blank"
@ -249,16 +259,6 @@ export default function LinkDetails({ link, isOwnerOrMod }: Props) {
className="w-5 h-5 text-sky-500 dark:text-sky-500"
/>
</Link>
<div
onClick={() => handleDownload("png")}
className="cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-2 rounded-md"
>
<FontAwesomeIcon
icon={faCloudArrowDown}
className="w-5 h-5 cursor-pointer text-sky-500 dark:text-sky-500"
/>
</div>
</div>
</div>
@ -272,6 +272,16 @@ export default function LinkDetails({ link, isOwnerOrMod }: Props) {
</div>
<div className="flex text-black dark:text-white gap-1">
<div
onClick={() => handleDownload("pdf")}
className="cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-2 rounded-md"
>
<FontAwesomeIcon
icon={faCloudArrowDown}
className="w-5 h-5 cursor-pointer text-sky-500 dark:text-sky-500"
/>
</div>
<Link
href={`/api/archives/${link.collectionId}/${link.id}.pdf`}
target="_blank"
@ -282,16 +292,6 @@ export default function LinkDetails({ link, isOwnerOrMod }: Props) {
className="w-5 h-5 text-sky-500 dark:text-sky-500"
/>
</Link>
<div
onClick={() => handleDownload("pdf")}
className="cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-2 rounded-md"
>
<FontAwesomeIcon
icon={faCloudArrowDown}
className="w-5 h-5 cursor-pointer text-sky-500 dark:text-sky-500"
/>
</div>
</div>
</div>
@ -301,7 +301,9 @@ export default function LinkDetails({ link, isOwnerOrMod }: Props) {
<FontAwesomeIcon icon={faGlobe} className="w-6 h-6" />
</div>
<p className="text-black dark:text-white">Archive.org Snapshot</p>
<p className="text-black dark:text-white">
Latest archive.org Snapshot
</p>
</div>
<Link

View File

@ -94,14 +94,7 @@ export default function Navbar() {
items={[
{
name: "Settings",
onClick: () => {
setModal({
modal: "ACCOUNT",
state: true,
active: account,
});
setProfileDropdown(!profileDropdown);
},
href: "/settings/account",
},
{
name: `Switch to ${theme === "light" ? "Dark" : "Light"}`,

View File

@ -1,55 +1,72 @@
import { Page, chromium, devices } from "playwright";
import { chromium, devices } from "playwright";
import { prisma } from "@/lib/api/db";
import createFile from "@/lib/api/storage/createFile";
import sendToWayback from "./sendToWayback";
export default async function archive(linkId: number, url: string) {
const browser = await chromium.launch();
const context = await browser.newContext(devices["Desktop Chrome"]);
const page = await context.newPage();
export default async function archive(
linkId: number,
url: string,
userId: number
) {
const user = await prisma.user.findUnique({
where: {
id: userId,
},
});
sendToWayback(url);
if (user?.archiveAsWaybackMachine) sendToWayback(url);
try {
await page.goto(url, { waitUntil: "domcontentloaded" });
if (user?.archiveAsPDF || user?.archiveAsScreenshot) {
const browser = await chromium.launch();
const context = await browser.newContext(devices["Desktop Chrome"]);
const page = await context.newPage();
await page.evaluate(
autoScroll,
Number(process.env.AUTOSCROLL_TIMEOUT) || 30
);
try {
await page.goto(url, { waitUntil: "domcontentloaded" });
const linkExists = await prisma.link.findUnique({
where: {
id: linkId,
},
});
await page.evaluate(
autoScroll,
Number(process.env.AUTOSCROLL_TIMEOUT) || 30
);
if (linkExists) {
const pdf = await page.pdf({
width: "1366px",
height: "1931px",
printBackground: true,
margin: { top: "15px", bottom: "15px" },
});
const screenshot = await page.screenshot({
fullPage: true,
const linkExists = await prisma.link.findUnique({
where: {
id: linkId,
},
});
createFile({
data: screenshot,
filePath: `archives/${linkExists.collectionId}/${linkId}.png`,
});
if (linkExists) {
if (user.archiveAsScreenshot) {
const screenshot = await page.screenshot({
fullPage: true,
});
createFile({
data: pdf,
filePath: `archives/${linkExists.collectionId}/${linkId}.pdf`,
});
createFile({
data: screenshot,
filePath: `archives/${linkExists.collectionId}/${linkId}.png`,
});
}
if (user.archiveAsPDF) {
const pdf = await page.pdf({
width: "1366px",
height: "1931px",
printBackground: true,
margin: { top: "15px", bottom: "15px" },
});
createFile({
data: pdf,
filePath: `archives/${linkExists.collectionId}/${linkId}.pdf`,
});
}
}
await browser.close();
} catch (err) {
console.log(err);
await browser.close();
}
await browser.close();
} catch (err) {
console.log(err);
await browser.close();
}
}

View File

@ -94,7 +94,7 @@ export default async function postLink(
createFolder({ filePath: `archives/${newLink.collectionId}` });
archive(newLink.id, newLink.url);
archive(newLink.id, newLink.url, userId);
return { response: newLink, status: 200 };
}

View File

@ -108,6 +108,9 @@ export default async function updateUser(
username: user.username.toLowerCase(),
email: user.email?.toLowerCase(),
isPrivate: user.isPrivate,
archiveAsScreenshot: user.archiveAsScreenshot,
archiveAsPDF: user.archiveAsPDF,
archiveAsWaybackMachine: user.archiveAsWaybackMachine,
password:
user.newPassword && user.newPassword !== ""
? newHashedPassword

View File

@ -1,34 +1,37 @@
import SettingsLayout from "@/layouts/SettingsLayout";
import { useTheme } from "next-themes";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSun, faMoon } from "@fortawesome/free-solid-svg-icons";
export default function appearance() {
const { theme, setTheme } = useTheme();
const handleToggle = () => {
if (theme === "dark") {
setTheme("light");
} else {
setTheme("dark");
}
};
return (
<SettingsLayout>
<p className="mb-3 text-sm">Select Theme</p>
<div className="flex gap-3 w-full">
<div
className="w-full text-center border-solid border-sky-100 border dark:border-neutral-700 h-40 rounded-md flex items-center justify-center cursor-pointer select-none bg-black text-white"
className={`w-full text-center outline-solid outline-sky-100 outline dark:outline-neutral-700 h-40 duration-100 rounded-md flex items-center justify-center cursor-pointer select-none bg-black ${
theme === "dark"
? "dark:outline-sky-500 text-sky-500"
: "text-white"
}`}
onClick={() => setTheme("dark")}
>
<p>Dark Theme</p>
<FontAwesomeIcon icon={faMoon} className="w-1/2 h-1/2" />
<p className="text-2xl">Dark Theme</p>
{/* <hr className="my-3 border-1 border-sky-100 dark:border-neutral-700" /> */}
{/* <hr className="my-3 outline-1 outline-sky-100 dark:outline-neutral-700" /> */}
</div>
<div
className="w-full text-center border-solid border-sky-100 border dark:border-neutral-700 h-40 rounded-md flex items-center justify-center cursor-pointer select-none bg-white text-black"
className={`w-full text-center outline-solid outline-sky-100 outline dark:outline-neutral-700 h-40 duration-100 rounded-md flex items-center justify-center cursor-pointer select-none bg-white ${
theme === "light" ? "outline-sky-500 text-sky-500" : "text-black"
}`}
onClick={() => setTheme("light")}
>
<p>Light Theme</p>
{/* <hr className="my-3 border-1 border-sky-100 dark:border-neutral-700" /> */}
<FontAwesomeIcon icon={faSun} className="w-1/2 h-1/2" />
<p className="text-2xl">Light Theme</p>
{/* <hr className="my-3 outline-1 outline-sky-100 dark:outline-neutral-700" /> */}
</div>
</div>
</SettingsLayout>

View File

@ -1,10 +1,88 @@
import Checkbox from "@/components/Checkbox";
import SubmitButton from "@/components/SubmitButton";
import SettingsLayout from "@/layouts/SettingsLayout";
import React from "react";
import React, { useEffect, useState } from "react";
import useAccountStore from "@/store/account";
import { toast } from "react-hot-toast";
import { AccountSettings } from "@/types/global";
export default function archive() {
const [submitLoader, setSubmitLoader] = useState(false);
const { account, updateAccount } = useAccountStore();
const [user, setUser] = useState<AccountSettings>(account);
const [archiveAsScreenshot, setArchiveAsScreenshot] =
useState<boolean>(false);
const [archiveAsPDF, setArchiveAsPDF] = useState<boolean>(false);
const [archiveAsWaybackMachine, setArchiveAsWaybackMachine] =
useState<boolean>(false);
useEffect(() => {
setUser({
...account,
archiveAsScreenshot,
archiveAsPDF,
archiveAsWaybackMachine,
});
}, [account, archiveAsScreenshot, archiveAsPDF, archiveAsWaybackMachine]);
function objectIsEmpty(obj: object) {
return Object.keys(obj).length === 0;
}
useEffect(() => {
if (!objectIsEmpty(account)) {
setArchiveAsScreenshot(account.archiveAsScreenshot);
setArchiveAsPDF(account.archiveAsPDF);
setArchiveAsWaybackMachine(account.archiveAsWaybackMachine);
}
}, [account]);
const submit = async () => {
setSubmitLoader(true);
const load = toast.loading("Applying...");
const response = await updateAccount({
...user,
});
toast.dismiss(load);
if (response.ok) {
toast.success("Settings Applied!");
} else toast.error(response.data as string);
setSubmitLoader(false);
};
return (
<SettingsLayout>
<div>archive</div>
<p>Formats to Archive webpages:</p>
<div className="p-3">
<Checkbox
label="Screenshot"
state={archiveAsScreenshot}
onClick={() => setArchiveAsScreenshot(!archiveAsScreenshot)}
/>
<Checkbox
label="PDF"
state={archiveAsPDF}
onClick={() => setArchiveAsPDF(!archiveAsPDF)}
/>
<Checkbox
label="Archive.org Snapshot"
state={archiveAsWaybackMachine}
onClick={() => setArchiveAsWaybackMachine(!archiveAsWaybackMachine)}
/>
</div>
<SubmitButton
onClick={submit}
loading={submitLoader}
label="Save"
className="mt-2 mx-auto lg:mx-0"
/>
</SettingsLayout>
);
}

View File

@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "archiveAsPDF" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "archiveAsScreenshot" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "archiveAsWaybackMachine" BOOLEAN NOT NULL DEFAULT false;

View File

@ -54,6 +54,10 @@ model User {
pinnedLinks Link[]
archiveAsScreenshot Boolean @default(true)
archiveAsPDF Boolean @default(true)
archiveAsWaybackMachine Boolean @default(false)
collectionsJoined UsersAndCollections[]
isPrivate Boolean @default(false)
whitelistedUsers WhitelistedUser[]