mirror of
https://github.com/revanced/revanced-website.git
synced 2025-04-29 22:24:31 +02:00
feat: use svelte query (#63)
This commit is contained in:
parent
368b2c9ee5
commit
e1b37fab7d
85
package-lock.json
generated
85
package-lock.json
generated
@ -8,6 +8,10 @@
|
||||
"name": "revanced-website",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "^4.24.4",
|
||||
"@tanstack/query-persist-client-core": "^4.24.4",
|
||||
"@tanstack/query-sync-storage-persister": "^4.24.4",
|
||||
"@tanstack/svelte-query": "^4.24.4",
|
||||
"asciidoctor": "^2.2.6",
|
||||
"marked": "^4.1.1"
|
||||
},
|
||||
@ -624,6 +628,54 @@
|
||||
"vite": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.24.4.tgz",
|
||||
"integrity": "sha512-9dqjv9eeB6VHN7lD3cLo16ZAjfjCsdXetSAD5+VyKqLUvcKTL0CklGQRJu+bWzdrS69R6Ea4UZo8obHYZnG6aA==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-persist-client-core": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-4.24.4.tgz",
|
||||
"integrity": "sha512-t4BR/th3tu2tkfF0Tcl5z+MbglDjTdIbsLZYlly7l2M6EhGXRjOLbvVUA5Kcx7E2a81Vup0zx0z0wlx+RPGfmg==",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "4.24.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-sync-storage-persister": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-4.24.4.tgz",
|
||||
"integrity": "sha512-0wffVqoOydMc1TDjOiATv/TM8wJfMpRcM82Cr19TuepListopTsuZ3RzSzLKBCo8WRl/0zCR1Ti9t1zn+Oai/A==",
|
||||
"dependencies": {
|
||||
"@tanstack/query-persist-client-core": "4.24.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/svelte-query": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/svelte-query/-/svelte-query-4.24.4.tgz",
|
||||
"integrity": "sha512-B2N+nNPOe9wxZ01i3Ebvkc8vDX9ZDQTiaazXqpld7O27R+zhIBlk6MU8vNzPBDSKrhhl2kak8SwyJOExsVZQXA==",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "4.24.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.54.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.1.tgz",
|
||||
@ -3200,7 +3252,6 @@
|
||||
"version": "3.55.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.0.tgz",
|
||||
"integrity": "sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
@ -3990,6 +4041,35 @@
|
||||
"vitefu": "^0.2.3"
|
||||
}
|
||||
},
|
||||
"@tanstack/query-core": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.24.4.tgz",
|
||||
"integrity": "sha512-9dqjv9eeB6VHN7lD3cLo16ZAjfjCsdXetSAD5+VyKqLUvcKTL0CklGQRJu+bWzdrS69R6Ea4UZo8obHYZnG6aA=="
|
||||
},
|
||||
"@tanstack/query-persist-client-core": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-4.24.4.tgz",
|
||||
"integrity": "sha512-t4BR/th3tu2tkfF0Tcl5z+MbglDjTdIbsLZYlly7l2M6EhGXRjOLbvVUA5Kcx7E2a81Vup0zx0z0wlx+RPGfmg==",
|
||||
"requires": {
|
||||
"@tanstack/query-core": "4.24.4"
|
||||
}
|
||||
},
|
||||
"@tanstack/query-sync-storage-persister": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-4.24.4.tgz",
|
||||
"integrity": "sha512-0wffVqoOydMc1TDjOiATv/TM8wJfMpRcM82Cr19TuepListopTsuZ3RzSzLKBCo8WRl/0zCR1Ti9t1zn+Oai/A==",
|
||||
"requires": {
|
||||
"@tanstack/query-persist-client-core": "4.24.4"
|
||||
}
|
||||
},
|
||||
"@tanstack/svelte-query": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/svelte-query/-/svelte-query-4.24.4.tgz",
|
||||
"integrity": "sha512-B2N+nNPOe9wxZ01i3Ebvkc8vDX9ZDQTiaazXqpld7O27R+zhIBlk6MU8vNzPBDSKrhhl2kak8SwyJOExsVZQXA==",
|
||||
"requires": {
|
||||
"@tanstack/query-core": "4.24.4"
|
||||
}
|
||||
},
|
||||
"@types/cookie": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.1.tgz",
|
||||
@ -5799,8 +5879,7 @@
|
||||
"svelte": {
|
||||
"version": "3.55.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.0.tgz",
|
||||
"integrity": "sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA=="
|
||||
},
|
||||
"svelte-check": {
|
||||
"version": "2.10.3",
|
||||
|
@ -35,6 +35,10 @@
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "^4.24.4",
|
||||
"@tanstack/query-persist-client-core": "^4.24.4",
|
||||
"@tanstack/query-sync-storage-persister": "^4.24.4",
|
||||
"@tanstack/svelte-query": "^4.24.4",
|
||||
"asciidoctor": "^2.2.6",
|
||||
"marked": "^4.1.1"
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
import { dev_log } from '$lib/utils';
|
||||
|
||||
const CACHE_KEY_PREFIX = 'revanced_api_cache_l1';
|
||||
const L1_CACHE_VALIDITY = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
function l1_key_name(endpoint: string) {
|
||||
return `${CACHE_KEY_PREFIX}:${endpoint}`;
|
||||
}
|
||||
|
||||
// Get item from the cache
|
||||
export function get(endpoint: string) {
|
||||
if (!browser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const key_name = l1_key_name(endpoint);
|
||||
const ls_data: { valid_until: number; data: any } | null = JSON.parse(
|
||||
localStorage.getItem(key_name) as string
|
||||
);
|
||||
|
||||
if (ls_data === null || ls_data.valid_until <= Date.now()) {
|
||||
dev_log('Cache', `missed "${endpoint}"`);
|
||||
localStorage.removeItem(key_name);
|
||||
return null;
|
||||
}
|
||||
|
||||
dev_log('Cache', `hit "${endpoint}"`);
|
||||
return ls_data.data;
|
||||
}
|
||||
|
||||
// Update the cache
|
||||
export function update(endpoint: string, data: any) {
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(
|
||||
l1_key_name(endpoint),
|
||||
JSON.stringify({
|
||||
data,
|
||||
valid_until: Date.now() + L1_CACHE_VALIDITY
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Clear the cache and reload
|
||||
export function clear_and_reload() {
|
||||
for (const key of Object.keys(localStorage)) {
|
||||
if (key.startsWith(CACHE_KEY_PREFIX)) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
location.reload();
|
||||
}
|
@ -1,130 +1,24 @@
|
||||
import type { Readable, Subscriber, Unsubscriber, Writable } from 'svelte/store';
|
||||
import { writable } from 'svelte/store';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
import { building, browser, dev } from '$app/environment';
|
||||
|
||||
import * as settings from './settings';
|
||||
import * as cache from './cache';
|
||||
|
||||
export class API<T> implements Readable<T> {
|
||||
private store: Writable<T>;
|
||||
// True if we have or are about to request data from the API.
|
||||
has_requested: boolean;
|
||||
|
||||
// `transform` will transform the data received from the API.
|
||||
constructor(
|
||||
public readonly endpoint: string,
|
||||
private readonly default_value: T,
|
||||
private readonly transform: (v: any) => T = (v) => v as T
|
||||
) {
|
||||
// Initialize with cached data if possible.
|
||||
const cached_data = cache.get(this.endpoint);
|
||||
this.has_requested = cached_data !== null;
|
||||
|
||||
this.store = writable(cached_data || this.default_value);
|
||||
}
|
||||
|
||||
private url() {
|
||||
return `${settings.api_base_url()}/${this.endpoint}`;
|
||||
}
|
||||
|
||||
// Please don't call this directly
|
||||
private async _update(fetch_fn: typeof fetch) {
|
||||
// Try to get data from the cache.
|
||||
let data = cache.get(this.endpoint);
|
||||
|
||||
if (data === null) {
|
||||
// Fetch and transform data
|
||||
const response = await fetch_fn(this.url());
|
||||
data = this.transform(await response.json());
|
||||
|
||||
// Update the cache.
|
||||
cache.update(this.endpoint, data);
|
||||
}
|
||||
|
||||
this.store.set(data);
|
||||
}
|
||||
|
||||
// Retrieve data and update.
|
||||
private update(fetch_fn = fetch) {
|
||||
// Make sure we set this immediately outside of the async function to avoid JS event loop weirdness.
|
||||
this.has_requested = true;
|
||||
return this._update(fetch_fn);
|
||||
}
|
||||
|
||||
// Start retrieving data if needed.
|
||||
retrieve_if_needed() {
|
||||
if (!this.has_requested) {
|
||||
return this.update();
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Implements the load function found in `+page/layout.ts` files.
|
||||
page_load_impl() {
|
||||
return async ({ fetch }) => {
|
||||
if (building) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Might be better to actually return some data from the load function and use that on the client.
|
||||
if (!(dev || browser || building)) {
|
||||
throw new Error(
|
||||
'The API client is not optimized for production server-side rendering. Please change that :)'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.update(fetch);
|
||||
return {};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw error(504, 'API Request Error');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Implement Svelte store.
|
||||
subscribe(run: Subscriber<T>, invalidate?: any): Unsubscriber {
|
||||
// Make sure we have up-to-date data from the API.
|
||||
if (browser) {
|
||||
this.retrieve_if_needed();
|
||||
}
|
||||
|
||||
return this.store.subscribe(run, invalidate);
|
||||
}
|
||||
}
|
||||
|
||||
// API Endpoints
|
||||
import type { Patch, Repository, Tool } from '../../utils/types';
|
||||
import { dev_log } from '$lib/utils';
|
||||
import type { Patch, Repository, Tool } from '$lib/types';
|
||||
|
||||
export type ReposData = Repository[];
|
||||
export type PatchesData = { patches: Patch[]; packages: string[] };
|
||||
export type ToolsData = { [repo: string]: Tool };
|
||||
|
||||
export const repositories = new API<ReposData>('contributors', [], (json) => json.repositories);
|
||||
async function get_json(endpoint: string) {
|
||||
const url = `${settings.api_base_url()}/${endpoint}`;
|
||||
return await fetch(url).then((r) => r.json());
|
||||
}
|
||||
|
||||
// It needs to look this way to not break everything.
|
||||
const tools_placeholder: ToolsData = {
|
||||
'revanced/revanced-manager': {
|
||||
version: 'v0.0.0',
|
||||
timestamp: '',
|
||||
repository: '',
|
||||
assets: [
|
||||
{
|
||||
url: '',
|
||||
name: '',
|
||||
content_type: '',
|
||||
size: null
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
async function repositories(): Promise<ReposData> {
|
||||
return await get_json('contributors').then((json) => json.repositories);
|
||||
}
|
||||
|
||||
export const tools = new API<ToolsData>('tools', tools_placeholder, (json) => {
|
||||
// The API returns data in a weird shape. Make it easier to work with.
|
||||
async function tools(): Promise<ToolsData> {
|
||||
const json = await get_json('tools');
|
||||
// Make the data easier to work with.
|
||||
let map: Map<string, Tool> = new Map();
|
||||
for (const tool of json['tools']) {
|
||||
const repo: string = tool.repository;
|
||||
@ -139,7 +33,7 @@ export const tools = new API<ToolsData>('tools', tools_placeholder, (json) => {
|
||||
});
|
||||
}
|
||||
|
||||
let value = map.get(repo);
|
||||
let value = map.get(repo)!!;
|
||||
value.assets.push({
|
||||
name: tool.name,
|
||||
size: tool.size,
|
||||
@ -151,14 +45,19 @@ export const tools = new API<ToolsData>('tools', tools_placeholder, (json) => {
|
||||
}
|
||||
|
||||
return Object.fromEntries(map);
|
||||
});
|
||||
}
|
||||
|
||||
export const patches = new API<PatchesData>('patches', { patches: [], packages: [] }, (patches) => {
|
||||
async function manager(): Promise<Tool> {
|
||||
return await tools().then((data) => data['revanced/revanced-manager']);
|
||||
}
|
||||
|
||||
async function patches(): Promise<PatchesData> {
|
||||
const json = await get_json('patches');
|
||||
const packagesWithCount: { [key: string]: number } = {};
|
||||
|
||||
// gets packages and patch count
|
||||
for (let i = 0; i < patches.length; i++) {
|
||||
patches[i].compatiblePackages.forEach((pkg: Patch) => {
|
||||
for (let i = 0; i < json.length; i++) {
|
||||
json[i].compatiblePackages.forEach((pkg: Patch) => {
|
||||
packagesWithCount[pkg.name] = (packagesWithCount[pkg.name] || 0) + 1;
|
||||
});
|
||||
}
|
||||
@ -168,5 +67,24 @@ export const patches = new API<PatchesData>('patches', { patches: [], packages:
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map((pkg) => pkg[0]);
|
||||
|
||||
return { patches, packages };
|
||||
});
|
||||
return { patches: json, packages };
|
||||
}
|
||||
|
||||
export const staleTime = 5 * 60 * 1000;
|
||||
export const queries = {
|
||||
manager: {
|
||||
queryKey: ['manager'],
|
||||
queryFn: manager,
|
||||
staleTime
|
||||
},
|
||||
patches: {
|
||||
queryKey: ['patches'],
|
||||
queryFn: patches,
|
||||
staleTime
|
||||
},
|
||||
repositories: {
|
||||
queryKey: ['repositories'],
|
||||
queryFn: repositories,
|
||||
staleTime
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { repositories } from '$data/api';
|
||||
import { queries } from '$data/api';
|
||||
import { friendlyName } from '$lib/utils';
|
||||
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
import Query from '$lib/components/Query.svelte';
|
||||
|
||||
const query = createQuery(['repositories'], queries.repositories);
|
||||
</script>
|
||||
|
||||
<svg aria-hidden="true" width="100%" height="8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@ -20,14 +25,15 @@
|
||||
<img src="/logo.svg" class="logo-image" alt="ReVanced Logo" />
|
||||
<div>
|
||||
<p>
|
||||
ReVanced was born out of Vanced's discontinuation and it is our goal to continue the legacy
|
||||
of what Vanced left behind. Thanks to ReVanced Patcher, it's possible to create long-lasting
|
||||
patches for nearly any Android app. ReVanced's patching system is designed to allow patches
|
||||
to work on new versions of the apps automatically with bare minimum maintenance.
|
||||
ReVanced was born out of Vanced's discontinuation and it is our goal to continue the
|
||||
legacy of what Vanced left behind. Thanks to ReVanced Patcher, it's possible to create
|
||||
long-lasting patches for nearly any Android app. ReVanced's patching system is designed to
|
||||
allow patches to work on new versions of the apps automatically with bare minimum
|
||||
maintenance.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="links-container">
|
||||
<div class="link-column">
|
||||
<li>Pages</li>
|
||||
@ -39,23 +45,31 @@
|
||||
</div>
|
||||
<div class="link-column">
|
||||
<li>Repositories</li>
|
||||
{#each $repositories as { name }}
|
||||
<li>
|
||||
<a href="https://github.com/{name}" target="_blank" rel="noreferrer">
|
||||
{friendlyName(name)}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
<Query {query} let:data>
|
||||
{#each data as { name }}
|
||||
<li>
|
||||
<a href="https://github.com/{name}" target="_blank" rel="noreferrer">
|
||||
{friendlyName(name)}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</Query>
|
||||
</div>
|
||||
<div class="link-column">
|
||||
<!-- to replace -->
|
||||
<li>Socials</li>
|
||||
<li><a href="https://github.com/revanced" target="_blank" rel="noreferrer">GitHub</a></li>
|
||||
<li><a href="https://revanced.app/discord" target="_blank" rel="noreferrer">Discord</a></li>
|
||||
<li><a href="https://reddit.com/r/revancedapp" target="_blank" rel="noreferrer">Reddit</a></li>
|
||||
<li>
|
||||
<a href="https://reddit.com/r/revancedapp" target="_blank" rel="noreferrer">Reddit</a>
|
||||
</li>
|
||||
<li><a href="https://t.me/app_revanced" target="_blank" rel="noreferrer">Telegram</a></li>
|
||||
<li><a href="https://twitter.com/revancedapp" target="_blank" rel="noreferrer">Twitter</a></li>
|
||||
<li><a href="https://www.youtube.com/c/ReVanced" target="_blank" rel="noreferrer">YouTube</a></li>
|
||||
<li>
|
||||
<a href="https://twitter.com/revancedapp" target="_blank" rel="noreferrer">Twitter</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.youtube.com/c/ReVanced" target="_blank" rel="noreferrer">YouTube</a>
|
||||
</li>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@ -66,7 +80,6 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
<style>
|
||||
footer {
|
||||
margin: 4rem 0 5rem 0;
|
||||
@ -95,7 +108,7 @@
|
||||
}
|
||||
|
||||
#logo-name span {
|
||||
color: var(--accent-color)
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.footer-bottom a {
|
||||
|
@ -1,9 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { queries } from '$data/api';
|
||||
import { dev_log } from '$lib/utils';
|
||||
|
||||
import RouterEvents from '$data/RouterEvents';
|
||||
import { useQueryClient } from '@tanstack/svelte-query';
|
||||
const client = useQueryClient();
|
||||
|
||||
export let href: string;
|
||||
export let queryKey: null | keyof typeof queries = null;
|
||||
|
||||
function prefetch() {
|
||||
if (queryKey !== null) {
|
||||
const query = queries[queryKey];
|
||||
dev_log('Prefetching', query);
|
||||
client.prefetchQuery(query as any);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<a data-sveltekit-preload-data {href}>
|
||||
<a data-sveltekit-preload-data on:mouseenter={prefetch} {href}>
|
||||
<!-- Check if href is equal to the first path -->
|
||||
<li class:selected={href === '/' + $RouterEvents.target_url.pathname.split('/')[1]}>
|
||||
<span><slot /></span>
|
||||
|
@ -9,10 +9,21 @@
|
||||
import Modal from '$lib/components/Dialogue.svelte';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
|
||||
import { clear_and_reload } from '$data/api/cache';
|
||||
import * as settings from '$data/api/settings';
|
||||
import RouterEvents from '$data/RouterEvents';
|
||||
|
||||
import { useQueryClient } from '@tanstack/svelte-query';
|
||||
|
||||
const client = useQueryClient();
|
||||
|
||||
function clear_and_reload() {
|
||||
client.clear();
|
||||
// `client.clear()` does technically do this for us, but it takes a while.
|
||||
localStorage.clear();
|
||||
|
||||
location.reload();
|
||||
}
|
||||
|
||||
let url = settings.api_base_url();
|
||||
|
||||
function save() {
|
||||
@ -58,13 +69,13 @@
|
||||
<div id="main-navigation">
|
||||
<div class="nav-buttons">
|
||||
<Navigation href="/">Home</Navigation>
|
||||
<Navigation href="/download">Download</Navigation>
|
||||
<Navigation href="/patches">Patches</Navigation>
|
||||
<Navigation queryKey="manager" href="/download">Download</Navigation>
|
||||
<Navigation queryKey="patches" href="/patches">Patches</Navigation>
|
||||
<div hidden>
|
||||
<!-- This is just temporary so the build doesn't fail -->
|
||||
<Navigation href="/docs">Docs</Navigation>
|
||||
</div>
|
||||
<Navigation href="/contributors">Contributors</Navigation>
|
||||
<Navigation queryKey="repositories" href="/contributors">Contributors</Navigation>
|
||||
</div>
|
||||
</div>
|
||||
<div id="secondary-navigation">
|
||||
@ -93,14 +104,16 @@
|
||||
<Modal bind:modalOpen>
|
||||
<svelte:fragment slot="icon">
|
||||
<Svg viewBoxHeight={24} viewBoxWidth={24} svgHeight={24}>
|
||||
<rect fill="none" height="24" width="24"/>
|
||||
<rect fill="none" height="24" width="24" />
|
||||
<path
|
||||
d="M19.5,12c0-0.23-0.01-0.45-0.03-0.68l1.86-1.41c0.4-0.3,0.51-0.86,0.26-1.3l-1.87-3.23c-0.25-0.44-0.79-0.62-1.25-0.42 l-2.15,0.91c-0.37-0.26-0.76-0.49-1.17-0.68l-0.29-2.31C14.8,2.38,14.37,2,13.87,2h-3.73C9.63,2,9.2,2.38,9.14,2.88L8.85,5.19 c-0.41,0.19-0.8,0.42-1.17,0.68L5.53,4.96c-0.46-0.2-1-0.02-1.25,0.42L2.41,8.62c-0.25,0.44-0.14,0.99,0.26,1.3l1.86,1.41 C4.51,11.55,4.5,11.77,4.5,12s0.01,0.45,0.03,0.68l-1.86,1.41c-0.4,0.3-0.51,0.86-0.26,1.3l1.87,3.23c0.25,0.44,0.79,0.62,1.25,0.42 l2.15-0.91c0.37,0.26,0.76,0.49,1.17,0.68l0.29,2.31C9.2,21.62,9.63,22,10.13,22h3.73c0.5,0,0.93-0.38,0.99-0.88l0.29-2.31 c0.41-0.19,0.8-0.42,1.17-0.68l2.15,0.91c0.46,0.2,1,0.02,1.25-0.42l1.87-3.23c0.25-0.44,0.14-0.99-0.26-1.3l-1.86-1.41 C19.49,12.45,19.5,12.23,19.5,12z M12.04,15.5c-1.93,0-3.5-1.57-3.5-3.5s1.57-3.5,3.5-3.5s3.5,1.57,3.5,3.5S13.97,15.5,12.04,15.5z"
|
||||
/>
|
||||
</Svg>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">Settings</svelte:fragment>
|
||||
<svelte:fragment slot="description">Configure the website's API. Defaults to ReVanced.</svelte:fragment>
|
||||
<svelte:fragment slot="description"
|
||||
>Configure the website's API. Defaults to ReVanced.</svelte:fragment
|
||||
>
|
||||
<div id="settings-content">
|
||||
<div class="input-wrapper">
|
||||
<input name="api-url" type="text" bind:value={url} />
|
||||
|
27
src/lib/components/Query.svelte
Normal file
27
src/lib/components/Query.svelte
Normal file
@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import type { CreateQueryResult } from '@tanstack/svelte-query';
|
||||
import { isRestoring } from '../../routes/+layout.svelte';
|
||||
// I might try to get this merged into tanstack query.
|
||||
|
||||
// So basically, this is how you do generics here.
|
||||
//https://github.com/sveltejs/language-tools/issues/273#issuecomment-1003496094
|
||||
type T = $$Generic;
|
||||
interface $$Slots {
|
||||
default: {
|
||||
// slot name
|
||||
data: T;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: errors
|
||||
|
||||
export let query: CreateQueryResult<T, any>;
|
||||
</script>
|
||||
|
||||
{#if !$isRestoring}
|
||||
{#if $query.isSuccess}
|
||||
<slot data={$query.data} />
|
||||
{:else if $query.isError}
|
||||
<slot name="error" />
|
||||
{/if}
|
||||
{/if}
|
@ -1,11 +1,44 @@
|
||||
<script lang="ts" context="module">
|
||||
import { writable } from 'svelte/store';
|
||||
// There might be a better place to put this, but I am not entirely sure...
|
||||
export const isRestoring = writable(false);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import '../app.scss';
|
||||
import { derived } from 'svelte/store';
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
import { QueryClient } from '@tanstack/query-core';
|
||||
import { persistQueryClient } from '@tanstack/query-persist-client-core';
|
||||
import { QueryClientProvider } from '@tanstack/svelte-query';
|
||||
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
|
||||
|
||||
import NavHost from '$layout/Navbar/NavHost.svelte';
|
||||
import Spinner from '$lib/components/Spinner.svelte';
|
||||
import { staleTime } from '$data/api';
|
||||
import RouterEvents from '$data/RouterEvents';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
enabled: browser,
|
||||
cacheTime: staleTime
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
isRestoring.set(true);
|
||||
const [unsubscribe, promise] = persistQueryClient({
|
||||
queryClient,
|
||||
persister: createSyncStoragePersister({ storage: localStorage })
|
||||
});
|
||||
promise.then(() => isRestoring.set(false));
|
||||
return unsubscribe;
|
||||
});
|
||||
|
||||
// Just like the set/clearInterval example found here: https://svelte.dev/docs#run-time-svelte-store-derived
|
||||
const show_loading_animation = derived(
|
||||
RouterEvents,
|
||||
@ -22,14 +55,14 @@
|
||||
);
|
||||
</script>
|
||||
|
||||
<NavHost />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<NavHost />
|
||||
|
||||
{#if $show_loading_animation}
|
||||
<Spinner />
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
<!--
|
||||
afn if you are moving the footer here, please make it not use the repositories store directly and instead use component props :) -->
|
||||
|
||||
<!-- <Footer repos={$repositories}> -->
|
||||
{#if $show_loading_animation}
|
||||
<Spinner />
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
<!-- guhh afn -->
|
||||
<!-- <Footer> -->
|
||||
</QueryClientProvider>
|
||||
|
@ -1,14 +1 @@
|
||||
import type { PageLoad } from './$types';
|
||||
import { repositories } from '$data/api';
|
||||
export const prerender = true;
|
||||
|
||||
const base = repositories.page_load_impl();
|
||||
|
||||
export const load: PageLoad = async ({ fetch }) => {
|
||||
// The entire site may softlock if the user sets a bad API url if we don't do this.
|
||||
try {
|
||||
return await base({ fetch });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
@ -2,11 +2,15 @@
|
||||
import { fly } from 'svelte/transition';
|
||||
import { quintOut } from 'svelte/easing';
|
||||
|
||||
import { repositories } from '$data/api';
|
||||
|
||||
import ContributorHost from './ContributorSection.svelte';
|
||||
import Footer from '$layout/Footer.svelte';
|
||||
import Meta from '$lib/components/Meta.svelte';
|
||||
import Query from '$lib/components/Query.svelte';
|
||||
|
||||
import { queries } from '$data/api';
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
|
||||
const query = createQuery(['repositories'], queries.repositories);
|
||||
</script>
|
||||
|
||||
<Meta title="Contributors" />
|
||||
@ -24,11 +28,13 @@
|
||||
</h4>
|
||||
</div>
|
||||
<div class="repos">
|
||||
{#each $repositories as { contributors, name: repo }}
|
||||
<div in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
|
||||
<ContributorHost {contributors} {repo} />
|
||||
</div>
|
||||
{/each}
|
||||
<Query {query} let:data>
|
||||
{#each data as { contributors, name: repo }}
|
||||
<div in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
|
||||
<ContributorHost {contributors} {repo} />
|
||||
</div>
|
||||
{/each}
|
||||
</Query>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
@ -1,4 +0,0 @@
|
||||
import { repositories } from '$data/api';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = repositories.page_load_impl();
|
@ -1,16 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { tools } from '$data/api';
|
||||
import { queries } from '$data/api';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { quintOut } from 'svelte/easing';
|
||||
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
|
||||
import manager_screenshot from '$images/manager_two.png?format=avif;webp;png&picture';
|
||||
|
||||
import Meta from '$lib/components/Meta.svelte';
|
||||
import Query from '$lib/components/Query.svelte';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Footer from '$layout/Footer.svelte';
|
||||
import Picture from '$lib/components/Picture.svelte';
|
||||
|
||||
$: manager = $tools['revanced/revanced-manager'];
|
||||
const query = createQuery(['manager'], queries.manager);
|
||||
</script>
|
||||
|
||||
<Meta title="Download" />
|
||||
@ -19,9 +22,11 @@
|
||||
<h2>ReVanced <span>Manager</span></h2>
|
||||
<p>Patch your favourite apps, right on your device.</p>
|
||||
<div class="buttons">
|
||||
<Button kind="primary" icon="download" href={manager.assets[0].url} target="_blank">
|
||||
{manager.version}
|
||||
</Button>
|
||||
<Query {query} let:data>
|
||||
<Button kind="primary" icon="download" href={data.assets[0].url} target="_blank">
|
||||
{data.version}
|
||||
</Button>
|
||||
</Query>
|
||||
<Button href="https://github.com/revanced/revanced-manager" target="_blank">View Source</Button>
|
||||
</div>
|
||||
<div class="screenshot">
|
||||
|
@ -1,5 +0,0 @@
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
import { tools } from '$data/api';
|
||||
|
||||
export const load: PageLoad = tools.page_load_impl();
|
@ -4,7 +4,9 @@
|
||||
|
||||
import type { PageData } from './$types';
|
||||
import type { Patch } from '$lib/types';
|
||||
import { patches as api_patches } from '$data/api';
|
||||
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
import { queries } from '$data/api';
|
||||
|
||||
import Meta from '$lib/components/Meta.svelte';
|
||||
import PackageMenu from '../PackageMenu.svelte';
|
||||
@ -14,8 +16,9 @@
|
||||
import Search from '$lib/components/Search.svelte';
|
||||
import FilterChip from '$lib/components/FilterChip.svelte';
|
||||
import Dialogue from '$lib/components/Dialogue.svelte';
|
||||
import Query from '$lib/components/Query.svelte';
|
||||
|
||||
$: ({ patches, packages } = $api_patches);
|
||||
const query = createQuery(['patches'], queries.patches);
|
||||
|
||||
export let data: PageData;
|
||||
$: ({ selectedPkg } = data);
|
||||
@ -25,24 +28,44 @@
|
||||
let timeout: any = null;
|
||||
let mobilePackages = false;
|
||||
|
||||
function filterByPackage(pkg: string | undefined, packageList: any[]) {
|
||||
return packageList.find((x: Package) => x.name === pkg);
|
||||
function checkCompatibility(patch: Patch, pkg: string, cmp: (a: string, b: string) => boolean) {
|
||||
if (pkg === '') {
|
||||
return false;
|
||||
}
|
||||
return !!patch.compatiblePackages.find((compat) => cmp(compat.name, pkg));
|
||||
}
|
||||
|
||||
function checkPkgName(pkg: string, packageList: any[]) {
|
||||
// Basically the same function as before lol
|
||||
return packageList.find((x: Package) => x.name.replace(/\./g, '').includes(pkg));
|
||||
function searchString(str: string, term: string, replacement_regex: RegExp) {
|
||||
return str
|
||||
.toLowerCase()
|
||||
.replace(replacement_regex, '')
|
||||
.includes(term || '');
|
||||
}
|
||||
|
||||
function search(patch: Patch) {
|
||||
return (
|
||||
patch.description.toLowerCase().replace(/\s/g, '').includes(searchTermFiltered) ||
|
||||
patch.name.toLowerCase().replace(/-/g, '').includes(searchTermFiltered) ||
|
||||
checkPkgName(searchTermFiltered, patch.compatiblePackages)
|
||||
);
|
||||
function filter(patches: Patch[], pkg: string, search?: string): Patch[] {
|
||||
if (search === undefined && pkg === '') {
|
||||
return patches;
|
||||
}
|
||||
|
||||
return patches.filter((patch) => {
|
||||
// Don't show if the patch doesn't support the selected package
|
||||
if (pkg && !checkCompatibility(patch, pkg, (a, b) => a === b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter based on the search term.
|
||||
if (search !== undefined) {
|
||||
return (
|
||||
searchString(patch.description, search, /\s/g) ||
|
||||
searchString(patch.name, search, /-/g) ||
|
||||
checkCompatibility(patch, pkg, (a, b) => searchString(a, b, /\./g))
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure we don't have filter the patches after every key press
|
||||
// Make sure we don't have to filter the patches after every key press
|
||||
const debounce = () => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
@ -83,55 +106,51 @@
|
||||
<FilterChip>Patch options</FilterChip> -->
|
||||
</div>
|
||||
|
||||
<div class="mobile-packages-Dialogue">
|
||||
<Dialogue bind:modalOpen={mobilePackages} fullscreen>
|
||||
<svelte:fragment slot="title">Packages</svelte:fragment>
|
||||
<div class="mobile-packages">
|
||||
<span
|
||||
on:click={() => (mobilePackages = !mobilePackages)}
|
||||
on:keypress={() => (mobilePackages = !mobilePackages)}
|
||||
>
|
||||
<Package {selectedPkg} name="All packages" />
|
||||
</span>
|
||||
{#each packages as pkg}
|
||||
<Query {query} let:data>
|
||||
<div class="mobile-packages-Dialogue">
|
||||
<Dialogue bind:modalOpen={mobilePackages} fullscreen>
|
||||
<svelte:fragment slot="title">Packages</svelte:fragment>
|
||||
<div class="mobile-packages">
|
||||
<span
|
||||
on:click={() => (mobilePackages = !mobilePackages)}
|
||||
on:keypress={() => (mobilePackages = !mobilePackages)}
|
||||
>
|
||||
<Package {selectedPkg} name={pkg} />
|
||||
<Package {selectedPkg} name="All packages" />
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
</Dialogue>
|
||||
</div>
|
||||
{#each data.packages as pkg}
|
||||
<span
|
||||
on:click={() => (mobilePackages = !mobilePackages)}
|
||||
on:keypress={() => (mobilePackages = !mobilePackages)}
|
||||
>
|
||||
<Package {selectedPkg} name={pkg} />
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
</Dialogue>
|
||||
</div>
|
||||
|
||||
<aside in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
|
||||
<PackageMenu>
|
||||
<span class="packages">
|
||||
<Package {selectedPkg} name="All packages" />
|
||||
{#each packages as pkg}
|
||||
<Package {selectedPkg} name={pkg} />
|
||||
{/each}
|
||||
</span>
|
||||
</PackageMenu>
|
||||
</aside>
|
||||
<aside in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
|
||||
<PackageMenu>
|
||||
<span class="packages">
|
||||
<Package {selectedPkg} name="All packages" />
|
||||
{#each data.packages as pkg}
|
||||
<Package {selectedPkg} name={pkg} />
|
||||
{/each}
|
||||
</span>
|
||||
</PackageMenu>
|
||||
</aside>
|
||||
|
||||
<div class="patches-container">
|
||||
{#each patches as patch}
|
||||
<!-- Trigger new animations when package or search changes (I love Svelte) -->
|
||||
{#key selectedPkg || searchTermFiltered}
|
||||
<!-- Show patch if it supports the selected package, or if no package has been selected -->
|
||||
{#if filterByPackage(selectedPkg, patch.compatiblePackages) || !selectedPkg}
|
||||
<!-- ...same with search -->
|
||||
{#if search(patch) || !searchTermFiltered}
|
||||
<div in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
|
||||
<PatchItem {patch} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/key}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="patches-container">
|
||||
{#each filter(data.patches, selectedPkg || '', searchTermFiltered) as patch}
|
||||
<!-- Trigger new animations when package or search changes (I love Svelte) -->
|
||||
{#key selectedPkg || searchTermFiltered}
|
||||
<div in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
|
||||
<PatchItem {patch} />
|
||||
</div>
|
||||
{/key}
|
||||
{/each}
|
||||
</div>
|
||||
</Query>
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
|
@ -1,10 +1,6 @@
|
||||
import { patches } from '$data/api';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
const api = patches.page_load_impl();
|
||||
|
||||
export const load: PageLoad = async ({ params, fetch }) => {
|
||||
export const load: PageLoad = async ({ params }) => {
|
||||
const selectedPkg = params.package || undefined;
|
||||
await api({ fetch });
|
||||
return { selectedPkg };
|
||||
};
|
||||
|
@ -10,11 +10,13 @@
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"types": ["node"],
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"paths": {
|
||||
"$data/*": ["./src/data/*"],
|
||||
"$lib": ["src/lib"],
|
||||
"$lib/*": ["./src/lib/*"],
|
||||
"$layout/*": ["./src/layout/*"],
|
||||
"$images/*": ["./images/*"],
|
||||
},
|
||||
"$images/*": ["./images/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user