mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-05-08 16:24:36 +00:00
Compare commits
No commits in common. "f228937e3e5e8ab2778bfa575b92ae6b0ec66d12" and "49e2d1b759f54949bd2baf9bb96fee9b52a061af" have entirely different histories.
f228937e3e
...
49e2d1b759
@ -5,13 +5,11 @@ import tailwindcss from '@tailwindcss/vite';
|
|||||||
|
|
||||||
import react from '@astrojs/react';
|
import react from '@astrojs/react';
|
||||||
|
|
||||||
import mdx from '@astrojs/mdx';
|
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()]
|
plugins: [tailwindcss()]
|
||||||
},
|
},
|
||||||
|
|
||||||
integrations: [react(), mdx()]
|
integrations: [react()]
|
||||||
});
|
});
|
||||||
@ -9,27 +9,19 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/mdx": "^4.3.3",
|
|
||||||
"@astrojs/react": "^4.3.0",
|
"@astrojs/react": "^4.3.0",
|
||||||
"@octokit/rest": "^22.0.0",
|
|
||||||
"@skeletonlabs/skeleton-react": "^1.2.4",
|
"@skeletonlabs/skeleton-react": "^1.2.4",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"@types/react": "^19.1.9",
|
"@types/react": "^19.1.9",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"astro": "^5.12.8",
|
"astro": "^5.12.8",
|
||||||
"date-fns": "^4.1.0",
|
|
||||||
"markdown-it": "^14.1.0",
|
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"sanitize-html": "^2.17.0",
|
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"usehooks-ts": "^3.1.1"
|
"usehooks-ts": "^3.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@skeletonlabs/skeleton": "^3.1.7",
|
"@skeletonlabs/skeleton": "^3.1.7"
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
|
||||||
"@types/markdown-it": "^14.1.2",
|
|
||||||
"@types/sanitize-html": "^2.16.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
|||||||
<div class="prose lg:prose-lg dark:prose-invert max-w-5xl mx-auto">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
---
|
---
|
||||||
import { FaGithub } from "react-icons/fa6";
|
|
||||||
import "../styles/global.css";
|
import "../styles/global.css";
|
||||||
import TopBar from "~/components/navigation/TopBar.astro";
|
import TopBar from "~/components/navigation/TopBar.astro";
|
||||||
---
|
---
|
||||||
@ -12,52 +11,10 @@ import TopBar from "~/components/navigation/TopBar.astro";
|
|||||||
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
<title>Spotube</title>
|
<title>Spotube</title>
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content="An Open Source Music Client for every platform"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="keywords"
|
|
||||||
content="music, client, open source, music, streaming"
|
|
||||||
/>
|
|
||||||
<meta name="author" content="KRTirtho" />
|
|
||||||
<meta name="robots" content="index, follow" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta name="theme-color" content="#1DB954" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main class="p-2 md:p-4 min-h-[90vh]">
|
|
||||||
<TopBar />
|
<TopBar />
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
|
||||||
<footer class="w-full bg-tertiary-100-900 p-4 flex justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 class="h3">Spotube</h3>
|
|
||||||
<p>
|
|
||||||
Copyright © {new Date().getFullYear()} Spotube
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/KRTirtho/spotube">
|
|
||||||
<FaGithub className="inline mr-1" />
|
|
||||||
Github
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://opencollective.org/spotube">
|
|
||||||
<img
|
|
||||||
src="https://avatars0.githubusercontent.com/u/13403593?v=4"
|
|
||||||
alt="OpenCollective"
|
|
||||||
height="20"
|
|
||||||
width="20"
|
|
||||||
class="inline mr-1"
|
|
||||||
/>
|
|
||||||
OpenCollective
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</footer>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
---
|
|
||||||
import type { IconType } from "react-icons";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
links: Record<string, [string, IconType[], string]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { links } = Astro.props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
|
||||||
{
|
|
||||||
Object.entries(links).map((link) => {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href={link[1][0]}
|
|
||||||
class="flex flex-col btn preset-tonal-secondary rounded-xl p-0 overflow-hidden"
|
|
||||||
>
|
|
||||||
<div class="relative bg-primary-500 p-4 flex gap-4 justify-center rounded-t-xl w-full">
|
|
||||||
{link[1][1].map((icon) => {
|
|
||||||
const Icon = icon;
|
|
||||||
return <Icon />;
|
|
||||||
})}
|
|
||||||
<p class="chip preset-tonal-warning text-warning-400 absolute right-2 uppercase">
|
|
||||||
{link[1][2]}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p class="p-4">{link[0]}</p>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import type { RestEndpointMethodTypes } from "@octokit/rest";
|
|
||||||
import { LuBook, LuChevronDown, LuChevronUp } from "react-icons/lu";
|
|
||||||
import markdownIt from "markdown-it";
|
|
||||||
import sanitizeHtml from "sanitize-html";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
release: RestEndpointMethodTypes["repos"]["getReleaseByTag"]["response"]["data"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ReleaseBody({ release }: Props) {
|
|
||||||
const summary = "Release Notes & Changelogs";
|
|
||||||
const body = release.body ?? "No release notes available.";
|
|
||||||
|
|
||||||
const md = markdownIt({
|
|
||||||
html: true,
|
|
||||||
linkify: true,
|
|
||||||
typographer: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sanitizedBody = sanitizeHtml(md.render(body));
|
|
||||||
return (<details className="rounded-md p-4 my-4 preset-tonal-primary group">
|
|
||||||
<summary className="flex items-center cursor-pointer font-semibold text-lg gap-2">
|
|
||||||
<LuBook className="inline" />
|
|
||||||
{summary}
|
|
||||||
<span className="ml-auto flex items-center">
|
|
||||||
<span className="block group-open:hidden">
|
|
||||||
<LuChevronDown />
|
|
||||||
</span>
|
|
||||||
<span className="hidden group-open:block">
|
|
||||||
<LuChevronUp />
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</summary>
|
|
||||||
<article
|
|
||||||
className="prose lg:prose-xl dark:prose-invert"
|
|
||||||
dangerouslySetInnerHTML={{ __html: sanitizedBody }}
|
|
||||||
/>
|
|
||||||
</details>)
|
|
||||||
}
|
|
||||||
@ -1,183 +0,0 @@
|
|||||||
import { formatDistanceToNow, formatRelative } from "date-fns";
|
|
||||||
import ReleaseBody from "~/modules/downloads/older/release-body";
|
|
||||||
import RootLayout from "~/layouts/RootLayout.astro";
|
|
||||||
import { Octokit, type RestEndpointMethodTypes } from "@octokit/rest";
|
|
||||||
import {
|
|
||||||
FaAndroid,
|
|
||||||
FaApple,
|
|
||||||
FaGit,
|
|
||||||
FaGooglePlay,
|
|
||||||
FaLinux,
|
|
||||||
FaWindows,
|
|
||||||
} from "react-icons/fa6";
|
|
||||||
import type { IconType } from "react-icons";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
function getIcon(assetUrl: string) {
|
|
||||||
assetUrl = assetUrl.toLowerCase();
|
|
||||||
if (assetUrl.includes("linux")) return FaLinux;
|
|
||||||
if (assetUrl.includes("windows")) return FaWindows;
|
|
||||||
if (assetUrl.includes("mac")) return FaApple;
|
|
||||||
if (assetUrl.includes("android")) return FaAndroid;
|
|
||||||
if (assetUrl.includes("playstore")) return FaGooglePlay;
|
|
||||||
if (assetUrl.includes("ios")) return FaApple;
|
|
||||||
|
|
||||||
return FaGit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatName(assetName: string) {
|
|
||||||
// format the assetName to be
|
|
||||||
// {OS} ({package extension})
|
|
||||||
|
|
||||||
const lowerCasedAssetName = assetName.toLowerCase();
|
|
||||||
const extension = assetName.split(".").at(-1);
|
|
||||||
|
|
||||||
if (lowerCasedAssetName.includes("linux")) {
|
|
||||||
if (lowerCasedAssetName.includes("aarch64")) {
|
|
||||||
return [`Linux`, extension, `ARM64`]
|
|
||||||
}
|
|
||||||
return [`Linux`, extension, `x64`]
|
|
||||||
};
|
|
||||||
if (lowerCasedAssetName.includes("windows")) return [`Windows`, extension];
|
|
||||||
if (lowerCasedAssetName.includes("mac")) return [`macOS`, extension];
|
|
||||||
if (
|
|
||||||
lowerCasedAssetName.includes("android") ||
|
|
||||||
lowerCasedAssetName.includes("playstore")
|
|
||||||
)
|
|
||||||
return [`Android`, extension];
|
|
||||||
if (lowerCasedAssetName.includes("ios")) return [`iOS`, extension];
|
|
||||||
|
|
||||||
return [assetName.replace(`.${extension}`, ""), extension];
|
|
||||||
}
|
|
||||||
|
|
||||||
type OctokitAsset =
|
|
||||||
RestEndpointMethodTypes["repos"]["listReleases"]["response"]["data"][0]["assets"][0];
|
|
||||||
|
|
||||||
function groupByOS(downloads: OctokitAsset[]) {
|
|
||||||
return downloads.reduce(
|
|
||||||
(acc, val) => {
|
|
||||||
const lowName = val.name.toLowerCase();
|
|
||||||
|
|
||||||
if (lowName.includes("android") || lowName.includes("playstore"))
|
|
||||||
acc["android"] = [...(acc.android ?? []), val];
|
|
||||||
if (lowName.includes("linux"))
|
|
||||||
acc["linux"] = [...(acc["linux"] ?? []), val];
|
|
||||||
if (lowName.includes("windows"))
|
|
||||||
acc["windows"] = [...(acc["windows"] ?? []), val];
|
|
||||||
if (lowName.includes("ios")) acc["ios"] = [...(acc["ios"] ?? []), val];
|
|
||||||
if (lowName.includes("mac")) acc["mac"] = [...(acc["mac"] ?? []), val];
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as Record<
|
|
||||||
"android" | "ios" | "mac" | "linux" | "windows",
|
|
||||||
OctokitAsset[]
|
|
||||||
>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const icons: Record<string, [IconType, string]> = {
|
|
||||||
android: [FaAndroid, "#3DDC84"],
|
|
||||||
mac: [FaApple, ""],
|
|
||||||
ios: [FaApple, ""],
|
|
||||||
linux: [FaLinux, "#000000"],
|
|
||||||
windows: [FaWindows, "#0078D7"],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ReleasesSection() {
|
|
||||||
const github = new Octokit();
|
|
||||||
|
|
||||||
const [releases, setReleases] = useState<RestEndpointMethodTypes["repos"]["listReleases"]["response"]["data"]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
github.repos.listReleases({
|
|
||||||
owner: "KRTirtho",
|
|
||||||
repo: "spotube",
|
|
||||||
|
|
||||||
}).then((res) => {
|
|
||||||
setReleases(
|
|
||||||
res.data.filter((release) => {
|
|
||||||
// Ignore all releases that were published before March 18 2025
|
|
||||||
return new Date(release.published_at ?? new Date()) >= new Date("2025-03-18T00:00:00Z");
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <>
|
|
||||||
{
|
|
||||||
releases.map((release) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h4
|
|
||||||
className="h4"
|
|
||||||
title={formatRelative(
|
|
||||||
release.published_at ?? new Date(),
|
|
||||||
new Date()
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{release.tag_name}
|
|
||||||
<span className="text-sm font-normal">
|
|
||||||
(
|
|
||||||
{formatDistanceToNow(release.published_at ?? new Date(), {
|
|
||||||
addSuffix: true,
|
|
||||||
})}
|
|
||||||
)
|
|
||||||
</span>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-5">
|
|
||||||
{Object.entries(groupByOS(release.assets)).map(
|
|
||||||
([osName, assets]) => {
|
|
||||||
const Icon = icons[osName][0];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<h5 className="h5 capitalize">
|
|
||||||
<Icon className="inline" color={icons[osName][1]} />
|
|
||||||
{osName}
|
|
||||||
</h5>
|
|
||||||
<div className="flex flex-wrap gap-4">
|
|
||||||
{assets.map((asset) => {
|
|
||||||
const Icon = getIcon(asset.browser_download_url);
|
|
||||||
const formattedName = formatName(asset.name);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a href={asset.browser_download_url}>
|
|
||||||
<button className="btn preset-tonal-primary rounded p-0 flex flex-col">
|
|
||||||
<span className="bg-primary-500 rounded-t p-3 w-full">
|
|
||||||
<Icon className="inline" />
|
|
||||||
</span>
|
|
||||||
<span className="p-4 space-x-1">
|
|
||||||
<span>
|
|
||||||
{formattedName[0]}
|
|
||||||
</span>
|
|
||||||
<span className="chip preset-tonal-error">
|
|
||||||
{formattedName[1]}
|
|
||||||
</span>
|
|
||||||
{
|
|
||||||
formattedName[2] ?
|
|
||||||
<span className="chip preset-tonal-error">
|
|
||||||
{formattedName[2]}
|
|
||||||
</span> : <></>
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<ReleaseBody release={release} />
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Avatar } from "@skeletonlabs/skeleton-react";
|
|
||||||
|
|
||||||
interface Member {
|
|
||||||
MemberId: number;
|
|
||||||
createdAt: string;
|
|
||||||
type: string;
|
|
||||||
role: string;
|
|
||||||
isActive: boolean;
|
|
||||||
totalAmountDonated: number;
|
|
||||||
currency?: string;
|
|
||||||
lastTransactionAt: string;
|
|
||||||
lastTransactionAmount: number;
|
|
||||||
profile: string;
|
|
||||||
name: string;
|
|
||||||
company?: string;
|
|
||||||
description?: string;
|
|
||||||
image?: string;
|
|
||||||
email?: string;
|
|
||||||
twitter?: string;
|
|
||||||
github?: string;
|
|
||||||
website?: string;
|
|
||||||
tier?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatter = new Intl.NumberFormat('en-US', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'USD',
|
|
||||||
compactDisplay: 'short',
|
|
||||||
maximumFractionDigits: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
export function Supporters() {
|
|
||||||
const [members, setMembers] = useState<Member[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Fetch members data from an API or other source
|
|
||||||
async function fetchMembers() {
|
|
||||||
const res = await fetch('https://opencollective.com/spotube/members/all.json');
|
|
||||||
const members = (await res.json()) as Member[];
|
|
||||||
setMembers(
|
|
||||||
members
|
|
||||||
.filter((m) => m.totalAmountDonated > 0)
|
|
||||||
.sort((a, b) => b.totalAmountDonated - a.totalAmountDonated)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchMembers();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
return <div
|
|
||||||
className="gap-4 grid"
|
|
||||||
style={{
|
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(150px, 1fr))',
|
|
||||||
gridAutoRows: 'minmax(50px, auto)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
members.map((member) => {
|
|
||||||
return <a
|
|
||||||
key={member.MemberId}
|
|
||||||
href={member.profile}
|
|
||||||
target="_blank"
|
|
||||||
className="flex items-center gap-2 px-2 py-1 overflow-ellipsis preset-tonal-secondary rounded-lg"
|
|
||||||
>
|
|
||||||
<Avatar src={member.image} name={member.name} classes="w-10 h-10" />
|
|
||||||
<div className="flex flex-col overflow-hidden">
|
|
||||||
<p className="truncate">{member.name}</p>
|
|
||||||
<p className="capitalize text-sm underline decoration-dotted">
|
|
||||||
{formatter.format(member.totalAmountDonated)}
|
|
||||||
({member.role.toLowerCase()})
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</a>;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
@ -2,27 +2,4 @@
|
|||||||
import RootLayout from "~/layouts/RootLayout.astro";
|
import RootLayout from "~/layouts/RootLayout.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<RootLayout>
|
<RootLayout />
|
||||||
<section class="p-4 md:p-16">
|
|
||||||
<h2 class="h2">About</h2>
|
|
||||||
|
|
||||||
<br /><br />
|
|
||||||
|
|
||||||
<h4 class="h4">Author & Developer</h4>
|
|
||||||
<br />
|
|
||||||
<a
|
|
||||||
href="https://github.com/KRTirtho"
|
|
||||||
target="_blank"
|
|
||||||
class="btn preset-tonal-primary max-w-44 flex flex-col items-center p-4 rounded-2xl"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Author of Spotube"
|
|
||||||
src="https://github.com/KRTirtho.png"
|
|
||||||
class="h-auto w-40 rounded-full"
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<h5>Kingkor Roy Tirtho</h5>
|
|
||||||
<p>Flutter developer</p>
|
|
||||||
</a>
|
|
||||||
</section>
|
|
||||||
</RootLayout>
|
|
||||||
|
|||||||
@ -1,53 +1,5 @@
|
|||||||
---
|
---
|
||||||
import type { IconType } from "react-icons";
|
|
||||||
import { LuDownload, LuHistory, LuPackage, LuSparkles } from "react-icons/lu";
|
|
||||||
import { extendedDownloadLinks } from "~/collections/app";
|
|
||||||
import RootLayout from "~/layouts/RootLayout.astro";
|
import RootLayout from "~/layouts/RootLayout.astro";
|
||||||
import DownloadItems from "~/modules/downloads/download-item.astro";
|
|
||||||
|
|
||||||
const otherDownloads: [string, string, IconType][] = [
|
|
||||||
["/downloads/packages", "CLI Packages Managers", LuPackage],
|
|
||||||
["/downloads/older", "Older Versions", LuHistory],
|
|
||||||
["/downloads/nightly", "Nightly Builds", LuSparkles],
|
|
||||||
];
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<RootLayout>
|
<RootLayout />
|
||||||
<section class="p-4 md:p-16 md:pb-4">
|
|
||||||
<h2 class="h2 flex items-center gap-4">
|
|
||||||
Download
|
|
||||||
<LuDownload className="inline" size={30} />
|
|
||||||
</h2>
|
|
||||||
<br /><br />
|
|
||||||
<h5 class="h5">Spotube is available for every platform</h5>
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<DownloadItems links={extendedDownloadLinks} />
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<!-- <Ads adSlot={ADS_SLOTS.downloadPageDisplay} adFormat="auto" /> -->
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<h2 class="h2">Other Downloads</h2>
|
|
||||||
<br /><br />
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2 max-w-3xl">
|
|
||||||
{
|
|
||||||
otherDownloads.map((download) => {
|
|
||||||
const Icon = download[2];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a href={download[0]}>
|
|
||||||
<div class="btn preset-tonal-secondary flex flex-col items-center p-4 gap-4">
|
|
||||||
<Icon />
|
|
||||||
<h5 class="h5">{download[1]}</h5>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<!-- <Ads adSlot={ADS_SLOTS.downloadPageDisplay} adFormat="auto" /> -->
|
|
||||||
</section>
|
|
||||||
</RootLayout>
|
|
||||||
|
|||||||
@ -1,47 +0,0 @@
|
|||||||
---
|
|
||||||
import { LuBug, LuSparkles, LuTriangleAlert } from "react-icons/lu";
|
|
||||||
import { extendedNightlyDownloadLinks } from "~/collections/app";
|
|
||||||
import RootLayout from "~/layouts/RootLayout.astro";
|
|
||||||
import DownloadItems from "~/modules/downloads/download-item.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<RootLayout>
|
|
||||||
<section class="p-4 md:p-16">
|
|
||||||
<h2 class="h2 flex items-center gap-4">
|
|
||||||
Nightly Downloads
|
|
||||||
<LuSparkles className="inline" size={30} />
|
|
||||||
</h2>
|
|
||||||
<br /><br />
|
|
||||||
<aside class="preset-tonal-warning rounded-xl">
|
|
||||||
<div class="h3 pl-4 pt-4">
|
|
||||||
<LuTriangleAlert className="text-warning-500" />
|
|
||||||
</div>
|
|
||||||
<div class="p-4">
|
|
||||||
<h3 class="h3">
|
|
||||||
Nightly versions may contain bugs <LuBug className="inline" />
|
|
||||||
</h3>
|
|
||||||
<p>
|
|
||||||
Although Nightly versions are packed with newest and greatest
|
|
||||||
features, it's often unstable and not tested by the maintainers and
|
|
||||||
publisher(s).
|
|
||||||
<br />
|
|
||||||
<span class="text-error-500 underline decoration-dotted">
|
|
||||||
So use it at your own risk.
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
Go to <a href="/downloads" class="anchor">Downloads</a> for more stable
|
|
||||||
releases.
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<p class="mb-4">Following are the new v5 Nightly versions:</p>
|
|
||||||
<DownloadItems links={extendedNightlyDownloadLinks} />
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<!-- <Ads adSlot={ADS_SLOTS.downloadPageDisplay} adFormat="auto" /> -->
|
|
||||||
<br />
|
|
||||||
</section>
|
|
||||||
</RootLayout>
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
import RootLayout from "~/layouts/RootLayout.astro";
|
|
||||||
import ReleasesSection from "~/modules/downloads/older/releases";
|
|
||||||
---
|
|
||||||
|
|
||||||
<RootLayout>
|
|
||||||
<div class="p-4 md:p-24">
|
|
||||||
<div class="flex flex-col gap-5">
|
|
||||||
<ReleasesSection client:only />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</RootLayout>
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import { FaLinux, FaWindows, FaApple } from 'react-icons/fa6';
|
|
||||||
import RootLayout from 'layouts/RootLayout.astro';
|
|
||||||
import MarkdownLayout from 'layouts/MarkdownLayout.astro';
|
|
||||||
|
|
||||||
<RootLayout>
|
|
||||||
<MarkdownLayout>
|
|
||||||
<div class="p-4 md:ps-24">
|
|
||||||
<h2 class="h2">Package Managers</h2>
|
|
||||||
Spotube is available in various Package Managers supported by Platform
|
|
||||||
## <FaLinux className="inline" /> Linux
|
|
||||||
### Flatpak📦
|
|
||||||
Make sure [Flatpak](https://flatpak.org) is installed in your Linux device & Run the following command in the terminal:
|
|
||||||
```bash
|
|
||||||
$ flatpak install com.github.KRTirtho.Spotube
|
|
||||||
```
|
|
||||||
### Arch User Repository (AUR)♾️
|
|
||||||
If you're an Arch Linux user, you can also install Spotube from AUR.
|
|
||||||
Make sure you have `yay`/`pamac`/`paru` installed in your system. And Run the Following command in the Terminal:
|
|
||||||
```bash
|
|
||||||
$ yay -Sy spotube-bin
|
|
||||||
```
|
|
||||||
```bash
|
|
||||||
$ pamac install spotube-bin
|
|
||||||
```
|
|
||||||
```bash
|
|
||||||
$ paru -Sy spotube-bin
|
|
||||||
```
|
|
||||||
{/* <Ads
|
|
||||||
style="display:block; text-align:center;"
|
|
||||||
adSlot={ADS_SLOTS.packagePageArticle}
|
|
||||||
adLayout="in-article"
|
|
||||||
adFormat="fluid"
|
|
||||||
fullWidthResponsive={false}
|
|
||||||
/> */}
|
|
||||||
## <FaApple className="inline" /> MacOS
|
|
||||||
### Homebrew🍻
|
|
||||||
Spotube can be installed through Homebrew. We host our own cask definition thus you'll need to add our tap first:
|
|
||||||
```bash
|
|
||||||
$ brew tap krtirtho/apps
|
|
||||||
$ brew install --cask spotube
|
|
||||||
```
|
|
||||||
{/* <Ads
|
|
||||||
style="display:block; text-align:center;"
|
|
||||||
adSlot={ADS_SLOTS.packagePageArticle}
|
|
||||||
adLayout="in-article"
|
|
||||||
adFormat="fluid"
|
|
||||||
fullWidthResponsive={false}
|
|
||||||
/> */}
|
|
||||||
## <FaWindows className="inline" color="#00A2F0" /> Windows
|
|
||||||
### Chocolatey🍫
|
|
||||||
Spotube is available in [community.chocolatey.org](https://community.chocolatey.org) repo. If you have chocolatey install in your system just run following command in an Elevated Command Prompt or PowerShell:
|
|
||||||
```powershell
|
|
||||||
$ choco install spotube
|
|
||||||
```
|
|
||||||
### WinGet💫
|
|
||||||
Spotube is also available in the Official Windows PackageManager WinGet. Make sure you have WinGet installed in your Windows machine and run following in a Terminal:
|
|
||||||
```powershell
|
|
||||||
$ winget install --id KRTirtho.Spotube
|
|
||||||
```
|
|
||||||
### Scoop🥄
|
|
||||||
Spotube is also available in [Scoop](https://scoop.sh) bucket. Make sure you have Scoop installed in your Windows machine and run following in a Terminal:
|
|
||||||
```powershell
|
|
||||||
$ scoop bucket add extras
|
|
||||||
$ scoop install spotube
|
|
||||||
```
|
|
||||||
</div>
|
|
||||||
</MarkdownLayout>
|
|
||||||
</RootLayout>
|
|
||||||
@ -1,78 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { FaAndroid, FaApple, FaLinux, FaWindows } from "react-icons/fa6";
|
|
||||||
import RootLayout from "../layouts/RootLayout.astro";
|
import RootLayout from "../layouts/RootLayout.astro";
|
||||||
import { LuHeart } from "react-icons/lu";
|
|
||||||
import { Supporters } from "~/modules/root/supporters";
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<RootLayout>
|
<RootLayout />
|
||||||
<section class="ps-4 pt-16 md:ps-24 md:pt-24">
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
<div>
|
|
||||||
<h1 class="h1">Spotube</h1>
|
|
||||||
<br />
|
|
||||||
<h3 class="h3">
|
|
||||||
A cross-platform Extensible open-source Music Streaming platform
|
|
||||||
<div class="inline-flex gap-3 items-center">
|
|
||||||
<FaAndroid className="inline text-[#3DDC84]" />
|
|
||||||
<FaWindows className="inline text-[#00A2F0]" />
|
|
||||||
<FaLinux className="inline" />
|
|
||||||
<FaApple className="inline" />
|
|
||||||
</div>
|
|
||||||
</h3>
|
|
||||||
<p class="text-surface-500">
|
|
||||||
And it's <span class="text-error-500 underline decoration-dashed"
|
|
||||||
>not</span
|
|
||||||
>
|
|
||||||
built with Electron (web technologies)
|
|
||||||
</p>
|
|
||||||
<br />
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<a
|
|
||||||
href="https://news.ycombinator.com/item?id=39066136"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="https://hackerbadge.vercel.app/api?id=39066136"
|
|
||||||
alt="HackerNews"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<!-- <a href="https://flathub.org/apps/com.github.KRTirtho.Spotube" target="_blank">
|
|
||||||
<img
|
|
||||||
width="160"
|
|
||||||
alt="Download on Flathub"
|
|
||||||
src="https://flathub.org/api/badge?locale=en"
|
|
||||||
/>
|
|
||||||
</a> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<!-- <Ads adSlot={ADS_SLOTS.rootPageDisplay} adFormat="auto" /> -->
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
<h2 class="h2">
|
|
||||||
Supporters
|
|
||||||
<LuHeart className="inline-block" color="red" />
|
|
||||||
</h2>
|
|
||||||
<p class="text-surface-500">
|
|
||||||
We are grateful for the support of individuals and organizations who
|
|
||||||
have made Spotube possible.
|
|
||||||
</p>
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<a href="https://opencollective.com/spotube/donate" target="_blank">
|
|
||||||
<img
|
|
||||||
src="https://opencollective.com/webpack/donate/button@2x.png?color=blue"
|
|
||||||
width="300"
|
|
||||||
alt="Open Collective"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<Supporters client:only />
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<!-- <Ads adSlot={ADS_SLOTS.rootPageDisplay} adFormat="auto" /> -->
|
|
||||||
</section>
|
|
||||||
</RootLayout>
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@plugin "@tailwindcss/typography";
|
|
||||||
|
|
||||||
@source '../../node_modules/@skeletonlabs/skeleton-react/dist';
|
@source '../../node_modules/@skeletonlabs/skeleton-react/dist';
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,5 @@
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
"baseUrl": "./src",
|
"baseUrl": "./src",
|
||||||
"module": "node16",
|
|
||||||
"moduleResolution": "node16"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user