feat: add older and nightly downloads page

This commit is contained in:
Kingkor Roy Tirtho 2024-02-09 13:07:39 +06:00
parent 36cf0a8141
commit 484068fc38
12 changed files with 369 additions and 20 deletions

View File

@ -10,12 +10,15 @@
"dependencies": {
"@floating-ui/dom": "1.6.1",
"@fortawesome/free-brands-svg-icons": "^6.5.1",
"@octokit/openapi-types": "^19.1.0",
"@octokit/rest": "^20.0.2",
"date-fns": "^3.3.1",
"highlight.js": "11.9.0",
"lucide-svelte": "^0.323.0",
"remark-container": "^0.1.2",
"remark-github": "^12.0.0",
"svelte-fa": "^4.0.2"
"svelte-fa": "^4.0.2",
"svelte-markdown": "^0.4.1"
},
"devDependencies": {
"@playwright/test": "^1.28.1",
@ -1231,6 +1234,11 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/marked": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz",
"integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg=="
},
"node_modules/@types/mdast": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz",
@ -1878,6 +1886,15 @@
"node": ">=4"
}
},
"node_modules/date-fns": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
"integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@ -2946,6 +2963,17 @@
"node": ">=12"
}
},
"node_modules/marked": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-5.1.2.tgz",
"integrity": "sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 16"
}
},
"node_modules/mdast-util-find-and-replace": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz",
@ -4270,6 +4298,18 @@
"svelte": "^3.19.0 || ^4.0.0"
}
},
"node_modules/svelte-markdown": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/svelte-markdown/-/svelte-markdown-0.4.1.tgz",
"integrity": "sha512-pOlLY6EruKJaWI9my/2bKX8PdTeP5CM0s4VMmwmC2prlOkjAf+AOmTM4wW/l19Y6WZ87YmP8+ZCJCCwBChWjYw==",
"dependencies": {
"@types/marked": "^5.0.1",
"marked": "^5.1.2"
},
"peerDependencies": {
"svelte": "^4.0.0"
}
},
"node_modules/svelte-preprocess": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz",

View File

@ -44,11 +44,14 @@
"dependencies": {
"@floating-ui/dom": "1.6.1",
"@fortawesome/free-brands-svg-icons": "^6.5.1",
"@octokit/openapi-types": "^19.1.0",
"@octokit/rest": "^20.0.2",
"date-fns": "^3.3.1",
"highlight.js": "11.9.0",
"lucide-svelte": "^0.323.0",
"remark-container": "^0.1.2",
"remark-github": "^12.0.0",
"svelte-fa": "^4.0.2"
"svelte-fa": "^4.0.2",
"svelte-markdown": "^0.4.1"
}
}

View File

@ -0,0 +1,25 @@
<script lang="ts">
import type { IconDefinition } from '@fortawesome/free-brands-svg-icons';
import Fa from 'svelte-fa';
export let links: Record<string, [string, IconDefinition[], string]>;
</script>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{#each Object.entries(links) as link}
<a
href={link[1][0]}
class="flex flex-col btn variant-ghost-primary rounded-xl p-0 overflow-hidden"
>
<div class="relative bg-primary-500 p-4 flex gap-4 justify-center rounded-t-xl w-full">
{#each link[1][1] as icon}
<Fa {icon} />
{/each}
<p class="chip variant-ghost-warning text-warning-400 absolute right-2 uppercase">
{link[1][2]}
</p>
</div>
<p class="p-4">{link[0]}</p>
</a>
{/each}
</div>

View File

@ -0,0 +1,3 @@
<article class="prose lg:prose-lg max-w-3xl">
<slot />
</article>

View File

@ -20,7 +20,9 @@
>
<Menu />
</button>
<h2 class="text-3xl">Spotube</h2>
<h2 class="text-3xl">
<a href="/"> Spotube </a>
</h2>
</div>
<a
class="mw-2 md:me-4"

View File

@ -44,3 +44,18 @@ export const extendedDownloadLinks: Record<string, [string, IconDefinition[], st
],
iPhone: [`${releasesUrl}/Spotube-iOS.ipa`, [faApple], 'ipa']
};
const nightlyReleaseUrl = 'https://github.com/KRTirtho/Spotube/releases/download/nightly';
export const extendedNightlyDownloadLinks: Record<string, [string, IconDefinition[], string]> = {
Android: [`${nightlyReleaseUrl}/Spotube-android-all-arch.apk`, [faAndroid], 'apk'],
Windows: [`${nightlyReleaseUrl}/Spotube-windows-x86_64-setup.exe`, [faWindows], 'exe'],
macOS: [`${nightlyReleaseUrl}/Spotube-macos-universal.dmg`, [faApple], 'dmg'],
'Ubuntu, Debian': [`${nightlyReleaseUrl}/Spotube-linux-x86_64.deb`, [faUbuntu, faDebian], 'deb'],
'Fedora, Redhat, Opensuse': [
`${nightlyReleaseUrl}/Spotube-linux-x86_64.rpm`,
[faFedora, faRedhat, faOpensuse],
'rpm'
],
iPhone: [`${nightlyReleaseUrl}/Spotube-iOS.ipa`, [faApple], 'ipa']
};

View File

@ -1,7 +1,14 @@
<script lang="ts">
import { extendedDownloadLinks } from '$lib';
import { Download } from 'lucide-svelte';
import Fa from 'svelte-fa';
import { History, Sparkles, Package } from 'lucide-svelte';
import DownloadItems from '../../components/downloads/download-items.svelte';
const otherDownloads: [string, string, any][] = [
['/downloads/packages', 'CLI Packages Managers', Package],
['/downloads/older', 'Older Versions', History],
['/downloads/nightly', 'Nightly Builds', Sparkles]
];
</script>
<section class="p-4 md:p-16">
@ -13,18 +20,19 @@
<h5 class="h5">Spotube is available for every platform</h5>
<br />
<div class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{#each Object.entries(extendedDownloadLinks) as links}
<a href={links[1][0]} class="flex flex-col btn variant-ghost-primary rounded-xl p-0">
<div class="relative bg-primary-500 p-4 flex gap-4 justify-center rounded-t-xl w-full">
{#each links[1][1] as icon}
<Fa {icon} />
{/each}
<p class="chip variant-ghost-error text-error-400 absolute right-2 uppercase">
{links[1][2]}
</p>
<DownloadItems links={extendedDownloadLinks} />
<br /><br /><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">
{#each otherDownloads as download}
<a href={download[0]}>
<div class="btn rounded variant-soft-secondary flex flex-col items-center p-4 gap-4">
<svelte:component this={download[2]} />
<h5 class="h5">{download[1]}</h5>
</div>
<p class="p-4">{links[0]}</p>
</a>
{/each}
</div>

View File

@ -0,0 +1,33 @@
<script>
import { AlertTriangle, Bug, Sparkles } from 'lucide-svelte';
import DownloadItems from '../../../components/downloads/download-items.svelte';
import { extendedNightlyDownloadLinks } from '$lib';
</script>
<section class="p-4 md:p-16">
<h2 class="h2 flex items-center gap-4">
Nightly Downloads
<Sparkles class="inline" size={30} />
</h2>
<br /><br />
<aside class="alert variant-ghost-warning">
<div><AlertTriangle class="text-warning-500" /></div>
<div class="alert-message">
<h3 class="h3">Nightly versions may contain bugs <Bug class="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 />
<DownloadItems links={extendedNightlyDownloadLinks} />
</section>

View File

@ -0,0 +1,138 @@
<script lang="ts">
import SvelteMarkdown from 'svelte-markdown';
import type { PageData } from './$types';
import { formatDistanceToNow, formatRelative } from 'date-fns';
import Layout from '../../../components/markdown/layout.svelte';
import { Accordion, AccordionItem } from '@skeletonlabs/skeleton';
import { Book, History } from 'lucide-svelte';
import {
faAndroid,
faApple,
faGit,
faGooglePlay,
faLinux,
faWindows,
type IconDefinition
} from '@fortawesome/free-brands-svg-icons';
import Fa from 'svelte-fa';
import type { RestEndpointMethodTypes } from '@octokit/rest';
export let data: PageData;
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')) return [`Linux`, extension];
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, [IconDefinition, string]> = {
android: [faAndroid, '#3DDC84'],
mac: [faApple, ''],
ios: [faApple, ''],
linux: [faLinux, '#000000'],
windows: [faWindows, '#0078D7']
};
</script>
<div class="p-4 md:p-24">
<div class="flex gap-2 items-center">
<h2 class="h2">Older versions</h2>
<History />
</div>
<br /><br />
<Accordion>
<div class="flex flex-col gap-5">
{#each data.releases as release}
<h4 class="h4" title={formatRelative(release.published_at ?? new Date(), new Date())}>
{release.tag_name}
<span class="text-sm font-normal">
({formatDistanceToNow(release.published_at ?? new Date(), { addSuffix: true })})
</span>
</h4>
<div class="flex flex-col gap-5">
{#each Object.entries(groupByOS(release.assets)) as [osName, assets]}
<div class="flex flex-col gap-4">
<h5 class="h5 capitalize">
<Fa class="inline" icon={icons[osName][0]} color={icons[osName][1]} />
{osName}
</h5>
<div class="flex flex-wrap gap-4">
{#each assets as asset}
<a href={release.assets_url}>
<button class="btn variant-glass-primary rounded p-0 flex flex-col gap-2">
<span class="bg-primary-500 rounded-t p-3 w-full">
<Fa class="inline" icon={getIcon(asset.browser_download_url)} />
</span>
<span class="p-4">
{formatName(asset.name)[0]}
<span class="chip variant-ghost-error">
{formatName(asset.name)[1]}
</span>
</span>
</button>
</a>
{/each}
</div>
</div>
{/each}
</div>
<AccordionItem>
<svelte:fragment slot="lead">
<Book />
</svelte:fragment>
<svelte:fragment slot="summary">Release Notes & Changelogs</svelte:fragment>
<svelte:fragment slot="content">
<Layout>
<SvelteMarkdown source={release.body} />
</Layout>
</svelte:fragment>
</AccordionItem>
<hr />
{/each}
</div>
</Accordion>
</div>

View File

@ -0,0 +1,14 @@
import type { PageLoad } from './$types';
import { Octokit } from '@octokit/rest';
const github = new Octokit();
export const load: PageLoad = async () => {
const { data: releases } = await github.repos.listReleases({
owner: 'KRTirtho',
repo: 'spotube'
});
return {
releases
};
};

View File

@ -0,0 +1,70 @@
<script context="module">
export let metadata = {
title: 'CLI Packages Managers',
author: 'Kingkor Roy Tirtho'
};
</script>
<script lang="ts">
import { faLinux, faWindows } from '@fortawesome/free-brands-svg-icons';
import Fa from 'svelte-fa';
</script>
<div class="p-4 md:ps-24">
<h2 class="h2">Package Managers</h2>
Spotube is available in various Package Managers supported by Platform
## <Fa class="inline" icon={faLinux} /> 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
```
## <Fa class="inline" icon={faWindows} 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>

View File

@ -1,20 +1,18 @@
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { mdsvex } from 'mdsvex';
import containers from 'remark-container';
import github from 'remark-github';
/** @type {import('@sveltejs/kit').Config} */
const config = {
extensions: ['.svelte'],
extensions: ['.svelte', '.svx', '.md'],
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: [
vitePreprocess(),
mdsvex({
extensions: ['.svx'],
extensions: ['.svx', '.md'],
highlight: {},
remarkPlugins: [containers, github]
layout: './src/components/markdown/layout.svelte'
})
],
vitePlugin: {