refactor: clean up unused imports and improve file loading logic
This commit is contained in:
parent
4f54880400
commit
31a8d62f1d
4 changed files with 105 additions and 67 deletions
|
@ -2,7 +2,7 @@ import { $, component$, useOnDocument, useSignal } from "@builder.io/qwik";
|
|||
import ky from "ky";
|
||||
|
||||
export default component$(() => {
|
||||
type TestimonialProps = {
|
||||
type Testimonial = {
|
||||
nickname: string;
|
||||
pfp?: string;
|
||||
id?: string;
|
||||
|
@ -19,7 +19,31 @@ export default component$(() => {
|
|||
success: boolean;
|
||||
}
|
||||
|
||||
const Testimonial = component$(({ nickname, id, quote, pfp }: TestimonialProps) => {
|
||||
// Array of testimonial objects
|
||||
const testimonials: Testimonial[] = [
|
||||
{
|
||||
nickname: "grng",
|
||||
id: "829372486780715018",
|
||||
quote: "stereo is the best file host I've ever used, it's fast, reliable, and the interface is so clean and easy to use. I love it!"
|
||||
},
|
||||
{
|
||||
nickname: "hexlocation",
|
||||
pfp: "https://git.iwakura.rip/avatars/38bbf57a26f2891c59102582240386a4e2fa52b3999374673e0f4c4249ed4149?size=512",
|
||||
quote: "I've been using stereo for a while now, and I can't imagine going back to any other file host. It's just that good!"
|
||||
},
|
||||
{
|
||||
nickname: "typed",
|
||||
pfp: "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fclipartcraft.com%2Fimages%2Ffire-emoji-transparent-snapchat-4.png&f=1&nofb=1&ipt=d59b80eec4d535f7f10b618a0daa9c0689a408643eaa6c1a054c0a03e7ca1835",
|
||||
quote: "stereo.cat saved my house from a fire, because when I was signing up I realized the email I had to log into needed my phone for 2fa, so when I went to the kitchen and grabbed my phone I noticed the stove was on, I thankfully turned it off. Thank you stereo.cat"
|
||||
},
|
||||
{
|
||||
nickname: "starlo",
|
||||
pfp: "https://cdn.discordapp.com/avatars/962173926849519716/04af851a9954ba623b5d2eb5dd189785.webp?size=512",
|
||||
quote: "I was expecting that stereo would be a greater website for storing my images, and I wasn't wrong and my doubts are always correct, it's more cooler than I expected 🔥"
|
||||
}
|
||||
];
|
||||
|
||||
const Testimonial = component$(({ nickname, id, quote, pfp }: Testimonial) => {
|
||||
const lanyard = useSignal<LanyardResponse>();
|
||||
|
||||
useOnDocument("DOMContentLoaded", $(async () => {
|
||||
|
@ -86,23 +110,9 @@ export default component$(() => {
|
|||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-6">
|
||||
<Testimonial
|
||||
nickname="grng"
|
||||
id="829372486780715018"
|
||||
quote="stereo is the best file host I've ever used, it's fast, reliable, and the interface is so clean and easy to use. I love it!"
|
||||
/>
|
||||
|
||||
<Testimonial
|
||||
pfp="https://git.iwakura.rip/avatars/38bbf57a26f2891c59102582240386a4e2fa52b3999374673e0f4c4249ed4149?size=512"
|
||||
nickname="hexlocation"
|
||||
quote="I've been using stereo for a while now, and I can't imagine going back to any other file host. It's just that good!"
|
||||
/>
|
||||
|
||||
<Testimonial
|
||||
pfp="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fclipartcraft.com%2Fimages%2Ffire-emoji-transparent-snapchat-4.png&f=1&nofb=1&ipt=d59b80eec4d535f7f10b618a0daa9c0689a408643eaa6c1a054c0a03e7ca1835"
|
||||
nickname="typed"
|
||||
quote="stereo.cat saved my house from a fire, because when I was signing up I realized the email I had to log into needed my phone for 2fa, so when I went to the kitchen and grabbed my phone I noticed the stove was on, I thankfully turned it off. Thank you stereo.cat"
|
||||
/>
|
||||
{testimonials.map((t, i) => (
|
||||
<Testimonial key={i} {...t} />
|
||||
))}
|
||||
</div>
|
||||
<p class="text-xl text-white/50">
|
||||
and many, many more...
|
||||
|
|
7
src/lib/misc.ts
Normal file
7
src/lib/misc.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export function debounce<T extends (...args: any[]) => void>(fn: T, delay = 200) {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
return (...args: Parameters<T>) => {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(async () => await fn(...args), delay);
|
||||
};
|
||||
}
|
|
@ -1,36 +1,28 @@
|
|||
import { $, component$, Signal, useSignal, useTask$, useVisibleTask$ } from "@builder.io/qwik";
|
||||
/* eslint-disable qwik/no-use-visible-task */
|
||||
import { component$, Signal, useSignal, useTask$, useVisibleTask$ } from "@builder.io/qwik";
|
||||
import { routeLoader$, type DocumentHead } from "@builder.io/qwik-city";
|
||||
import Actionbar from "~/components/dashboard/Actionbar";
|
||||
import Settings from "~/components/dashboard/Settings";
|
||||
import Titlebar from "~/components/dashboard/Titlebar";
|
||||
// import Dropzone from "~/components/Dropzone";
|
||||
import { useNanostore$ } from "~/hooks/nanostores";
|
||||
import { api } from "~/lib/api";
|
||||
import { areFilesLoaded, dashboardFiles } from "~/lib/stores";
|
||||
import { debounce } from "~/lib/misc";
|
||||
import { StereoFile } from "~/lib/types";
|
||||
|
||||
export const useAuthCheck = routeLoader$(({ cookie, redirect: r }) => {
|
||||
export const useAuthenticated = routeLoader$(({ cookie, redirect: r }) => {
|
||||
const jwt = cookie.get("jwt");
|
||||
if (jwt) return {};
|
||||
|
||||
throw r(302, "/api/auth/login");
|
||||
});
|
||||
|
||||
export default component$(() => {
|
||||
const files = useNanostore$<StereoFile[]>(dashboardFiles);
|
||||
const loaded = useNanostore$<boolean>(areFilesLoaded);
|
||||
|
||||
useVisibleTask$(async () => {
|
||||
loaded.value = false;
|
||||
files.value = await api.list();
|
||||
loaded.value = true;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Settings />
|
||||
<div class="flex flex-col w-full h-screen p-8 gap-6 bg-gradient-to-b from-stereo/20 to-transparent justify-self-end">
|
||||
<Titlebar />
|
||||
<Files files={files} loaded={loaded} />
|
||||
<Files />
|
||||
<Actionbar />
|
||||
</div>
|
||||
</>
|
||||
|
@ -45,11 +37,8 @@ const formatSize = (bytes: number) => {
|
|||
}
|
||||
|
||||
|
||||
const Files = component$<{
|
||||
files: Signal<StereoFile[]>;
|
||||
loaded: Signal<boolean>;
|
||||
}>(({ files }) => {
|
||||
const File = component$(({ file }: { file: StereoFile }) => {
|
||||
const Files = component$(() => {
|
||||
const File = component$(({ file }: { file: StereoFile }) => {
|
||||
const Preview = component$(() => {
|
||||
type FileType = "image" | "video" | "audio" | "other";
|
||||
const fileType: Signal<FileType> = useSignal<FileType>("other");
|
||||
|
@ -90,7 +79,7 @@ const Files = component$<{
|
|||
)}
|
||||
{fileType.value === "other" && (
|
||||
<div class="w-full min-h-30 flex items-center justify-center bg-gray-200 text-gray-500">
|
||||
<p>Unsupported file type</p>
|
||||
<p>unsupported file type</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -119,26 +108,68 @@ const Files = component$<{
|
|||
});
|
||||
|
||||
const loadingMore = useSignal(false);
|
||||
const hasMore = useSignal(true);
|
||||
const sentinel = useSignal<HTMLDivElement>();
|
||||
|
||||
const onScroll = $(async (e: Event) => {
|
||||
const element = e.target as HTMLElement;
|
||||
if (!element) return;
|
||||
const files = useSignal<StereoFile[]>([]);
|
||||
const page = useSignal(1);
|
||||
|
||||
const isAtBottom = element.scrollHeight - element.scrollTop - element.clientHeight < threshold;
|
||||
if (isAtBottom) {
|
||||
console.log("Loading more files...");
|
||||
}
|
||||
});
|
||||
// TODO: make it load enough images to fill the viewport instead
|
||||
useVisibleTask$(({ cleanup }) => {
|
||||
if (!sentinel.value) return;
|
||||
|
||||
return (
|
||||
<div class="px-2 mb-6 flex-grow overflow-y-auto mask-clip-content rounded-3xl" onScroll$={onScroll}>
|
||||
<div class="w-full columns-1 md:columns-2 lg:columns-4 gap-2 space-y-2">
|
||||
{files.value.map((file) => (
|
||||
<File key={file.ID} file={file} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
if (
|
||||
entry.isIntersecting &&
|
||||
!loadingMore.value &&
|
||||
hasMore.value
|
||||
) {
|
||||
loadingMore.value = true;
|
||||
|
||||
console.log("Loading more files...");
|
||||
|
||||
debounce(async () => {
|
||||
const newFiles = await api.list(page.value, 4); // gotta be a multiple of 4 so pages dont look weird
|
||||
if (newFiles.length === 0) {
|
||||
hasMore.value = false;
|
||||
if (sentinel.value) observer.unobserve(sentinel.value);
|
||||
} else {
|
||||
files.value = [...files.value, ...newFiles];
|
||||
page.value++;
|
||||
}
|
||||
loadingMore.value = false;
|
||||
}, 50)();
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(sentinel.value);
|
||||
|
||||
cleanup(() => {
|
||||
if (observer && sentinel.value) observer.unobserve(sentinel.value);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="px-2 mb-6 flex-grow overflow-y-auto mask-clip-content rounded-3xl">
|
||||
<div class="w-full columns-1 md:columns-2 lg:columns-4 gap-2 space-y-2 relative">
|
||||
{files.value.map((file) => (
|
||||
<File key={file.ID} file={file} />
|
||||
))}
|
||||
|
||||
{hasMore.value && (
|
||||
<div
|
||||
ref={sentinel}
|
||||
class="absolute bottom-0 left-0 h-56 flex items-center justify-center w-full bg-blue-500"
|
||||
>
|
||||
{/* {loadingMore.value && (
|
||||
loading spinner?
|
||||
)} */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const head: DocumentHead = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable qwik/jsx-img */
|
||||
import { component$ } from "@builder.io/qwik";
|
||||
import { DocumentHead, routeLoader$ } from "@builder.io/qwik-city";
|
||||
import { DocumentHead } from "@builder.io/qwik-city";
|
||||
import CallToAction from "~/components/landing/CallToAction";
|
||||
import Footer from "~/components/landing/Footer";
|
||||
import Hero from "~/components/landing/Hero";
|
||||
|
@ -8,16 +8,6 @@ import Navbar from "~/components/landing/Navbar";
|
|||
import Stats from "~/components/landing/Stats";
|
||||
import Testimonials from "~/components/landing/Testimonials";
|
||||
|
||||
export const useAuthCheck = routeLoader$(({ cookie, redirect: r, query }) => {
|
||||
const jwt = cookie.get("jwt");
|
||||
const set = Boolean(query.get("jwt_set"));
|
||||
|
||||
if (jwt && set) {
|
||||
throw r(302, "/api/auth/login");
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
export default component$(() => {
|
||||
return (
|
||||
<>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue