feat: initial commit

This commit is contained in:
2024-11-03 11:08:53 +00:00
commit 44decd4e7b
26 changed files with 4819 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
import * as blurhash from "blurhash";
const bgHash =
"|ZCshDM^M_axogogWCWAWA-@WAWBj]axaxj[j]ay9boGobWCR%ayt7ofj[4,xZoeWCaxj[ofj]j[V=ofaxa}ofj[WCWVfP$|WEj^ofofayWBaya{s+R*ogs;axWBayj[j[RiWUj]j[WVj[j[axayRioKflfSa{axaxfPa#";
const bgUrl = "/images/1226538.webp";
const onLoaded = () => {
const searchBar = document.querySelector(".search-bar") as HTMLDivElement;
if (searchBar) {
searchBar.classList.add("loaded");
searchBar.style.opacity = "1";
}
const quickLaunch = document.querySelector(".quick-launch") as HTMLDivElement;
if (quickLaunch) {
quickLaunch.classList.add("loaded");
quickLaunch.style.opacity = "1";
}
};
export const initBackground = () => {
const hashCanvas = document.getElementById("bg-hash") as HTMLCanvasElement;
if (!hashCanvas) {
throw new Error("Could not find canvas");
}
hashCanvas.width = 32;
hashCanvas.height = 18;
const pixels = blurhash.decode(bgHash, 32, 18);
const ctx = hashCanvas.getContext("2d");
if (!ctx) {
throw new Error("Could not get 2d context");
}
const imageData = ctx.createImageData(hashCanvas.width, hashCanvas.height);
imageData.data.set(pixels);
ctx.putImageData(imageData, 0, 0);
// Load background image
const bg = document.getElementById("bg-main") as HTMLDivElement;
const img = new Image();
img.src = bgUrl;
img.onload = () => {
bg.style.backgroundImage = `url(${bgUrl})`;
bg.style.opacity = "1";
bg.classList.add("loaded");
setTimeout(() => {
// remove hashCanvas from dom
hashCanvas.remove();
}, 2000);
onLoaded();
};
};
+209
View File
@@ -0,0 +1,209 @@
export const launcherItems: LauncherItem[] = [
{
name: "Jellyfin",
href: "http://10.0.0.101:8096/web",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/jellyfin.svg",
pinned: true,
},
{
name: "Musicfin",
href: "http://armbian:8096/web",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/jellyfin.svg",
},
{
name: "Youtube2MP3",
href: "http://yt2mp3.home.ip",
icon: "https://git.rul.sh/khairul169/Go-YT2MP3/raw/branch/main/ui/public/android-chrome-512x512.png",
},
{
name: "Memos",
href: "https://memos.rul.sh",
icon: "https://memos.rul.sh/apple-touch-icon.png",
pinned: true,
},
{
name: "Flatnotes",
href: "http://100.64.0.3:8124",
icon: "https://github.com/dullage/flatnotes/blob/develop/client/public/android-chrome-192x192.png?raw=true",
},
{
name: "Gitea",
href: "https://git.rul.sh",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/gitea.svg",
},
{
name: "Gotify",
href: "https://gotify.rul.sh",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/gotify.svg",
},
{
name: "AriaNg",
href: "http://ariang.home.ip/",
icon: "https://raw.githubusercontent.com/mayswind/AriaNg/master/src/tileicon.png",
disabled: true,
},
{
name: "File Browser",
href: "http://10.0.0.101:9005/files",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/filebrowser.svg",
pinned: true,
},
{
name: "File Browser (Armbian)",
href: "https://fm.rul.sh",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/filebrowser.svg",
},
{
name: "Change Detection",
href: "https://chgdetect.rul.sh/",
icon: "https://raw.githubusercontent.com/dgtlmoon/changedetection.io/master/changedetectionio/static/images/avatar-256x256.png",
},
{
name: "Vaultwarden",
href: "https://vw.rul.sh",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/vaultwarden.svg",
pinned: true,
},
{
name: "Garage WebUI",
href: "http://10.0.0.101:3909/buckets",
icon: "https://garagehq.deuxfleurs.fr/icons/apple-touch-icon.png",
disabled: false,
},
{
name: "Pi-hole",
href: "http://pi.hole/admin",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/pi-hole.svg",
},
{
name: "Headscale",
href: "https://headscale.rul.sh/web/devices.html",
icon: "https://raw.githubusercontent.com/juanfont/headscale/refs/heads/main/docs/logo/headscale3-dots.svg",
},
{
name: "Nginx Proxy Manager",
href: "http://nginx-pm.home.ip/",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/nginx-proxy-manager.svg",
},
{
name: "Nginx UI",
href: "https://nginx-ui.rul.sh/",
icon: "https://nginxui.com/assets/icon.svg",
},
{
name: "pgAdmin",
href: "http://pgadmin.home.ip/",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/pgadmin.svg",
},
{
name: "phpMyAdmin",
href: "http://pma.home.ip/",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/phpmyadmin.svg",
},
{
name: "Mongo Express",
href: "http://home:27018/",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/mongodb.svg",
disabled: true,
},
{
name: "Hoppscotch",
href: "https://hopp.rul.sh/",
icon: "https://github.com/walkxcode/dashboard-icons/raw/be82e22c418f5980ee2a13064d50f1483df39c8c/svg/hoppscotch.svg",
disabled: true,
},
{
name: "aaPanel",
href: "https://10.0.0.102:29760/590d1b7a",
icon: "https://www.aapanel.com/static/images/bt_logo.png",
disabled: true,
},
{
name: "ProxmoxVE",
href: "https://pve.rul.sh/",
icon: "https://www.proxmox.com/apple-touch-icon.png",
},
{
name: "LXConsole",
href: "https://lxc.rul.sh/",
icon: "https://github.com/PenningLabs/lxconsole/raw/refs/heads/main/lxconsole/static/assets/img/logo-light.svg",
},
{
name: "Incus UI",
href: "https://164.152.166.61:8443/",
icon: "https://linuxcontainers.org/static/img/containers.small.png",
disabled: true,
},
{
name: "Incus UI (Armbian)",
href: "https://armbian:8443",
icon: "https://linuxcontainers.org/static/img/containers.small.png",
disabled: true,
},
];
type LauncherItem = {
name: string;
href: string;
icon: string;
disabled?: boolean;
pinned?: boolean;
};
const openBtn = document.querySelector(".open-launcher") as HTMLButtonElement;
const launcher = document.querySelector(".launcher") as HTMLDivElement;
const title = launcher.querySelector(".title") as HTMLButtonElement;
const itemContainer = launcher.querySelector(".items") as HTMLDivElement;
let isLauncherOpen = false;
export const openLauncher = () => {
launcher.classList.add("open");
launcher.style.opacity = "1";
isLauncherOpen = true;
};
export const closeLauncher = () => {
launcher.classList.remove("open");
launcher.style.opacity = "0";
isLauncherOpen = false;
};
const onScroll = (e: WheelEvent) => {
if (e.deltaY > 10 && !isLauncherOpen) {
openLauncher();
return;
}
const curScroll = launcher.scrollTop;
if (e.deltaY < -50 && isLauncherOpen && curScroll <= 1) {
closeLauncher();
return;
}
};
export const initLauncher = () => {
itemContainer.innerHTML = launcherItems
.filter((item) => item.disabled !== true)
.map((item) => {
return `
<a href="${item.href}" class="item" rel="noopener noreferrer" title="${item.name}">
<div class="image">
<img src="${item.icon}" alt="${item.name}" />
</div>
<span class="title">${item.name}</span>
</a>
`;
})
.join("");
title.addEventListener("click", closeLauncher);
openBtn.addEventListener("click", openLauncher);
document.addEventListener("wheel", onScroll);
launcher.addEventListener("click", (e) => {
if (e.target === launcher) {
closeLauncher();
}
});
};
+12
View File
@@ -0,0 +1,12 @@
import { initBackground } from "./background";
import { initLauncher } from "./launcher";
import { initQuickLaunch } from "./quick-launch";
import "./style.css";
const onReady = () => {
initBackground();
initQuickLaunch();
initLauncher();
};
document.addEventListener("DOMContentLoaded", onReady);
+21
View File
@@ -0,0 +1,21 @@
import { launcherItems } from "./launcher";
export const initQuickLaunch = () => {
const container = document.querySelector(".quick-launch .items");
if (!container) {
throw new Error("Could not find quick-launch container");
}
const pinnedItems = launcherItems.filter((item) => item.pinned);
container.innerHTML = pinnedItems
.filter((item) => item.disabled !== true)
.map((item) => {
return `
<a href="${item.href}" class="item" rel="noopener noreferrer" title="${item.name}">
<img src="${item.icon}" alt="${item.name}" />
</a>
`;
})
.join("");
};
+101
View File
@@ -0,0 +1,101 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body,
#app {
@apply w-full min-h-screen max-h-dvh relative bg-gray-900 overflow-hidden p-0;
}
.background {
@apply absolute left-0 top-0 w-full h-full scale-100 transition-[opacity,_transform] duration-1000 ease-in-out;
}
.background.loaded {
@apply scale-105;
}
.search-bar {
@apply bg-white/80 backdrop-blur-md shadow-lg rounded-full absolute top-[5%] md:top-[10%] left-1/2 -translate-x-1/2 z-[2] flex items-center overflow-hidden w-[90%] max-w-[500px];
}
.search-bar input {
@apply bg-transparent outline-none p-2 md:p-3 flex-1 w-full min-w-0;
}
.search-bar.loaded {
@apply transition-opacity duration-500 ease-in-out delay-300;
}
.quick-launch {
@apply absolute bottom-[3%] md:bottom-[10%] left-1/2 -translate-x-1/2 z-[3] w-full flex flex-col items-center justify-center;
}
.quick-launch .open-launcher {
@apply rounded-full size-16 mb-2 transition-[background,_transform] ease-in-out duration-700 flex items-center justify-center;
}
.quick-launch .open-launcher:hover {
@apply bg-white/10 -translate-y-2;
}
.quick-launch .open-launcher:hover img {
@apply scale-y-110;
}
.quick-launch .items {
@apply bg-white/10 backdrop-blur-[3px] shadow-lg p-4 rounded-xl flex items-center gap-4;
}
.quick-launch .item {
@apply bg-white rounded-xl p-2 aspect-square overflow-hidden block transition-transform flex-shrink-0;
}
.quick-launch .item:hover {
@apply scale-110;
}
.quick-launch .item img {
@apply size-12 rounded-lg;
}
.quick-launch.loaded {
@apply transition-opacity duration-500 ease-in-out delay-200;
}
.launcher {
@apply w-full h-screen max-h-dvh overflow-y-auto absolute translate-y-[10%] transition-all duration-300 ease-in-out z-10 pointer-events-none flex flex-col p-4 md:p-8;
}
.launcher.open {
@apply translate-y-0 pointer-events-auto backdrop-blur-lg;
}
.launcher .sheet {
@apply bg-white/50 backdrop-blur-sm rounded-2xl p-4 md:p-8 w-full max-w-2xl m-auto;
}
.launcher .items {
@apply grid grid-cols-3 lg:grid-cols-4 gap-y-4 gap-2 md:gap-4 md:gap-y-6 lg:gap-6 lg:gap-y-10 p-2 md:p-4;
}
.launcher .item {
@apply flex flex-col items-center transition-transform;
}
.launcher .item:hover {
@apply scale-110;
}
.launcher .item .image {
@apply bg-white rounded-xl p-2 aspect-square overflow-hidden;
}
.launcher .item .image img {
@apply size-12 md:size-14 rounded-lg;
}
.launcher .item span {
@apply text-center text-sm md:text-base leading-4 mt-3 block;
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />