Merge branch 'dev' into feat/docusaurus

This commit is contained in:
Ushie
2023-08-09 20:56:53 +03:00
37 changed files with 1174 additions and 20 deletions

View File

@@ -53,6 +53,7 @@ body {
--grey-nine: hsla(240, 6%, 7%, 0.3);
--grey-ten: hsl(230, 9.5%, 17.5%);
--grey-eleven: hsl(208, 10%, 40%);
--red-one: hsl(333, 84%, 62%);
--bezier-one: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--drop-shadow-one: 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12),
0px 2px 4px -1px rgba(0, 0, 0, 0.2);

View File

@@ -1,11 +1,21 @@
import * as settings from './settings';
// API Endpoints
import type { Patch, Repository, Metadata, Asset } from '$lib/types';
import type {
Patch,
Repository,
Metadata,
Asset,
TeamMember,
DonationPlatform,
CryptoWallet
} from '$lib/types';
export type ReposData = Repository[];
export type PatchesData = { patches: Patch[]; packages: string[] };
export type ReleaseData = { metadata: Metadata; assets: Asset[] };
export type TeamData = { members: TeamMember[] };
export type DonationData = { wallets: CryptoWallet[]; platforms: DonationPlatform[] };
async function get_json(endpoint: string) {
const url = `${settings.api_base_url()}/${endpoint}`;
@@ -42,6 +52,17 @@ async function patches(): Promise<PatchesData> {
return { patches: json.patches, packages };
}
async function team(): Promise<TeamData> {
const json = await get_json('v2/team/members');
return { members: json.members };
}
async function donate(): Promise<DonationData> {
const json = await get_json('v2/donations');
return { wallets: json.donations.wallets, platforms: json.donations.links };
}
export const staleTime = 5 * 60 * 1000;
export const queries = {
manager: {
@@ -58,5 +79,15 @@ export const queries = {
queryKey: ['repositories'],
queryFn: repositories,
staleTime
},
team: {
queryKey: ['team'],
queryFn: team,
staleTime
},
donate: {
queryKey: ['donate'],
queryFn: donate,
staleTime
}
};

View File

@@ -53,6 +53,7 @@
<li><a href="/download">Download</a></li>
<li><a href="/patches">Patches</a></li>
<li><a href="/contributors">Contributors</a></li>
<li><a href="/donate">Donate</a></li>
<li><a href="{new URL($page.url).origin}/docs/">Docs</a></li>
</FooterSection>
<FooterSection title="Repositories">
@@ -90,7 +91,7 @@
</div>
<div class="footer-bottom">
<div id="logo-name"><span>Re</span>Vanced</div>
<a href="https://liberapay.com/ReVanced/donate"><div>Donate</div></a>
<a href="/donate"><div>Donate</div></a>
<a href="mailto:contact@revanced.app"><div>Email</div></a>
</div>
</footer>

View File

@@ -7,13 +7,21 @@
const client = useQueryClient();
export let href: string;
export let queryKey: null | keyof typeof queries = null;
export let queryKey: null | keyof typeof queries | Array<keyof typeof queries> = null;
function prefetch() {
if (queryKey !== null) {
const query = queries[queryKey];
dev_log('Prefetching', query);
client.prefetchQuery(query as any);
if (Array.isArray(queryKey)) {
queryKey.forEach((key) => {
const query = queries[key];
dev_log('Prefetching', query);
client.prefetchQuery(query as any);
});
} else {
const query = queries[queryKey];
dev_log('Prefetching', query);
client.prefetchQuery(query as any);
}
}
}
</script>

View File

@@ -52,6 +52,8 @@
<svelte:window bind:scrollY={y} />
<nav class:scrolled={y > 10}>
<a class="menu-btn skiptab-btn" href="#skiptab">Skip navigation</a>
<button
class="menu-btn mobile-only"
on:click={() => (menuOpen = !menuOpen)}
@@ -73,6 +75,7 @@
<Navigation queryKey="manager" href="/download">Download</Navigation>
<Navigation queryKey="patches" href="/patches">Patches</Navigation>
<Navigation queryKey="repositories" href="/contributors">Contributors</Navigation>
<Navigation queryKey={['donate', 'team']} href="/donate">Donate</Navigation>
<Navigation href="{new URL($page.url).origin}/docs">Docs</Navigation>
</div>
</div>
@@ -206,8 +209,7 @@
}
.scrolled {
box-shadow: 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12),
0px 2px 4px -1px rgba(0, 0, 0, 0.2);
box-shadow: var(--drop-shadow-one);
}
.overlay {
@@ -323,4 +325,21 @@
.menu-btn.open .menu-btn__burger::after {
transform: rotate(-45deg) translate(10px, 10px);
}
.skiptab-btn {
position: fixed;
left: -300px;
border-radius: 100px;
text-decoration: none;
background-color: var(--accent-color);
z-index: 10;
color: var(--grey-four);
font-weight: 600;
font-size: 0.95rem;
padding: 16px 24px;
}
.skiptab-btn:focus {
left: 12px;
}
</style>

View File

@@ -39,7 +39,7 @@
color: var(--white);
font-weight: 600;
border: none;
border-radius: 12px;
border-radius: 100px;
display: flex;
justify-content: center;
align-items: center;

View File

@@ -122,7 +122,6 @@
border-radius: 26px;
background-color: var(--grey-six);
display: flex;
user-select: none;
gap: 5%;
white-space: normal;
display: flex;

View File

@@ -0,0 +1,61 @@
<script lang="ts">
import { slide, fade } from 'svelte/transition';
import { expoOut } from 'svelte/easing';
export let open = false;
export let closeIcon = false;
export let dismissTime = 3000;
let timeout: ReturnType<typeof setTimeout>;
$: if (open) {
clearTimeout(timeout);
timeout = setTimeout(() => (open = false), dismissTime);
}
</script>
{#if open}
<div
class="snackbar"
class:closeIcon
in:slide|local={{ duration: 400, easing: expoOut }}
out:fade|local={{ duration: 300, easing: expoOut }}
>
<div class="text">
<slot name="text" />
</div>
</div>
{/if}
<style lang="scss">
.snackbar {
display: flex;
box-sizing: border-box;
justify-content: space-between;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding-left: 1rem;
padding-right: 0.5rem;
height: 3rem;
gap: 1.5rem;
align-items: center;
border-radius: 0.25rem;
min-width: 12.5rem;
max-width: 35rem;
position: fixed;
margin-left: 2.25rem;
margin-right: 2.25rem;
z-index: 9999;
left: 0;
right: 0;
bottom: 2rem;
background-color: var(--white);
transition: all 0.4s var(--bezier-one);
box-shadow: var(--drop-shadow-one);
}
.text {
color: var(--grey-two);
font-weight: 500;
font-size: 14px;
}
</style>

View File

@@ -9,8 +9,8 @@
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="none"
viewBox="0 0 {viewBoxHeight} {viewBoxWidth}"
style:height={svgHeight+'px'}
style:width={svgWidth+'px'}
style:height={svgHeight + 'px'}
style:width={svgWidth + 'px'}
>
<slot />
</svg>

View File

@@ -47,3 +47,23 @@ export interface Metadata {
published_at: string;
body: string;
}
export interface TeamMember {
login: string;
avatar_url: string;
html_url: string;
bio?: string;
}
export interface CryptoWallet {
network: string;
currency_code: string;
address: string;
preferred: boolean;
}
export interface DonationPlatform {
name: string;
url: string;
preferred: boolean;
}

View File

@@ -105,10 +105,12 @@
</svelte:fragment>
</Dialogue>
{#if $show_loading_animation}
<Spinner />
{:else}
<slot />
{/if}
<div id="skiptab">
{#if $show_loading_animation}
<Spinner />
{:else}
<slot />
{/if}
</div>
<!-- <Footer> -->
</QueryClientProvider>

View File

@@ -46,13 +46,13 @@
<div class="wrapper">
<div class="text-container" in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<h2>Made possible by the community.</h2>
<h4>
<p>
Want to show up here? <span
><a href="https://github.com/revanced" target="_blank" rel="noreferrer"
>Become a contributor</a
></span
>
</h4>
</p>
</div>
<div class="repos">
<Query {query} let:data>
@@ -82,7 +82,7 @@
margin-bottom: 0.3rem;
}
h4 {
p {
text-align: center;
color: var(--grey-four);
}

View File

@@ -0,0 +1,323 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import { queries } from '$data/api';
import { createQuery } from '@tanstack/svelte-query';
import Meta from '$lib/components/Meta.svelte';
import Footer from '$layout/Footer/FooterHost.svelte';
import Button from '$lib/components/Button.svelte';
import Snackbar from '$lib/components/Snackbar.svelte';
import Query from '$lib/components/Query.svelte';
import Dialogue from '$lib/components/Dialogue.svelte';
import QRCode from './QRCode.svelte';
import DonateHeartAnimation from './DonateHeartAnimation.svelte';
import TeamMember from './TeamMember.svelte';
import { supportsWebP } from '$util/supportsWebP';
const teamQuery = createQuery(['team'], queries.team);
const donateQuery = createQuery(['donate'], queries.donate);
let qrCodeDialogue = false;
let cryptoDialogue = false;
let addressSnackbar = false;
let qrCodeValue = '';
let qrCodeDialogueName = '';
async function copyToClipboard(walletAddress: string) {
addressSnackbar = true;
qrCodeDialogue = false;
try {
await navigator.clipboard.writeText(walletAddress);
} catch (error) {
console.error('Failed to copy crypto wallet:', error);
}
}
</script>
<Meta title="Donate" />
<main class="wrapper" in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<section>
<div>
<h2>🎉 Support <span style="color: var(--accent-color);">ReVanced</span></h2>
<p>
ReVanced offers a variety of patches, including ad-blocking, custom themes, and innovative
features. All of which is completely open source and free of charge. Donating will allow
ReVanced maintain our servers and develop new features.
</p>
</div>
<div id="heart">
<DonateHeartAnimation
backgroundImageUrl="/revanced-logo-background.svg"
foregroundImageUrl="/icons/heart.svg"
alt="ReVanced Logo"
/>
</div>
</section>
<h3>Donate</h3>
<Query query={donateQuery} let:data>
<div class="donate-cards">
{#if data.platforms}
{#each data.platforms as platform}
<a class="donate-card" target="_blank" rel="noreferrer" href={platform.url}>
<!-- not using <img/> because we want the image height to always be 200px -->
<div
style="background-image: url('/donate/card-images/{platform.name}.{supportsWebP() ? 'webp' : 'png'}'), url('/donate/card-images/fallback.png');"
role="img"
aria-label="{platform.name} preview image"
>
</div>
<span>{platform.name}</span>
</a>
{/each}
{/if}
{#if data.wallets}
<button class="donate-card" on:click={() => (cryptoDialogue = !cryptoDialogue)}>
<div
style="background-image: url('/donate/card-images/Cryptocurrencies.{supportsWebP() ? 'webp' : 'png'}'), url('/donate/card-images/fallback.png');"
role="img"
aria-label="Cryptocurrencies preview image"
/>
<span>Cryptocurrencies</span>
</button>
{/if}
</div>
</Query>
<Query query={teamQuery} let:data>
<h3>Meet the team</h3>
{#if data.members.length > 0}
<section class="team">
<!-- randomize team members because equality -->
{#each data.members.sort(() => (Math.random() > 0.5 ? -1 : 1)) as member, i}
<TeamMember {member} {i} />
{/each}
</section>
{/if}
</Query>
</main>
<Dialogue bind:modalOpen={cryptoDialogue}>
<svelte:fragment slot="icon">
<img class="qr-code" src="/icons/coins.svg" alt="Cryptocurrencies" />
</svelte:fragment>
<svelte:fragment slot="title">Cryptocurrencies</svelte:fragment>
<svelte:fragment slot="description">
<hr style="margin: 1rem 0;" />
<div class="wallets">
<Query query={donateQuery} let:data>
{#each data.wallets as wallet}
<button
on:click={() => {
qrCodeValue = wallet.address;
qrCodeDialogueName = wallet.currency_code;
qrCodeDialogue = !qrCodeDialogue;
// when the user clicks a wallet the crypto wallet goes away
// because multi page dialogues aren't implemented yet oops
cryptoDialogue = false;
}}
>
<div class="wallet-name">
<img
src="/donate/crypto/{wallet.currency_code}.svg"
onerror="this.onerror=null; this.src='/donate/fallback.svg'"
alt={`${wallet.network} icon.'`}
/>
{`${wallet.network} (${wallet.currency_code})`}
</div>
<img id="arrow" src="/icons/expand_less.svg" alt="continue" />
</button>
{/each}
</Query>
</div>
</svelte:fragment>
<svelte:fragment slot="buttons">
<Button type="filled" on:click={() => (cryptoDialogue = false)}>Close</Button>
</svelte:fragment>
</Dialogue>
<Dialogue bind:modalOpen={qrCodeDialogue}>
<svelte:fragment slot="icon">
<img class="qr-code" src="/icons/wallet.svg" alt="QR Code" />
</svelte:fragment>
<svelte:fragment slot="title">{qrCodeDialogueName} Wallet</svelte:fragment>
<svelte:fragment slot="description">
<div class="qr-code-body">
{qrCodeValue}
<QRCode codeValue={qrCodeValue} />
</div>
</svelte:fragment>
<svelte:fragment slot="buttons">
<Button
type="text"
on:click={() => {
qrCodeDialogue = false;
cryptoDialogue = true;
}}>Back</Button
>
<Button type="filled" on:click={() => copyToClipboard(qrCodeValue)}>Copy Address</Button>
</svelte:fragment>
</Dialogue>
<Snackbar bind:open={addressSnackbar} closeIcon>
<svelte:fragment slot="text">Address copied to clipboard</svelte:fragment>
</Snackbar>
<Footer />
<style lang="scss">
main {
display: flex;
flex-direction: column;
margin-top: 7rem;
// support revanced and heart thingy
section {
display: flex;
justify-content: center;
align-items: center;
@media screen and (max-width: 768px) {
flex-direction: column-reverse;
}
}
}
h2 {
margin-bottom: 0.5rem;
color: var(--white);
}
h3 {
margin-bottom: 1.5rem;
}
p {
margin-bottom: 2rem;
width: 60%;
@media screen and (max-width: 1200px) {
width: 90%;
}
@media screen and (max-width: 768px) {
width: 100%;
}
}
// COPEEEE
@media screen and (max-width: 768px) {
#heart {
display: none;
}
}
.donate-cards {
display: flex;
gap: 1rem;
margin-bottom: 3rem;
@media screen and (max-width: 768px) {
flex-direction: column;
}
}
.donate-card {
text-decoration: none;
background-color: var(--grey-ten);
border-radius: 1.5rem;
width: 100%;
cursor: pointer;
text-align: left;
border: none;
overflow: hidden;
transition: 0.3s border-radius var(--bezier-one), 0.3s background-color var(--bezier-one);
&:hover {
background-color: var(--accent-low-opacity);
}
&:active {
border-radius: 2.75rem;
}
span {
display: block;
color: var(--grey-five);
font-size: 1.05rem;
font-weight: 500;
padding: 1.5rem;
}
div {
height: 200px;
background-size: cover;
background-position: center;
max-width: 100%;
}
}
.wallets {
// i just guessed this
width: clamp(200px, 75vw, 375px);
#arrow {
height: 20px;
transform: rotate(90deg);
}
button {
width: 100%;
font-size: 0.9rem;
background-color: transparent;
border: none;
color: var(--grey-five);
cursor: pointer;
text-align: left;
display: flex;
justify-content: space-between;
background-color: var(--grey-six);
padding: 0.75rem 1.25rem;
transition: filter 0.4s var(--bezier-one);
&:hover {
filter: brightness(85%);
}
}
}
.wallet-name {
display: flex;
align-items: center;
gap: 0.5rem;
// crypto icon
img {
height: 24px;
width: 24px;
}
}
.qr-code-body {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
word-break: break-word;
text-align: center;
}
.team {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(325px, 1fr));
justify-content: space-between;
align-items: stretch;
gap: 1rem;
margin-bottom: 4rem;
}
</style>

View File

@@ -0,0 +1,124 @@
<script lang="ts">
export let backgroundImageUrl: string;
export let foregroundImageUrl: string;
export let alt: string;
</script>
<div id="pulsating-image-scale">
<div id="pulsating-image-hover">
<div id="background" style:background-image="url({backgroundImageUrl})">
<img src={foregroundImageUrl} {alt} />
</div>
</div>
</div>
<style lang="scss">
#pulsating-image-scale {
user-select: none;
cursor: pointer;
transition: transform 0.4s ease, filter 0.2s ease;
&:hover {
transform: scale(1.05);
}
&:active {
filter: brightness(1.3);
transform: scaleY(0.95) rotate(-5deg);
}
#pulsating-image-hover {
height: 225px;
width: 225px;
transition: all 0.2s ease;
&:hover {
animation: wiggle 1s;
@keyframes wiggle {
0% {
transform: rotate(0);
}
25% {
transform: rotate(5deg);
}
50% {
transform: rotate(-5deg);
}
75% {
transform: rotate(5deg);
}
100% {
transform: rotate(0deg);
}
}
}
#background {
pointer-events: none;
display: flex;
background-repeat: no-repeat;
justify-content: center;
align-items: center;
animation: pulse-size 1.2s infinite;
will-change: transform;
height: 100%;
width: 100%;
img {
height: 50%;
animation: double-pulse-size 1.2s infinite, pulse-glow 1.2s infinite;
will-change: transform, box-shadow;
@keyframes double-pulse-size {
0% {
transform: scale(0.7);
}
13% {
transform: scale(0.8);
}
30% {
transform: scale(0.7);
}
45% {
transform: scale(0.8);
}
100% {
transform: scale(0.7);
}
}
@keyframes pulse-glow {
30% {
filter: drop-shadow(0 0 0rem var(--red-one));
}
45% {
filter: drop-shadow(0 0 0.5rem var(--red-one));
}
}
}
@keyframes pulse-size {
0% {
transform: scale(0.7);
}
30% {
filter: brightness(1);
}
39% {
filter: brightness(1.2);
}
32% {
transform: scale(0.68);
}
50% {
transform: scale(0.78);
}
100% {
transform: scale(0.7);
}
}
}
}
}
</style>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import { onMount } from 'svelte';
import QRious from 'qrious/dist/qrious';
export let codeValue: string;
export let squareSize: number = 150;
onMount(() => {
new QRious({
element: document.getElementById('qrcode'),
value: codeValue,
size: squareSize
});
});
</script>
<canvas id="qrcode"/>
<style>
canvas {
border-radius: 0.5rem;
background-color: white;
padding: 0.25rem;
}
</style>

View File

@@ -0,0 +1,71 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import type { TeamMember } from '$lib/types';
export let member: TeamMember;
export let i: number;
</script>
<a
class="member"
href={member.html_url}
rel="noreferrer"
target="_blank"
in:fly={{ y: 10, easing: quintOut, duration: 750, delay: 50 * i }}
>
<img src={member.avatar_url} alt={`${member.login}'s profile picture.'`} />
<div class="member-text">
<h4>{member.login}</h4>
{#if member.bio}
<h6>{member.bio}</h6>
{/if}
</div>
</a>
<style lang="scss">
.member {
width: 100%;
color: var(--white);
border: 1px solid var(--grey-three);
text-decoration: none;
cursor: pointer;
padding: 1rem;
border-radius: 12px;
display: flex;
gap: 1rem;
transition: 0.3s background-color var(--bezier-one);
&:hover {
background-color: var(--grey-six);
}
@media screen and (max-width: 768px) {
width: 100%;
gap: 1rem;
}
}
.member-text {
display: flex;
flex-direction: column;
word-break: break-word;
}
img {
border-radius: 50%;
height: 64px;
width: 64px;
transition: transform 0.4s var(--bezier-one);
user-select: none;
margin-bottom: 1rem;
@media screen and (max-width: 768px) {
margin-bottom: 0;
height: 48px;
width: 48px;
}
}
</style>

3
src/util/supportsWebP.ts Normal file
View File

@@ -0,0 +1,3 @@
export function supportsWebP() {
return document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0;
}