mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-16 17:05:17 +00:00
Compare commits
3 Commits
49e2d1b759
...
f228937e3e
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f228937e3e | ||
![]() |
ee7d0cfeb5 | ||
![]() |
7a630507fb |
@ -5,11 +5,13 @@ 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()]
|
integrations: [react(), mdx()]
|
||||||
});
|
});
|
@ -9,19 +9,27 @@
|
|||||||
"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
3
website/src/layouts/MarkdownLayout.astro
Normal file
3
website/src/layouts/MarkdownLayout.astro
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div class="prose lg:prose-lg dark:prose-invert max-w-5xl mx-auto">
|
||||||
|
<slot />
|
||||||
|
</div>
|
@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
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";
|
||||||
---
|
---
|
||||||
@ -11,10 +12,52 @@ 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>
|
||||||
<TopBar />
|
<main class="p-2 md:p-4 min-h-[90vh]">
|
||||||
<slot />
|
<TopBar />
|
||||||
|
<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>
|
||||||
|
|
||||||
|
33
website/src/modules/downloads/download-item.astro
Normal file
33
website/src/modules/downloads/download-item.astro
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
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>
|
39
website/src/modules/downloads/older/release-body.tsx
Normal file
39
website/src/modules/downloads/older/release-body.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
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>)
|
||||||
|
}
|
183
website/src/modules/downloads/older/releases.tsx
Normal file
183
website/src/modules/downloads/older/releases.tsx
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
80
website/src/modules/root/supporters.tsx
Normal file
80
website/src/modules/root/supporters.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
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,4 +2,27 @@
|
|||||||
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,5 +1,53 @@
|
|||||||
---
|
---
|
||||||
|
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>
|
||||||
|
47
website/src/pages/downloads/nightly/index.astro
Normal file
47
website/src/pages/downloads/nightly/index.astro
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
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>
|
12
website/src/pages/downloads/older/index.astro
Normal file
12
website/src/pages/downloads/older/index.astro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
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>
|
68
website/src/pages/downloads/packages/index.mdx
Normal file
68
website/src/pages/downloads/packages/index.mdx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
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,5 +1,78 @@
|
|||||||
---
|
---
|
||||||
|
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,4 +1,5 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
@plugin "@tailwindcss/typography";
|
||||||
|
|
||||||
@source '../../node_modules/@skeletonlabs/skeleton-react/dist';
|
@source '../../node_modules/@skeletonlabs/skeleton-react/dist';
|
||||||
|
|
||||||
|
@ -16,5 +16,7 @@
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
"baseUrl": "./src",
|
"baseUrl": "./src",
|
||||||
|
"module": "node16",
|
||||||
|
"moduleResolution": "node16"
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user