Moved from Solidjs to Nextjs for better compatibility with docs & blogs stuff
6
website/.eslintrc.json
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"next/core-web-vitals",
|
||||||
|
"prettier"
|
||||||
|
]
|
||||||
|
}
|
37
website/.gitignore
vendored
Normal file → Executable file
@ -1,2 +1,35 @@
|
|||||||
node_modules
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
dist
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
35
website/README.md
Normal file → Executable file
@ -1,3 +1,34 @@
|
|||||||
# Website
|
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||||
|
|
||||||
The official Website of Spotube
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
|
||||||
|
|
||||||
|
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||||
|
123
website/components/AdDetector.tsx
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Heading,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
useDisclosure,
|
||||||
|
VStack,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import { FC, ReactNode, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const AdDetector: FC<{ children: ReactNode }> = ({ children }) => {
|
||||||
|
const [adBlockEnabled, setAdBlockEnabled] = useState(false);
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [joke, setJoke] = useState<Record<string, any>>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const googleAdUrl =
|
||||||
|
"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js";
|
||||||
|
try {
|
||||||
|
await fetch(new Request(googleAdUrl));
|
||||||
|
} catch (e) {
|
||||||
|
setAdBlockEnabled(true);
|
||||||
|
setJoke(
|
||||||
|
await (
|
||||||
|
await fetch(
|
||||||
|
"https://v2.jokeapi.dev/joke/Any?blacklistFlags=racist,sexist"
|
||||||
|
)
|
||||||
|
).json()
|
||||||
|
);
|
||||||
|
onOpen();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent mt="5" mx="3">
|
||||||
|
<ModalHeader>Support the Creator💚</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<p>
|
||||||
|
Open source developers work really hard to provide the best,
|
||||||
|
secure & efficient software experience for you & people all around
|
||||||
|
the world. Most of the time we work without any wages at all but
|
||||||
|
we need minimum support to live & these <b> Ads Helps Us</b> earn
|
||||||
|
the minimum wage that we need to live.{" "}
|
||||||
|
<Text color="green.500" fontWeight="bold" textAlign="justify">
|
||||||
|
So, please support Spotube by disabling the AdBlocker on this
|
||||||
|
page or by sponsoring or donating to our collectives directly
|
||||||
|
</Text>
|
||||||
|
</p>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button onClick={() => window.location.reload()}>
|
||||||
|
Reload without AdBlocker
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
{!adBlockEnabled ? (
|
||||||
|
children
|
||||||
|
) : (
|
||||||
|
<Stack
|
||||||
|
direction="column"
|
||||||
|
w="100vw"
|
||||||
|
h="100vh"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
p="5"
|
||||||
|
>
|
||||||
|
<Heading></Heading>
|
||||||
|
<VStack spacing="2" alignItems="flex-start">
|
||||||
|
<Heading size="sm">Here's something interesting:</Heading>
|
||||||
|
<Heading size="md">
|
||||||
|
{joke.joke ?? (
|
||||||
|
<>
|
||||||
|
<p>{joke.setup}</p>
|
||||||
|
<p>{joke.delivery}</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Heading>
|
||||||
|
</VStack>
|
||||||
|
<VStack justifySelf="flex-end">
|
||||||
|
<Heading
|
||||||
|
mt="10"
|
||||||
|
size={{
|
||||||
|
base: "lg",
|
||||||
|
lg: "xl",
|
||||||
|
}}
|
||||||
|
maxW="700px"
|
||||||
|
textAlign="justify"
|
||||||
|
lineHeight="1.5"
|
||||||
|
>
|
||||||
|
Be grateful for all the favors you get. But don't let it
|
||||||
|
become a pile of debt. Try returning them as soon as you can.
|
||||||
|
You'll feel relieved
|
||||||
|
</Heading>
|
||||||
|
<Heading
|
||||||
|
size={{
|
||||||
|
lg: "lg",
|
||||||
|
base: "md",
|
||||||
|
}}
|
||||||
|
alignSelf="flex-end"
|
||||||
|
>
|
||||||
|
- Kingkor Roy Tirtho
|
||||||
|
</Heading>
|
||||||
|
</VStack>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdDetector;
|
48
website/components/Navbar.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
Heading,
|
||||||
|
HStack,
|
||||||
|
IconButton,
|
||||||
|
useColorMode,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import NavLink from "next/link";
|
||||||
|
import { GoLightBulb } from "react-icons/go";
|
||||||
|
import { FiSun, } from "react-icons/fi";
|
||||||
|
|
||||||
|
const Navbar = () => {
|
||||||
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
|
return (
|
||||||
|
<HStack justifyContent="space-between" as="nav" w="full">
|
||||||
|
<HStack alignItems="center">
|
||||||
|
<NavLink href="/" passHref>
|
||||||
|
<Heading p="2" as="a" size="lg" mr="2">
|
||||||
|
Spotube
|
||||||
|
</Heading>
|
||||||
|
</NavLink>
|
||||||
|
<ButtonGroup>
|
||||||
|
<NavLink href="/other-downloads" passHref>
|
||||||
|
<Button as="a" colorScheme="gray" variant="ghost">
|
||||||
|
Other Downloads
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink href="/about" passHref>
|
||||||
|
<Button as="a" variant="ghost" colorScheme="gray">
|
||||||
|
About
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
</ButtonGroup>
|
||||||
|
</HStack>
|
||||||
|
<IconButton
|
||||||
|
variant="ghost"
|
||||||
|
icon={colorMode == "light" ? <GoLightBulb /> : <FiSun />}
|
||||||
|
aria-label="Dark Mode Toggle"
|
||||||
|
onClick={() => {
|
||||||
|
toggleColorMode();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Navbar;
|
72
website/components/special.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import Script from "next/script";
|
||||||
|
import { FC, useId } from "react";
|
||||||
|
|
||||||
|
type AdComponent = FC<{
|
||||||
|
slot: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const DisplayAd: AdComponent = ({ slot }) => {
|
||||||
|
const id = useId();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ins
|
||||||
|
className="adsbygoogle"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
data-ad-client={process.env.NEXT_PUBLIC_ADSENSE_ID}
|
||||||
|
data-ad-slot={slot}
|
||||||
|
data-ad-format="auto"
|
||||||
|
data-full-width-responsive="true"
|
||||||
|
></ins>
|
||||||
|
<Script
|
||||||
|
id={id + "#" + slot}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `(adsbygoogle = window.adsbygoogle || []).push({});`,
|
||||||
|
}}
|
||||||
|
></Script>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GridMultiplexAd: AdComponent = ({ slot }) => {
|
||||||
|
const id = useId();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ins
|
||||||
|
className="adsbygoogle"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
data-ad-format="autorelaxed"
|
||||||
|
data-ad-client={process.env.NEXT_PUBLIC_ADSENSE_ID}
|
||||||
|
data-ad-slot={slot}
|
||||||
|
></ins>
|
||||||
|
<Script
|
||||||
|
id={id + "#" + slot}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `(adsbygoogle = window.adsbygoogle || []).push({});`,
|
||||||
|
}}
|
||||||
|
></Script>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InFeedAd = () => {
|
||||||
|
const id = useId();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ins
|
||||||
|
className="adsbygoogle"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
data-ad-format="fluid"
|
||||||
|
data-ad-layout-key="-e5+6n-34-bt+x0"
|
||||||
|
data-ad-client="ca-pub-6419300932495863"
|
||||||
|
data-ad-slot="6460144484"
|
||||||
|
></ins>
|
||||||
|
<Script
|
||||||
|
id={id}
|
||||||
|
strategy="afterInteractive"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `(adsbygoogle = window.adsbygoogle || []).push({});`,
|
||||||
|
}}
|
||||||
|
></Script>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
17
website/configurations/gtag.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID;
|
||||||
|
|
||||||
|
// https://developers.google.com/analytics/devguides/collection/gtagjs/pages
|
||||||
|
export const pageview = (url: any) => {
|
||||||
|
(window as any).gtag("config", GA_TRACKING_ID, {
|
||||||
|
page_path: url,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
|
||||||
|
export const event = ({ action, category, label, value }: any) => {
|
||||||
|
(window as any).gtag("event", action, {
|
||||||
|
event_category: category,
|
||||||
|
event_label: label,
|
||||||
|
value: value,
|
||||||
|
});
|
||||||
|
};
|
26
website/hooks/usePlatform.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export enum Platform {
|
||||||
|
linux = "Linux",
|
||||||
|
windows = "Windows",
|
||||||
|
mac = "Mac",
|
||||||
|
android = "Android",
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePlatform(): Platform {
|
||||||
|
const [platform, setPlatform] = useState(Platform.linux);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const platform = (
|
||||||
|
((navigator as unknown as any).userAgentData?.platform as
|
||||||
|
| string
|
||||||
|
| undefined) ?? navigator.platform
|
||||||
|
).toLowerCase();
|
||||||
|
|
||||||
|
if (platform.includes("windows")) setPlatform(Platform.windows);
|
||||||
|
else if (platform.includes("mac")) setPlatform(Platform.mac);
|
||||||
|
else if (platform.includes("android")) setPlatform(Platform.android);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return platform;
|
||||||
|
}
|
@ -1,52 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<link
|
|
||||||
rel="apple-touch-icon"
|
|
||||||
sizes="180x180"
|
|
||||||
href="/src/assets/apple-touch-icon.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="32x32"
|
|
||||||
href="/src/assets/favicon-32x32.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="16x16"
|
|
||||||
href="/src/assets/favicon-16x16.png"
|
|
||||||
/>
|
|
||||||
<link rel="manifest" href="/src/assets/site.webmanifest" />
|
|
||||||
<title>Spotube</title>
|
|
||||||
<script
|
|
||||||
async
|
|
||||||
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-6419300932495863"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
></script>
|
|
||||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
|
||||||
<script
|
|
||||||
async
|
|
||||||
src="https://www.googletagmanager.com/gtag/js?id=G-L593ZMVP33"
|
|
||||||
></script>
|
|
||||||
<script>
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
|
||||||
function gtag() {
|
|
||||||
dataLayer.push(arguments);
|
|
||||||
}
|
|
||||||
gtag("js", new Date());
|
|
||||||
|
|
||||||
gtag("config", "G-L593ZMVP33");
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
|
|
||||||
<script src="/src/index.tsx" type="module"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
13
website/misc/MarkdownComponentDefs.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Link as Anchor, Heading, Text, chakra } from "@chakra-ui/react";
|
||||||
|
|
||||||
|
export const MarkdownComponentDefs = {
|
||||||
|
a: (props: any) => <Anchor {...props} color="blue.500" />,
|
||||||
|
h1: (props: any) => <Heading {...props} size="xl" mt="5" mb="1.5" />,
|
||||||
|
h2: (props: any) => <Heading {...props} size="lg" mt="5" mb="1.5" />,
|
||||||
|
h3: (props: any) => <Heading {...props} size="md" mt="5" mb="1.5" />,
|
||||||
|
h4: (props: any) => <Heading {...props} size="sm" />,
|
||||||
|
h5: (props: any) => <Heading {...props} size="xs" />,
|
||||||
|
h6: (props: any) => <Heading {...props} size="xs" />,
|
||||||
|
p: (props: any) => <Text {...props} />,
|
||||||
|
li: (props: any) => <chakra.li {...props} ml="4" />,
|
||||||
|
};
|
5
website/next-env.d.ts
vendored
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
7
website/next.config.js
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
swcMinify: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
@ -1,30 +1,34 @@
|
|||||||
{
|
{
|
||||||
"name": "vite-template-solid",
|
"name": "website",
|
||||||
"version": "0.0.0",
|
"version": "0.1.0",
|
||||||
"description": "",
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"dev": "next dev",
|
||||||
"dev": "vite",
|
"build": "next build",
|
||||||
"build": "vite build",
|
"start": "next start",
|
||||||
"serve": "vite preview"
|
"lint": "next lint"
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"devDependencies": {
|
|
||||||
"typescript": "^4.7.4",
|
|
||||||
"vite": "^3.0.0",
|
|
||||||
"vite-plugin-solid": "^2.3.0"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hope-ui/solid": "^0.6.2",
|
"@chakra-ui/react": "^2.2.4",
|
||||||
|
"@chakra-ui/theme-tools": "^2.0.5",
|
||||||
|
"@emotion/react": "^11",
|
||||||
|
"@emotion/styled": "^11",
|
||||||
"@octokit/rest": "^19.0.3",
|
"@octokit/rest": "^19.0.3",
|
||||||
"@stitches/core": "^1.2.8",
|
"framer-motion": "^6",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"next": "12.2.2",
|
||||||
"remark-gfm": "^3.0.1",
|
"react": "18.2.0",
|
||||||
"solid-app-router": "^0.4.1",
|
"react-dom": "18.2.0",
|
||||||
"solid-cached-resource": "^0.3.0",
|
"react-icons": "^4.4.0",
|
||||||
"solid-icons": "^0.5.0",
|
"react-markdown": "^8.0.3",
|
||||||
"solid-js": "^1.4.7",
|
"remark-gfm": "^3.0.1"
|
||||||
"solid-markdown": "^1.2.0",
|
},
|
||||||
"solid-transition-group": "^0.0.10"
|
"devDependencies": {
|
||||||
|
"@types/node": "18.0.5",
|
||||||
|
"@types/react": "18.0.15",
|
||||||
|
"@types/react-dom": "18.0.6",
|
||||||
|
"eslint": "8.20.0",
|
||||||
|
"eslint-config-next": "12.2.2",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
"typescript": "4.7.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
125
website/pages/_app.tsx
Executable file
@ -0,0 +1,125 @@
|
|||||||
|
import "../styles/globals.css";
|
||||||
|
import type { AppProps } from "next/app";
|
||||||
|
import {
|
||||||
|
ChakraProvider,
|
||||||
|
extendTheme,
|
||||||
|
withDefaultColorScheme,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import Navbar from "components/Navbar";
|
||||||
|
import { mode } from "@chakra-ui/theme-tools";
|
||||||
|
import Head from "next/head";
|
||||||
|
import Script from "next/script";
|
||||||
|
import * as gtag from "configurations/gtag";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import AdDetector from "components/AdDetector";
|
||||||
|
|
||||||
|
const customTheme = extendTheme(
|
||||||
|
{
|
||||||
|
styles: {
|
||||||
|
global: (props: any) => ({
|
||||||
|
body: {
|
||||||
|
bg: mode("white", "#171717")(props),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
green: {
|
||||||
|
50: "#d4f3df",
|
||||||
|
100: "#b7ecca",
|
||||||
|
200: "#9be4b4",
|
||||||
|
300: "#61d48a",
|
||||||
|
400: "#45cd74",
|
||||||
|
500: "#32ba62",
|
||||||
|
600: "#2b9e53",
|
||||||
|
700: "#238144",
|
||||||
|
800: "#1b6435",
|
||||||
|
900: "#134826",
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Link: {
|
||||||
|
baseStyle: {
|
||||||
|
color: "green",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
withDefaultColorScheme({ colorScheme: "green" })
|
||||||
|
);
|
||||||
|
|
||||||
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
useEffect(() => {
|
||||||
|
const handleRouteChange = (url: string) => {
|
||||||
|
gtag.pageview(url);
|
||||||
|
};
|
||||||
|
router.events.on("routeChangeComplete", handleRouteChange);
|
||||||
|
router.events.on("hashChangeComplete", handleRouteChange);
|
||||||
|
return () => {
|
||||||
|
router.events.off("routeChangeComplete", handleRouteChange);
|
||||||
|
router.events.off("hashChangeComplete", handleRouteChange);
|
||||||
|
};
|
||||||
|
}, [router.events]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Script
|
||||||
|
async
|
||||||
|
onError={(e) => {
|
||||||
|
console.error("Script failed to load", e);
|
||||||
|
}}
|
||||||
|
src={`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${process.env.NEXT_PUBLIC_ADSENSE_ID}`}
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
/>
|
||||||
|
{/* Global Site Tag (gtag.js) - Google Analytics */}
|
||||||
|
<Script
|
||||||
|
strategy="afterInteractive"
|
||||||
|
src={`https://www.googletagmanager.com/gtag/js?id=${gtag.GA_TRACKING_ID}`}
|
||||||
|
/>
|
||||||
|
<Script
|
||||||
|
id="gtag-init"
|
||||||
|
strategy="afterInteractive"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', '${gtag.GA_TRACKING_ID}', {
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ChakraProvider theme={customTheme}>
|
||||||
|
<Head>
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="180x180"
|
||||||
|
href="/apple-touch-icon.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="/favicon-32x32.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href="/favicon-16x16.png"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
<title>Spotube</title>
|
||||||
|
</Head>
|
||||||
|
<AdDetector>
|
||||||
|
<Navbar />
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</AdDetector>
|
||||||
|
</ChakraProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyApp;
|
13
website/pages/api/hello.ts
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
type Data = {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<Data>
|
||||||
|
) {
|
||||||
|
res.status(200).json({ name: 'John Doe' })
|
||||||
|
}
|
106
website/pages/index.tsx
Executable file
@ -0,0 +1,106 @@
|
|||||||
|
import {
|
||||||
|
Heading,
|
||||||
|
Menu,
|
||||||
|
ButtonGroup,
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
MenuItem,
|
||||||
|
VStack,
|
||||||
|
MenuButton,
|
||||||
|
Link as Anchor,
|
||||||
|
chakra,
|
||||||
|
MenuList,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import { FaCaretDown } from "react-icons/fa";
|
||||||
|
import { DisplayAd } from "../components/special";
|
||||||
|
import { Platform, usePlatform } from "../hooks/usePlatform";
|
||||||
|
|
||||||
|
const baseURL = "https://github.com/KRTirtho/spotube/releases/latest/download/";
|
||||||
|
|
||||||
|
const DownloadLinks = Object.freeze({
|
||||||
|
[Platform.linux]: [
|
||||||
|
{ name: "deb", url: baseURL + "Spotube-linux-x86_64.deb" },
|
||||||
|
{ name: "tar", url: baseURL + "Spotube-linux-x86_64.tar.xz" },
|
||||||
|
{ name: "AppImage", url: baseURL + "Spotube-linux-x86_64.AppImage" },
|
||||||
|
],
|
||||||
|
[Platform.android]: [
|
||||||
|
{
|
||||||
|
name: "apk",
|
||||||
|
url: baseURL + "Spotube-android-all-arch.apk",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[Platform.mac]: [{ name: "dmg", url: baseURL + "Spotube-macos-x86_64.dmg" }],
|
||||||
|
[Platform.windows]: [
|
||||||
|
{ name: "exe", url: baseURL + "Spotube-windows-x86_64-setup.exe" },
|
||||||
|
{ name: "nupkg", url: baseURL + "Spotube-windows-x86_64.nupkg " },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const Root = () => {
|
||||||
|
const platform = usePlatform();
|
||||||
|
|
||||||
|
const allPlatforms = Object.entries(Platform)
|
||||||
|
.map(([, value]) => {
|
||||||
|
return DownloadLinks[value].map((s) => ({
|
||||||
|
...s,
|
||||||
|
name: `${value} (.${s.name})`,
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.flat(1);
|
||||||
|
|
||||||
|
const currentPlatform = DownloadLinks[platform][0];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<VStack spacing="$4" alignItems="stretch">
|
||||||
|
<chakra.section
|
||||||
|
h="60vh"
|
||||||
|
bgColor="#f5f5f5"
|
||||||
|
bgImage="url(/spotube-screenshot-web.jpg)"
|
||||||
|
bgRepeat="no-repeat"
|
||||||
|
bgSize="contain"
|
||||||
|
bgPos="right"
|
||||||
|
>
|
||||||
|
<VStack mt="10" mx="6" spacing="4" alignItems="flex-start">
|
||||||
|
<Heading color="#212121" size="2xl">
|
||||||
|
Spotube
|
||||||
|
</Heading>
|
||||||
|
<Heading color="#212121" size="lg" textAlign="justify" maxW="500px">
|
||||||
|
A fast, modern, lightweight & efficient Spotify Music Client for
|
||||||
|
every platform
|
||||||
|
</Heading>
|
||||||
|
<Menu placement="bottom-end">
|
||||||
|
<ButtonGroup spacing="0.5">
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
as={Anchor}
|
||||||
|
href={currentPlatform.url}
|
||||||
|
_hover={{ textDecoration: "none" }}
|
||||||
|
>
|
||||||
|
Download for {platform} (.{currentPlatform.name})
|
||||||
|
</Button>
|
||||||
|
<MenuButton
|
||||||
|
aria-label="Show More Downloads"
|
||||||
|
as={IconButton}
|
||||||
|
variant="solid"
|
||||||
|
icon={<FaCaretDown />}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
<MenuList>
|
||||||
|
{allPlatforms.map(({ name, url }) => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={url} as={Anchor} href={url}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</VStack>
|
||||||
|
</chakra.section>
|
||||||
|
<DisplayAd slot="9501208974" />
|
||||||
|
</VStack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Root;
|
36
website/pages/other-downloads/index.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Link as Anchor, Heading, VStack, chakra } from "@chakra-ui/react";
|
||||||
|
import NavLink from "next/link";
|
||||||
|
import { GridMultiplexAd } from "components/special";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
function OtherDownloads() {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<VStack my="20" mx="5">
|
||||||
|
<Heading size="lg">Download other versions of Spotube</Heading>
|
||||||
|
<chakra.ul pl="5">
|
||||||
|
<li>
|
||||||
|
<NavLink href={router.pathname + "/stable-downloads"} passHref>
|
||||||
|
<Anchor color="blue.500">
|
||||||
|
Download previous versions of Spotube
|
||||||
|
</Anchor>
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<NavLink href={router.pathname + "/nightly-downloads"} passHref>
|
||||||
|
<Anchor color="blue.500">
|
||||||
|
Download Bleeding Edge Nightly version of Spotube
|
||||||
|
</Anchor>
|
||||||
|
</NavLink>
|
||||||
|
(Nightly releases)
|
||||||
|
</li>
|
||||||
|
</chakra.ul>
|
||||||
|
</VStack>
|
||||||
|
<GridMultiplexAd slot="4575915852" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OtherDownloads;
|
87
website/pages/other-downloads/nightly-downloads.tsx
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import {
|
||||||
|
Link as Anchor,
|
||||||
|
Button,
|
||||||
|
Heading,
|
||||||
|
Table,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
VStack,
|
||||||
|
chakra,
|
||||||
|
Text,
|
||||||
|
Tr,
|
||||||
|
HStack,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import { GridMultiplexAd } from "components/special";
|
||||||
|
import NavLink from "next/link";
|
||||||
|
|
||||||
|
const baseURL =
|
||||||
|
"https://nightly.link/KRTirtho/spotube/workflows/spotube-nightly/build/";
|
||||||
|
|
||||||
|
const DownloadLinks = Object.freeze({
|
||||||
|
Linux: baseURL + "Spotube-Linux-Bundle.zip",
|
||||||
|
Android: baseURL + "Spotube-Android-Bundle.zip",
|
||||||
|
Windows: baseURL + "Spotube-Windows-Bundle.zip",
|
||||||
|
MacOS: baseURL + "Spotube-Macos-Bundle.zip",
|
||||||
|
});
|
||||||
|
|
||||||
|
function NightlyDownloads() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<VStack>
|
||||||
|
<VStack
|
||||||
|
alignSelf="center"
|
||||||
|
alignItems="flex-start"
|
||||||
|
spacing="4"
|
||||||
|
maxW="500px"
|
||||||
|
overflow="auto"
|
||||||
|
m="5"
|
||||||
|
>
|
||||||
|
<Heading size="2xl">Nightly Release</Heading>
|
||||||
|
<Text>Download latest & most bleeding edge version of Spotube</Text>
|
||||||
|
<Text size="sm" color="red.500" textAlign="justify">
|
||||||
|
Disclaimer!: Nightly versions are untested and not the final version
|
||||||
|
of spotube. So it can consists of many hidden or unknown bugs but at
|
||||||
|
the same time will be opted with latest & greatest features too. So
|
||||||
|
use it at your own risk! If you don't know what you're
|
||||||
|
doing than we recommend you to download the{" "}
|
||||||
|
<NavLink href="/" passHref>
|
||||||
|
<Anchor color="blue.500">latest stable version of</Anchor>
|
||||||
|
</NavLink>{" "}
|
||||||
|
Spotube
|
||||||
|
</Text>
|
||||||
|
<chakra.section
|
||||||
|
border="2px solid"
|
||||||
|
borderColor="gray"
|
||||||
|
borderRadius="md"
|
||||||
|
px="4"
|
||||||
|
py="2"
|
||||||
|
w="100%"
|
||||||
|
>
|
||||||
|
{Object.entries(DownloadLinks).map(([platform, url]) => {
|
||||||
|
const segments = url.split("/");
|
||||||
|
return (
|
||||||
|
<HStack key={url}>
|
||||||
|
<Text w="100px">{platform}</Text>
|
||||||
|
<Anchor
|
||||||
|
overflowWrap="break-word"
|
||||||
|
wordBreak="break-word"
|
||||||
|
w="full"
|
||||||
|
href={url}
|
||||||
|
color="blue.500"
|
||||||
|
>
|
||||||
|
{segments.at(segments.length - 1)?.replace("build", "")}
|
||||||
|
</Anchor>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</chakra.section>
|
||||||
|
</VStack>
|
||||||
|
<chakra.div w="full">
|
||||||
|
<GridMultiplexAd slot="3192619797" />
|
||||||
|
</chakra.div>
|
||||||
|
</VStack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NightlyDownloads;
|
@ -4,20 +4,20 @@ import {
|
|||||||
AccordionIcon,
|
AccordionIcon,
|
||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionPanel,
|
AccordionPanel,
|
||||||
Anchor,
|
Link as Anchor,
|
||||||
Heading,
|
Heading,
|
||||||
HStack,
|
HStack,
|
||||||
Skeleton,
|
|
||||||
Text,
|
Text,
|
||||||
VStack,
|
VStack,
|
||||||
} from "@hope-ui/solid";
|
chakra,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
import { Octokit, RestEndpointMethodTypes } from "@octokit/rest";
|
import { Octokit, RestEndpointMethodTypes } from "@octokit/rest";
|
||||||
import { createCachedResource } from "solid-cached-resource";
|
import ReactMarkdown from "react-markdown";
|
||||||
import SolidMarkdown from "solid-markdown";
|
import { Platform } from "hooks/usePlatform";
|
||||||
import { Platform } from "../hooks/usePlatform";
|
|
||||||
import gfm from "remark-gfm";
|
import gfm from "remark-gfm";
|
||||||
import { MarkdownComponentDefs } from "../misc/MarkdownComponentDefs";
|
import { DisplayAd, InFeedAd } from "components/special";
|
||||||
import { DisplayAd } from "../components/special";
|
import { GetServerSideProps, NextPage } from "next";
|
||||||
|
import { MarkdownComponentDefs } from "misc/MarkdownComponentDefs";
|
||||||
|
|
||||||
enum AssetTypes {
|
enum AssetTypes {
|
||||||
sums = "sums",
|
sums = "sums",
|
||||||
@ -27,30 +27,73 @@ enum AssetTypes {
|
|||||||
android = "android",
|
android = "android",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const octokit = new Octokit();
|
export const octokit: Octokit = new Octokit();
|
||||||
function StableDownloads() {
|
|
||||||
const [data] = createCachedResource("gh-releases", () => {
|
type ReleaseResponse = {
|
||||||
return octokit.repos.listReleases({
|
id: number;
|
||||||
|
body: string | null | undefined;
|
||||||
|
tag_name: string;
|
||||||
|
assets: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
browser_download_url: string;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
data: ReleaseResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getServerSideProps: GetServerSideProps<Props> = async ({
|
||||||
|
res,
|
||||||
|
}) => {
|
||||||
|
res.setHeader(
|
||||||
|
"Cache-Control",
|
||||||
|
"public, s-maxage=10, stale-while-revalidate=59"
|
||||||
|
);
|
||||||
|
const { data } = await octokit.repos.listReleases({
|
||||||
owner: "KRTirtho",
|
owner: "KRTirtho",
|
||||||
repo: "spotube",
|
repo: "spotube",
|
||||||
});
|
});
|
||||||
|
const releaseResponse: ReleaseResponse = data.map((data) => {
|
||||||
|
return {
|
||||||
|
tag_name: data.tag_name,
|
||||||
|
id: data.id,
|
||||||
|
body: data.body,
|
||||||
|
assets: data.assets.map((asset) => ({
|
||||||
|
id: asset.id,
|
||||||
|
name: asset.name,
|
||||||
|
browser_download_url: asset.browser_download_url,
|
||||||
|
})),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
data: releaseResponse,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const StableDownloads: NextPage<Props> = ({ data }) => {
|
||||||
return (
|
return (
|
||||||
<VStack alignItems="stretch" m="$3">
|
<VStack alignItems="stretch" m="3">
|
||||||
<Heading size="3xl">Previous Versions</Heading>
|
<Heading size="xl">Previous Versions</Heading>
|
||||||
<Text my="$5">
|
<Text my="5">
|
||||||
If any of your version is not working correctly than you can download &
|
If any of your version is not working correctly than you can download &
|
||||||
use previous versions of Spotube too
|
use previous versions of Spotube too
|
||||||
</Text>
|
</Text>
|
||||||
<HStack alignItems="flex-start">
|
<HStack alignItems="flex-start" wrap="wrap">
|
||||||
<VStack alignItems="stretch" spacing="$3" mr="$1">
|
<VStack
|
||||||
{data.loading &&
|
alignItems="stretch"
|
||||||
!data.latest && [
|
w={{
|
||||||
<Skeleton h="$6" w="$56" />,
|
base: "full",
|
||||||
...Array.from({ length: 8 }, () => <Skeleton h="$6" w="$96" />),
|
sm: "70%",
|
||||||
]}
|
md: "60%",
|
||||||
{(data.latest ?? data())?.data.map((release, i) => {
|
}}
|
||||||
|
spacing="3"
|
||||||
|
mr="1"
|
||||||
|
>
|
||||||
|
{data.map((release, i) => {
|
||||||
const releaseSome = release.assets
|
const releaseSome = release.assets
|
||||||
.map((asset) => {
|
.map((asset) => {
|
||||||
const platform =
|
const platform =
|
||||||
@ -70,46 +113,53 @@ function StableDownloads() {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<VStack
|
<VStack
|
||||||
py="$3"
|
key={release.id}
|
||||||
|
py="3"
|
||||||
alignItems="flex-start"
|
alignItems="flex-start"
|
||||||
borderBottom="1px solid grey"
|
borderBottom="1px solid grey"
|
||||||
_last={{ borderBottom: "none" }}
|
_last={{ borderBottom: "none" }}
|
||||||
>
|
>
|
||||||
<Heading size="xl">
|
<Heading size="md">
|
||||||
Version{" "}
|
Version{" "}
|
||||||
<Text as="span" color="$success8">
|
<Text as="span" color="green.500">
|
||||||
{release.tag_name}
|
{release.tag_name}
|
||||||
</Text>{" "}
|
</Text>{" "}
|
||||||
{i == 0 && "(Latest)"}
|
{i == 0 && "(Latest)"}
|
||||||
</Heading>
|
</Heading>
|
||||||
{Object.entries(releaseSome).map(([type, assets]) => {
|
{Object.entries(releaseSome).map(([type, assets], i) => {
|
||||||
return (
|
return (
|
||||||
<HStack py="$2" alignItems="flex-start">
|
<HStack key={i} spacing={0} py="2" alignItems="flex-start">
|
||||||
<Heading
|
<Heading
|
||||||
w={90}
|
w={90}
|
||||||
p="$2"
|
p="2"
|
||||||
color="$info12"
|
colorScheme="blue"
|
||||||
border={`2px solid #404040`}
|
border="2px solid"
|
||||||
|
borderColor="gray"
|
||||||
borderRadius="5px 0 0 5px"
|
borderRadius="5px 0 0 5px"
|
||||||
borderRight="none"
|
borderRight="none"
|
||||||
|
size="sm"
|
||||||
>
|
>
|
||||||
{type[0].toUpperCase() + type.slice(1)}
|
{type[0].toUpperCase() + type.slice(1)}
|
||||||
</Heading>
|
</Heading>
|
||||||
<VStack
|
<VStack
|
||||||
alignItems="flex-start"
|
alignItems="flex-start"
|
||||||
border={`2px solid #404040`}
|
border="2px solid"
|
||||||
|
borderColor="gray"
|
||||||
borderRadius={`0 5px 5px ${
|
borderRadius={`0 5px 5px ${
|
||||||
assets.length !== 1 ? 5 : 0
|
assets.length !== 1 ? 5 : 0
|
||||||
}px`}
|
}px`}
|
||||||
w="$72"
|
w="72"
|
||||||
>
|
>
|
||||||
{assets.map((asset) => {
|
{assets.map((asset) => {
|
||||||
return (
|
return (
|
||||||
<Anchor
|
<Anchor
|
||||||
color="$info11"
|
key={asset.id}
|
||||||
width="$full"
|
color="blue.500"
|
||||||
p="$2"
|
width="full"
|
||||||
href={asset.name}
|
p="1.5"
|
||||||
|
href={asset.browser_download_url}
|
||||||
|
target="_blank"
|
||||||
|
referrerPolicy="no-referrer"
|
||||||
>
|
>
|
||||||
{asset.name}
|
{asset.name}
|
||||||
</Anchor>
|
</Anchor>
|
||||||
@ -119,18 +169,18 @@ function StableDownloads() {
|
|||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<Accordion defaultIndex={i}>
|
<Accordion defaultIndex={i} allowToggle>
|
||||||
<AccordionItem>
|
<AccordionItem>
|
||||||
<AccordionButton>
|
<AccordionButton>
|
||||||
Release Notes <AccordionIcon />
|
Release Notes <AccordionIcon />
|
||||||
</AccordionButton>
|
</AccordionButton>
|
||||||
<AccordionPanel>
|
<AccordionPanel>
|
||||||
<SolidMarkdown
|
<ReactMarkdown
|
||||||
components={MarkdownComponentDefs}
|
components={MarkdownComponentDefs}
|
||||||
remarkPlugins={[gfm]}
|
remarkPlugins={[gfm]}
|
||||||
>
|
>
|
||||||
{release.body ?? ""}
|
{release.body ?? ""}
|
||||||
</SolidMarkdown>
|
</ReactMarkdown>
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@ -138,15 +188,22 @@ function StableDownloads() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</VStack>
|
</VStack>
|
||||||
<VStack id="Ad">
|
<chakra.div
|
||||||
|
id="Ad"
|
||||||
|
w={{
|
||||||
|
base: "full",
|
||||||
|
sm: "25%",
|
||||||
|
md: "35%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DisplayAd slot="1391349310" />
|
<DisplayAd slot="1391349310" />
|
||||||
<DisplayAd slot="6452104301" />
|
<DisplayAd slot="6452104301" />
|
||||||
<DisplayAd slot="1199777626" />
|
<DisplayAd slot="1199777626" />
|
||||||
<DisplayAd slot="2001723409" />
|
<DisplayAd slot="2001723409" />
|
||||||
</VStack>
|
</chakra.div>
|
||||||
</HStack>
|
</HStack>
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default StableDownloads;
|
export default StableDownloads;
|
@ -1,2 +0,0 @@
|
|||||||
/all-versions /index.html 200
|
|
||||||
/about /index.html 200
|
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 892 B After Width: | Height: | Size: 892 B |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 323 KiB After Width: | Height: | Size: 323 KiB |
@ -1,146 +0,0 @@
|
|||||||
import {
|
|
||||||
Button,
|
|
||||||
createDisclosure,
|
|
||||||
Heading,
|
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalContent,
|
|
||||||
ModalFooter,
|
|
||||||
ModalHeader,
|
|
||||||
ModalOverlay,
|
|
||||||
Stack,
|
|
||||||
Text,
|
|
||||||
VStack,
|
|
||||||
} from "@hope-ui/solid";
|
|
||||||
import {
|
|
||||||
Component,
|
|
||||||
createEffect,
|
|
||||||
createResource,
|
|
||||||
createSignal,
|
|
||||||
} from "solid-js";
|
|
||||||
import Navbar from "./components/Navbar";
|
|
||||||
import { Route, Router, Routes } from "solid-app-router";
|
|
||||||
import { Root } from "./pages/Root";
|
|
||||||
import OtherDownloads from "./pages/OtherDownloads";
|
|
||||||
import NightlyDownloads from "./pages/NightlyDownloads";
|
|
||||||
import StableDownloads from "./pages/StableDownloads";
|
|
||||||
|
|
||||||
const App: Component = () => {
|
|
||||||
const [adBlockEnabled, setAdBlockEnabled] = createSignal(false);
|
|
||||||
const { isOpen, onOpen, onClose } = createDisclosure();
|
|
||||||
const [joke, setJoke] = createSignal<Record<string, any>>({});
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
const googleAdUrl =
|
|
||||||
"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js";
|
|
||||||
try {
|
|
||||||
await fetch(new Request(googleAdUrl)).catch((_) =>
|
|
||||||
setAdBlockEnabled(true)
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
setAdBlockEnabled(true);
|
|
||||||
} finally {
|
|
||||||
if (adBlockEnabled()) {
|
|
||||||
setJoke(
|
|
||||||
await (
|
|
||||||
await fetch(
|
|
||||||
"https://v2.jokeapi.dev/joke/Any?blacklistFlags=racist,sexist"
|
|
||||||
)
|
|
||||||
).json()
|
|
||||||
);
|
|
||||||
onOpen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Router>
|
|
||||||
<Modal opened={isOpen()} onClose={onClose}>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent mt="$5" mx="$3">
|
|
||||||
<ModalHeader>Support the Creator💚</ModalHeader>
|
|
||||||
<ModalBody>
|
|
||||||
<p>
|
|
||||||
Open source developers work really hard to provide the best,
|
|
||||||
secure & efficient software experience for you & people all around
|
|
||||||
the world. Most of the time we work without any wages at all but
|
|
||||||
we need minimum support to live & these <b> Ads Helps Us</b> earn
|
|
||||||
the minimum wage that we need to live.{" "}
|
|
||||||
<Text color="$success10" fontWeight="bold" textAlign="justify">
|
|
||||||
So, please support Spotube by disabling the AdBlocker on this
|
|
||||||
page or by sponsoring or donating to our collectives directly
|
|
||||||
</Text>
|
|
||||||
</p>
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onClick={() => window.location.reload()}>
|
|
||||||
Reload without AdBlocker
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
{!adBlockEnabled() ? (
|
|
||||||
<VStack alignItems="stretch">
|
|
||||||
<Navbar />
|
|
||||||
<Routes>
|
|
||||||
<Route path="/" component={Root} />
|
|
||||||
<Route path="/other-downloads" component={OtherDownloads} />
|
|
||||||
<Route path="/stable-downloads" component={StableDownloads} />
|
|
||||||
<Route path="/nightly-downloads" component={NightlyDownloads} />
|
|
||||||
</Routes>
|
|
||||||
</VStack>
|
|
||||||
) : (
|
|
||||||
<Stack
|
|
||||||
direction="column"
|
|
||||||
w="100vw"
|
|
||||||
h="100vh"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
p="$5"
|
|
||||||
>
|
|
||||||
<Heading></Heading>
|
|
||||||
<VStack spacing="$2" alignItems="flex-start">
|
|
||||||
<Heading>Here's something interesting:</Heading>
|
|
||||||
<Heading size="xl">
|
|
||||||
{joke().joke ?? (
|
|
||||||
<>
|
|
||||||
<p>{joke().setup}</p>
|
|
||||||
<p>{joke().delivery}</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Heading>
|
|
||||||
</VStack>
|
|
||||||
<VStack justifySelf="flex-end">
|
|
||||||
<Heading
|
|
||||||
mt="$10"
|
|
||||||
size={{
|
|
||||||
"@lg": "4xl",
|
|
||||||
"@initial": "2xl",
|
|
||||||
}}
|
|
||||||
maxW="700px"
|
|
||||||
textAlign="justify"
|
|
||||||
lineHeight="1.5"
|
|
||||||
>
|
|
||||||
Be grateful for all the favors you get. But don't let it become a
|
|
||||||
pile of debt. Try returning them as soon as you can. You'll feel
|
|
||||||
relieved
|
|
||||||
</Heading>
|
|
||||||
<Heading
|
|
||||||
size={{
|
|
||||||
"@lg": "2xl",
|
|
||||||
"@initial": "lg",
|
|
||||||
}}
|
|
||||||
alignSelf="flex-end"
|
|
||||||
>
|
|
||||||
- Kingkor Roy Tirtho
|
|
||||||
</Heading>
|
|
||||||
</VStack>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
Before Width: | Height: | Size: 15 KiB |
@ -1,47 +0,0 @@
|
|||||||
import {
|
|
||||||
Button,
|
|
||||||
ButtonGroup,
|
|
||||||
Heading,
|
|
||||||
HStack,
|
|
||||||
IconButton,
|
|
||||||
useColorMode,
|
|
||||||
} from "@hope-ui/solid";
|
|
||||||
import { NavLink } from "solid-app-router";
|
|
||||||
import { FaLightbulb } from "solid-icons/fa";
|
|
||||||
import { FiSun } from "solid-icons/fi";
|
|
||||||
|
|
||||||
const Navbar = () => {
|
|
||||||
const { colorMode, toggleColorMode } = useColorMode();
|
|
||||||
return (
|
|
||||||
<HStack py="$2" justifyContent="space-between" as="nav" w="$full">
|
|
||||||
<section>
|
|
||||||
<Heading p="$2" size="3xl" mr="$2" as={NavLink} href="/">
|
|
||||||
Spotube
|
|
||||||
</Heading>
|
|
||||||
<ButtonGroup>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
as={NavLink}
|
|
||||||
href="/other-downloads"
|
|
||||||
>
|
|
||||||
Other Downloads
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="sm" as={NavLink} href="/about">
|
|
||||||
About
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</section>
|
|
||||||
<IconButton
|
|
||||||
variant="ghost"
|
|
||||||
icon={colorMode() == "dark" ? <FaLightbulb /> : <FiSun />}
|
|
||||||
aria-label="Dark Mode Toggle"
|
|
||||||
onClick={() => {
|
|
||||||
toggleColorMode();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Navbar;
|
|
@ -1,40 +0,0 @@
|
|||||||
import { Component, createEffect } from "solid-js";
|
|
||||||
|
|
||||||
type AdComponent = Component<{
|
|
||||||
slot: string;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export const DisplayAd: AdComponent = ({ slot }) => {
|
|
||||||
createEffect(() => {
|
|
||||||
//@ts-ignore
|
|
||||||
(window.adsbygoogle = window.adsbygoogle || []).push({});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ins
|
|
||||||
class="adsbygoogle"
|
|
||||||
style={{ display: "block" }}
|
|
||||||
data-ad-client="ca-pub-6419300932495863"
|
|
||||||
data-ad-slot={slot}
|
|
||||||
data-ad-format="auto"
|
|
||||||
data-full-width-responsive="true"
|
|
||||||
></ins>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GridMultiplexAd: AdComponent = ({ slot }) => {
|
|
||||||
createEffect(() => {
|
|
||||||
//@ts-ignore
|
|
||||||
(window.adsbygoogle = window.adsbygoogle || []).push({});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ins
|
|
||||||
class="adsbygoogle"
|
|
||||||
style={{ display: "block" }}
|
|
||||||
data-ad-format="autorelaxed"
|
|
||||||
data-ad-client="ca-pub-6419300932495863"
|
|
||||||
data-ad-slot={slot}
|
|
||||||
></ins>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,19 +0,0 @@
|
|||||||
export enum Platform {
|
|
||||||
linux = "Linux",
|
|
||||||
windows = "Windows",
|
|
||||||
mac = "Mac",
|
|
||||||
android = "Android",
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePlatform(): Platform {
|
|
||||||
const platform = (
|
|
||||||
((navigator as unknown as any).userAgentData?.platform as
|
|
||||||
| string
|
|
||||||
| undefined) ?? navigator.platform
|
|
||||||
).toLowerCase();
|
|
||||||
|
|
||||||
if (platform.includes("windows")) return Platform.windows;
|
|
||||||
else if (platform.includes("mac")) return Platform.mac;
|
|
||||||
else if (platform.includes("android")) return Platform.android;
|
|
||||||
else return Platform.linux;
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
||||||
monospace;
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
/* @refresh reload */
|
|
||||||
import { render } from "solid-js/web";
|
|
||||||
|
|
||||||
import "./index.css";
|
|
||||||
import App from "./App";
|
|
||||||
import { HopeProvider } from "@hope-ui/solid";
|
|
||||||
|
|
||||||
render(
|
|
||||||
() => (
|
|
||||||
<HopeProvider
|
|
||||||
config={{
|
|
||||||
lightTheme: {
|
|
||||||
colors: {
|
|
||||||
primary1: "#d4f3df",
|
|
||||||
primary2: "#d4f3df",
|
|
||||||
primary3: "#b7ecca",
|
|
||||||
primary4: "#9be4b4",
|
|
||||||
primary5: "#7edc9f",
|
|
||||||
primary6: "#61d48a",
|
|
||||||
primary7: "#45cd74",
|
|
||||||
primary8: "#32ba62",
|
|
||||||
primary9: "#2b9e53",
|
|
||||||
primary10: "#238144",
|
|
||||||
primary11: "#1b6435",
|
|
||||||
primary12: "#134826",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
initialColorMode: "system",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<App />
|
|
||||||
</HopeProvider>
|
|
||||||
),
|
|
||||||
document.getElementById("root") as HTMLElement
|
|
||||||
);
|
|
@ -1,13 +0,0 @@
|
|||||||
import { Anchor, Heading, Text, hope } from "@hope-ui/solid";
|
|
||||||
|
|
||||||
export const MarkdownComponentDefs = {
|
|
||||||
a: (props: any) => <Anchor {...props} color="$info10" />,
|
|
||||||
h1: (props: any) => <Heading {...props} size="4xl" mt="$5" mb="$1_5" />,
|
|
||||||
h2: (props: any) => <Heading {...props} size="3xl" mt="$5" mb="$1_5" />,
|
|
||||||
h3: (props: any) => <Heading {...props} size="2xl" mt="$5" mb="$1_5" />,
|
|
||||||
h4: (props: any) => <Heading {...props} size="md" />,
|
|
||||||
h5: (props: any) => <Heading {...props} size="lg" />,
|
|
||||||
h6: (props: any) => <Heading {...props} size="md" />,
|
|
||||||
p: (props: any) => <Text {...props} />,
|
|
||||||
li: (props: any) => <hope.li {...props} ml="$4" />,
|
|
||||||
};
|
|
@ -1,75 +0,0 @@
|
|||||||
import {
|
|
||||||
Anchor,
|
|
||||||
Button,
|
|
||||||
Heading,
|
|
||||||
Table,
|
|
||||||
Td,
|
|
||||||
Th,
|
|
||||||
Tr,
|
|
||||||
VStack,
|
|
||||||
hope,
|
|
||||||
Text,
|
|
||||||
} from "@hope-ui/solid";
|
|
||||||
import { NavLink } from "solid-app-router";
|
|
||||||
import { GridMultiplexAd } from "../components/special";
|
|
||||||
|
|
||||||
const baseURL =
|
|
||||||
"https://nightly.link/KRTirtho/spotube/workflows/spotube-nightly/build";
|
|
||||||
|
|
||||||
const DownloadLinks = Object.freeze({
|
|
||||||
Linux: baseURL + "Spotube-Linux-Bundle.zip",
|
|
||||||
Android: baseURL + "Spotube-Android-Bundle.zip",
|
|
||||||
Windows: baseURL + "Spotube-Windows-Bundle.zip",
|
|
||||||
MacOS: baseURL + "Spotube-Macos-Bundle.zip",
|
|
||||||
});
|
|
||||||
|
|
||||||
function NightlyDownloads() {
|
|
||||||
return (
|
|
||||||
<VStack
|
|
||||||
mx="$3"
|
|
||||||
alignSelf="center"
|
|
||||||
alignItems="flex-start"
|
|
||||||
spacing="$4"
|
|
||||||
maxW="500px"
|
|
||||||
overflow="auto"
|
|
||||||
>
|
|
||||||
<Heading size="2xl">Nightly Release</Heading>
|
|
||||||
<Text>Download latest & most bleeding edge version of Spotube</Text>
|
|
||||||
<Text size="sm" color="$danger11" textAlign="justify">
|
|
||||||
Disclaimer!: Nightly versions are untested and not the final version of
|
|
||||||
spotube. So it can consists of many hidden or unknown bugs but at the
|
|
||||||
same time will be opted with latest & greatest features too. So use it
|
|
||||||
at your own risk! If you don't know what you're doing than we recommend
|
|
||||||
you to download the{" "}
|
|
||||||
<Anchor as={NavLink} color="$info10" href="/">
|
|
||||||
latest stable version of
|
|
||||||
</Anchor>{" "}
|
|
||||||
Spotube
|
|
||||||
</Text>
|
|
||||||
<hope.section
|
|
||||||
border="2px solid"
|
|
||||||
borderColor="$neutral10"
|
|
||||||
borderRadius="$md"
|
|
||||||
>
|
|
||||||
<Table overflow="auto">
|
|
||||||
{Object.entries(DownloadLinks).map(([platform, url]) => {
|
|
||||||
const segments = url.split("/");
|
|
||||||
return (
|
|
||||||
<Tr>
|
|
||||||
<Th>{platform}</Th>
|
|
||||||
<Td>
|
|
||||||
<Button as={Anchor} href={url} variant="ghost">
|
|
||||||
{segments.at(segments.length - 1)?.replace("build", "")}
|
|
||||||
</Button>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Table>
|
|
||||||
</hope.section>
|
|
||||||
<GridMultiplexAd slot="3192619797" />
|
|
||||||
</VStack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NightlyDownloads;
|
|
@ -1,27 +0,0 @@
|
|||||||
import { Anchor, Heading, VStack } from "@hope-ui/solid";
|
|
||||||
import { NavLink } from "solid-app-router";
|
|
||||||
import { GridMultiplexAd } from "../components/special";
|
|
||||||
|
|
||||||
function OtherDownloads() {
|
|
||||||
return (
|
|
||||||
<VStack alignItems="flex-start" ml="$10" mt="$20">
|
|
||||||
<Heading size="2xl">Download other versions of Spotube</Heading>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<Anchor color="$info10" as={NavLink} href="/stable-downloads">
|
|
||||||
Download previous versions of Spotube
|
|
||||||
</Anchor>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Anchor color="$info10" as={NavLink} href="/nightly-downloads">
|
|
||||||
Download Bleeding Edge Nightly version of Spotube
|
|
||||||
</Anchor>{" "}
|
|
||||||
(Nightly releases)
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<GridMultiplexAd slot="4575915852" />
|
|
||||||
</VStack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default OtherDownloads;
|
|
@ -1,101 +0,0 @@
|
|||||||
import {
|
|
||||||
Heading,
|
|
||||||
Menu,
|
|
||||||
ButtonGroup,
|
|
||||||
Button,
|
|
||||||
Anchor,
|
|
||||||
MenuTrigger,
|
|
||||||
IconButton,
|
|
||||||
MenuContent,
|
|
||||||
MenuItem,
|
|
||||||
VStack,
|
|
||||||
hope,
|
|
||||||
} from "@hope-ui/solid";
|
|
||||||
import { FaSolidCaretDown } from "solid-icons/fa";
|
|
||||||
import { DisplayAd } from "../components/special";
|
|
||||||
import { Platform, usePlatform } from "../hooks/usePlatform";
|
|
||||||
|
|
||||||
const baseURL = "https://github.com/KRTirtho/spotube/releases/latest/download/";
|
|
||||||
|
|
||||||
const DownloadLinks = Object.freeze({
|
|
||||||
[Platform.linux]: [
|
|
||||||
{ name: "deb", url: baseURL + "Spotube-linux-x86_64.deb" },
|
|
||||||
{ name: "tar", url: baseURL + "Spotube-linux-x86_64.tar.xz" },
|
|
||||||
{ name: "AppImage", url: baseURL + "Spotube-linux-x86_64.AppImage" },
|
|
||||||
],
|
|
||||||
[Platform.android]: [
|
|
||||||
{
|
|
||||||
name: "apk",
|
|
||||||
url: baseURL + "Spotube-android-all-arch.apk",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[Platform.mac]: [{ name: "dmg", url: baseURL + "Spotube-macos-x86_64.dmg" }],
|
|
||||||
[Platform.windows]: [
|
|
||||||
{ name: "exe", url: baseURL + "Spotube-windows-x86_64-setup.exe" },
|
|
||||||
{ name: "nupkg", url: baseURL + "Spotube-windows-x86_64.nupkg " },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Root = () => {
|
|
||||||
const platform = usePlatform();
|
|
||||||
|
|
||||||
const allPlatforms = Object.entries(Platform)
|
|
||||||
.map(([key, value]) => {
|
|
||||||
return DownloadLinks[value].map((s) => ({
|
|
||||||
...s,
|
|
||||||
name: `${value} (.${s.name})`,
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
.flat(1);
|
|
||||||
|
|
||||||
const currentPlatform = DownloadLinks[platform][0];
|
|
||||||
return (
|
|
||||||
<VStack spacing="$4" alignItems="stretch">
|
|
||||||
<hope.section
|
|
||||||
h="60vh"
|
|
||||||
backgroundColor="#f5f5f5"
|
|
||||||
style={{
|
|
||||||
"background-image": "url(/src/assets/spotube-screenshot-web.jpg)",
|
|
||||||
"background-repeat": "no-repeat",
|
|
||||||
"background-size": "contain",
|
|
||||||
"background-position": "right",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<VStack mt="$10" mx="$6" spacing="$4" alignItems="flex-start">
|
|
||||||
<Heading color="#212121" size="5xl">
|
|
||||||
Spotube
|
|
||||||
</Heading>
|
|
||||||
<Heading color="#212121" size="2xl" textAlign="justify" maxW="500px">
|
|
||||||
A fast, modern, lightweight & efficient Spotify Music Client for
|
|
||||||
every platform
|
|
||||||
</Heading>
|
|
||||||
<Menu placement="bottom-end">
|
|
||||||
<ButtonGroup spacing="$0_5">
|
|
||||||
<Button
|
|
||||||
variant="subtle"
|
|
||||||
as={Anchor}
|
|
||||||
href={currentPlatform.url}
|
|
||||||
_hover={{ textDecoration: "none" }}
|
|
||||||
>
|
|
||||||
Download for {platform} (.{currentPlatform.name})
|
|
||||||
</Button>
|
|
||||||
<MenuTrigger as={IconButton} variant="subtle">
|
|
||||||
<FaSolidCaretDown />
|
|
||||||
</MenuTrigger>
|
|
||||||
</ButtonGroup>
|
|
||||||
<MenuContent>
|
|
||||||
{allPlatforms.map(({ name, url }) => {
|
|
||||||
return (
|
|
||||||
<MenuItem as={Anchor} href={url}>
|
|
||||||
{name}
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</MenuContent>
|
|
||||||
</Menu>
|
|
||||||
</VStack>
|
|
||||||
</hope.section>
|
|
||||||
<DisplayAd slot="9501208974" />
|
|
||||||
</VStack>
|
|
||||||
);
|
|
||||||
};
|
|
16
website/styles/globals.css
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||||
|
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
28
website/tsconfig.json
Normal file → Executable file
@ -1,15 +1,23 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"target": "ESNext",
|
"forceConsistentCasingInFileNames": true,
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"jsxImportSource": "solid-js",
|
|
||||||
"types": ["vite/client"],
|
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"isolatedModules": true
|
"declaration": true,
|
||||||
}
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"baseUrl": "."
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import { defineConfig } from "vite";
|
|
||||||
import solidPlugin from "vite-plugin-solid";
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [solidPlugin()],
|
|
||||||
server: {
|
|
||||||
port: 3000,
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
"node-fetch": "isomorphic-fetch",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
target: "esnext",
|
|
||||||
},
|
|
||||||
});
|
|