new settings layout
This commit is contained in:
parent
8e5dff01c0
commit
361c4e0b62
2 changed files with 180 additions and 116 deletions
|
@ -1,24 +1,111 @@
|
|||
import { component$ } from "@builder.io/qwik";
|
||||
// import { component$ } from "@builder.io/qwik";
|
||||
// import { useNanostore$ } from "~/hooks/nanostores";
|
||||
// import { isSettingsOpen, userInfo } from "~/lib/stores";
|
||||
// import { StereoUser } from "~/lib/types";
|
||||
|
||||
import { component$, useComputed$, useSignal, useTask$ } from "@builder.io/qwik";
|
||||
import { useNanostore$ } from "~/hooks/nanostores";
|
||||
import { isSettingsOpen, userInfo } from "~/lib/stores";
|
||||
import { StereoUser } from "~/lib/types";
|
||||
import { isSettingsOpen } from "~/lib/stores";
|
||||
|
||||
const StorageAndPlan = component$(() => {
|
||||
return (
|
||||
<div>
|
||||
<p>current plan: stereo free</p>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const Integrations = component$(() => {
|
||||
return (
|
||||
<div>
|
||||
<p>manage your api keys and integrations here</p>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const PrivacyAndSecurity = component$(() => {
|
||||
return (
|
||||
<div>
|
||||
<p>manage your privacy settings and security options</p>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
export default component$(() => {
|
||||
const info = useNanostore$<StereoUser>(userInfo);
|
||||
const open = useNanostore$<boolean>(isSettingsOpen);
|
||||
if (open.value) return (
|
||||
<div onClick$={() => open.value = false} class="z-[50] absolute flex w-full h-screen items-center justify-center backdrop-blur-3xl bg-black/50 text-white text-6xl">
|
||||
<div onClick$={e => e.stopPropagation()} class="flex gap-8 bg-black/50 p-8 rounded-3xl shadow-lg w-4/7 h-4/7">
|
||||
<div class="flex flex-col flex-1/4 border-r-2 border-white/25">
|
||||
<h1 class="text-2xl">settings</h1>
|
||||
</div>
|
||||
const visible = useSignal(false);
|
||||
const shouldRender = useSignal(open.value);
|
||||
|
||||
<div class="flex flex-col flex-3/4">
|
||||
<h1 class="text-2xl">content</h1>
|
||||
useTask$(({ track }) => {
|
||||
track(() => open.value);
|
||||
if (open.value) {
|
||||
shouldRender.value = true;
|
||||
setTimeout(() => { visible.value = true; }, 10);
|
||||
} else {
|
||||
visible.value = false;
|
||||
setTimeout(() => { shouldRender.value = false; }, 300);
|
||||
}
|
||||
});
|
||||
|
||||
const categories = useSignal([
|
||||
{ name: "storage & plan", component: StorageAndPlan },
|
||||
{ name: "api & integrations" , component: Integrations },
|
||||
{ name: "privacy & security" , component: PrivacyAndSecurity }
|
||||
])
|
||||
|
||||
const selectedCategory = useSignal(0);
|
||||
const SelectedComponent = useComputed$(() => {
|
||||
return categories.value[selectedCategory.value].component;
|
||||
});
|
||||
|
||||
if (!shouldRender.value) return <></>;
|
||||
return (
|
||||
<div
|
||||
onClick$={() => (open.value = false)}
|
||||
style={{
|
||||
opacity: visible.value ? 1 : 0,
|
||||
}}
|
||||
class="z-[50] fixed inset-0 flex items-center justify-center bg-black/50 text-white text-6xl backdrop-blur-3xl transition-opacity duration-300"
|
||||
>
|
||||
<div
|
||||
data-aos="fade-up"
|
||||
data-aos-duration="400"
|
||||
data-aos-anchor-placement="top-center"
|
||||
data-aos-easing="ease-out-quad"
|
||||
style={{
|
||||
border: "0.5px solid #FF264E",
|
||||
boxShadow:
|
||||
"0px 4px 20px 0px rgba(255, 38, 78, 0.08), 0px 8px 12px 0px rgba(0, 0, 0, 0.12), 0px 4px 4px 0px rgba(0, 0, 0, 0.06), 0px 2px 1px 0px rgba(0, 0, 0, 0.04), 0px 4px 8px 0px rgba(255, 38, 78, 0.12) inset, 0px 1px 3px 0px rgba(255, 38, 78, 0.24) inset",
|
||||
}}
|
||||
onClick$={e => e.stopPropagation()}
|
||||
class="flex gap-8 bg-black/30 bg-gradient-to-t from-stereo/20 to-transparent p-8 rounded-3xl shadow-lg w-4/7 h-4/7"
|
||||
>
|
||||
<div class="flex flex-col justify-between flex-1/4 border-r-2 border-white/25">
|
||||
<h1 class="text-2xl">settings</h1>
|
||||
<div>
|
||||
{categories.value.map((category, i) => (
|
||||
<div
|
||||
key={category.name}
|
||||
class={[
|
||||
"group py-2 px-3 transition-colors cursor-pointer rounded-l-xl",
|
||||
selectedCategory.value === i
|
||||
? "bg-white/20 text-white"
|
||||
: "hover:bg-white/15 text-white/75 hover:text-white"
|
||||
].join(" ")}
|
||||
onClick$={() => (selectedCategory.value = i)}
|
||||
>
|
||||
<h2 class="text-lg">{category.name}</h2>
|
||||
<p class="text-sm text-white/25">description</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col flex-3/4 text-lg">
|
||||
<h1 class="text-2xl mb-4">{categories.value[selectedCategory.value].name}</h1>
|
||||
<SelectedComponent.value />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
); else return (
|
||||
<></>
|
||||
);
|
||||
})
|
||||
});
|
|
@ -39,114 +39,91 @@ export default component$(() => {
|
|||
});
|
||||
|
||||
const formatSize = (bytes: number) => {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
||||
}
|
||||
|
||||
|
||||
const Files = component$<{
|
||||
files: Signal<StereoFile[]>;
|
||||
loaded: Signal<boolean>;
|
||||
files: Signal<StereoFile[]>;
|
||||
loaded: Signal<boolean>;
|
||||
}>(({ files }) => {
|
||||
|
||||
const File = component$(({ file }: { file: StereoFile }) => {
|
||||
const Preview = component$(() => {
|
||||
type FileType =
|
||||
| "image"
|
||||
| "video"
|
||||
| "audio"
|
||||
| "other";
|
||||
|
||||
const fileType: Signal<FileType> = useSignal<FileType>("other");
|
||||
const type = file.Mime.split("/")[1];
|
||||
|
||||
useTask$(async () => {
|
||||
if (
|
||||
["jpeg", "jpg", "png", "gif", "webp"]
|
||||
.includes(type)
|
||||
) fileType.value = "image";
|
||||
|
||||
else if (
|
||||
["mp4", "webm", "ogg", "avi", "mov"]
|
||||
.includes(type)
|
||||
) fileType.value = "video";
|
||||
|
||||
else if (
|
||||
["mp3", "wav", "flac", "aac"]
|
||||
.includes(type)
|
||||
) fileType.value = "audio";
|
||||
|
||||
else fileType.value = "other";
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="w-full h-60 object-cover flex-grow relative overflow-clip">
|
||||
{fileType.value === "image" && (
|
||||
<img
|
||||
width={400}
|
||||
height={300}
|
||||
src={`/api/${file.ID}`}
|
||||
alt={file.Name}
|
||||
class="w-full h-60 object-cover flex-grow hover:scale-105 transition-all duration-300"
|
||||
/>
|
||||
)}
|
||||
|
||||
{fileType.value === "video" && (
|
||||
<video
|
||||
width={400}
|
||||
height={300}
|
||||
src={`/api/${file.ID}`}
|
||||
controls
|
||||
class="w-full h-60 object-cover flex-grow hover:scale-105 transition-all duration-300"
|
||||
/>
|
||||
)}
|
||||
|
||||
{fileType.value === "audio" && (
|
||||
<audio
|
||||
src={`/api/${file.ID}`}
|
||||
controls
|
||||
class="w-full h-60 object-cover flex-grow hover:scale-105 transition-all duration-300"
|
||||
/>
|
||||
)}
|
||||
|
||||
{fileType.value === "other" && (
|
||||
<div class="w-full h-60 flex items-center justify-center bg-gray-200 text-gray-500">
|
||||
<p>Unsupported file type</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
background: "linear-gradient(180deg, rgba(255, 38, 78, 0.00) 57.21%, rgba(255, 38, 78, 0.17) 100%); ",
|
||||
boxShadow: "0px 4px 21.2px 2px rgba(255, 38, 78, 0.05)",
|
||||
}} class="transition-all rounded-3xl flex flex-col overflow-clip items-center justify-center">
|
||||
<Preview />
|
||||
|
||||
<div class="flex flex-col items-center justify-center text-center w-full h-full p-5">
|
||||
<p class="text-xl truncate w-full">{file.Name}</p>
|
||||
<p class="text-stereo/50 text-lg">
|
||||
{formatSize(file.Size)}
|
||||
<span class="text-stereo/40"> • </span>
|
||||
Uploaded on {new Date(file.CreatedAt).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="px-2 mb-6 flex flex-col w-full max-h-full overflow-y-auto overflow-x-hidden mask-clip-content rounded-3xl">
|
||||
<div class="w-full h-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-2">
|
||||
{files.value.map((file) => (
|
||||
<File key={file.ID} file={file} />
|
||||
))}
|
||||
const File = component$(({ file }: { file: StereoFile }) => {
|
||||
const Preview = component$(() => {
|
||||
type FileType = "image" | "video" | "audio" | "other";
|
||||
const fileType: Signal<FileType> = useSignal<FileType>("other");
|
||||
const type = file.Mime.split("/")[1];
|
||||
useTask$(() => {
|
||||
if (["jpeg", "jpg", "png", "gif", "webp"].includes(type)) fileType.value = "image";
|
||||
else if (["mp4", "webm", "ogg", "avi", "mov"].includes(type)) fileType.value = "video";
|
||||
else if (["mp3", "wav", "flac", "aac"].includes(type)) fileType.value = "audio";
|
||||
else fileType.value = "other";
|
||||
});
|
||||
return (
|
||||
<div class="w-full h-max object-cover flex-grow relative overflow-clip">
|
||||
{fileType.value === "image" && (
|
||||
<img
|
||||
width={400}
|
||||
src={`/api/${file.ID}`}
|
||||
alt={file.Name}
|
||||
class="w-full h-60 object-cover flex-grow hover:scale-105 transition-all duration-300"
|
||||
/>
|
||||
)}
|
||||
{fileType.value === "video" && (
|
||||
<video
|
||||
width={400}
|
||||
src={`/api/${file.ID}`}
|
||||
controls
|
||||
class="w-full h-60 object-cover flex-grow hover:scale-105 transition-all duration-300"
|
||||
/>
|
||||
)}
|
||||
{fileType.value === "audio" && (
|
||||
<audio
|
||||
src={`/api/${file.ID}`}
|
||||
controls
|
||||
class="w-full h-60 object-cover flex-grow hover:scale-105 transition-all duration-300"
|
||||
/>
|
||||
)}
|
||||
{fileType.value === "other" && (
|
||||
<div class="w-full h-60 flex items-center justify-center bg-gray-200 text-gray-500">
|
||||
<p>Unsupported file type</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(180deg, rgba(255, 38, 78, 0.00) 57.21%, rgba(255, 38, 78, 0.17) 100%); ",
|
||||
boxShadow: "0px 4px 21.2px 2px rgba(255, 38, 78, 0.05)",
|
||||
}}
|
||||
class="transition-all rounded-3xl flex flex-col overflow-clip items-center justify-center mb-4 break-inside-avoid"
|
||||
>
|
||||
<Preview />
|
||||
<div class="flex flex-col items-center justify-center text-center w-full h-full p-5">
|
||||
<p class="text-xl truncate w-full">{file.Name}</p>
|
||||
<p class="text-stereo/50 text-lg">
|
||||
{formatSize(file.Size)}
|
||||
<span class="text-stereo/40"> • </span>
|
||||
Uploaded on {new Date(file.CreatedAt).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div class="px-2 mb-6 flex flex-col w-full max-h-full overflow-y-auto overflow-x-hidden mask-clip-content rounded-3xl">
|
||||
<div class="w-full h-full columns-1 md:columns-2 lg:columns-4 gap-2">
|
||||
{files.value.map((file) => (
|
||||
<File key={file.ID} file={file} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const head: DocumentHead = {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue