mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +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 mdx from '@astrojs/mdx';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
plugins: [tailwindcss()]
|
||||
},
|
||||
|
||||
integrations: [react()]
|
||||
integrations: [react(), mdx()]
|
||||
});
|
@ -9,19 +9,27 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.3.3",
|
||||
"@astrojs/react": "^4.3.0",
|
||||
"@octokit/rest": "^22.0.0",
|
||||
"@skeletonlabs/skeleton-react": "^1.2.4",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@types/react": "^19.1.9",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"astro": "^5.12.8",
|
||||
"date-fns": "^4.1.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"sanitize-html": "^2.17.0",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"usehooks-ts": "^3.1.1"
|
||||
},
|
||||
"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 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" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<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>
|
||||
<body>
|
||||
<TopBar />
|
||||
<slot />
|
||||
<main class="p-2 md:p-4 min-h-[90vh]">
|
||||
<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>
|
||||
</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";
|
||||
---
|
||||
|
||||
<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 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 { 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";
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@source '../../node_modules/@skeletonlabs/skeleton-react/dist';
|
||||
|
||||
|
@ -16,5 +16,7 @@
|
||||
],
|
||||
},
|
||||
"baseUrl": "./src",
|
||||
"module": "node16",
|
||||
"moduleResolution": "node16"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user