diff --git a/src/app.css b/src/app.css index 33d6d00..490aff3 100644 --- a/src/app.css +++ b/src/app.css @@ -102,7 +102,7 @@ h6 { } ::-webkit-scrollbar-thumb { - background-color: var(--grey-six); + background-color: var(--grey-one); background-clip: content-box; border-radius: 100px; } diff --git a/src/data/api/index.ts b/src/data/api/index.ts index c265bba..9a47ae9 100644 --- a/src/data/api/index.ts +++ b/src/data/api/index.ts @@ -1,161 +1,172 @@ -import type { Readable, Subscriber, Unsubscriber, Writable } from "svelte/store"; -import { writable } from "svelte/store"; -import { error } from "@sveltejs/kit"; +import type { Readable, Subscriber, Unsubscriber, Writable } from 'svelte/store'; +import { writable } from 'svelte/store'; +import { error } from '@sveltejs/kit'; -import { prerendering, browser, dev } from "$app/environment"; +import { prerendering, browser, dev } from '$app/environment'; -import * as settings from "./settings"; -import * as cache from "./cache"; +import * as settings from './settings'; +import * as cache from './cache'; export class API implements Readable { - private store: Writable; - // True if we have or are about to request data from the API. - has_requested: boolean; + private store: Writable; + // 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; + // `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); - } + this.store = writable(cached_data || this.default_value); + } - private url() { - return `${settings.api_base_url()}/${this.endpoint}`; - } + 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); + // 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()); + 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); - } + // Update the cache. + cache.update(this.endpoint, data); + } - this.store.set(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); - } + // 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() - } + // 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 (prerendering) { - return {}; - } + // Implements the load function found in `+page/layout.ts` files. + page_load_impl() { + return async ({ fetch }) => { + if (prerendering) { + return {}; + } - // Might be better to actually return some data from the load function and use that on the client. - if (!(dev || browser || prerendering)) { - throw new Error("The API client is not optimized for production server-side rendering. Please change that :)"); - } + // Might be better to actually return some data from the load function and use that on the client. + if (!(dev || browser || prerendering)) { + 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"); - } - }; - } + try { + await this.update(fetch); + return {}; + } catch (e) { + console.error(e); + throw error(504, 'API Request Error'); + } + }; + } - // Implement Svelte store. - subscribe(run: Subscriber, invalidate?: any): Unsubscriber { - // Make sure we have up-to-date data from the API. - if (browser) { - this.retrieve_if_needed(); - } + // Implement Svelte store. + subscribe(run: Subscriber, 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); - } + return this.store.subscribe(run, invalidate); + } } // API Endpoints import type { Patch, Repository, Tool } from '../types'; -import { dev_log } from "$lib/utils"; +import { dev_log } from '$lib/utils'; export type ReposData = Repository[]; export type PatchesData = { patches: Patch[]; packages: string[] }; export type ToolsData = { [repo: string]: Tool }; -export const repositories = new API("contributors", [], json => json.repositories); +export const repositories = new API('contributors', [], (json) => json.repositories); // 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, - }] - } -} + 'revanced/revanced-manager': { + version: 'v0.0.0', + timestamp: '', + repository: '', + assets: [ + { + url: '', + name: '', + content_type: '', + size: null + } + ] + } +}; -export const tools = new API("tools", tools_placeholder, json => { - // The API returns data in a weird shape. Make it easier to work with. - let map: Map = new Map(); - for (const tool of json["tools"]) { - const repo: string = tool.repository; +export const tools = new API('tools', tools_placeholder, (json) => { + // The API returns data in a weird shape. Make it easier to work with. + let map: Map = new Map(); + for (const tool of json['tools']) { + const repo: string = tool.repository; - if (!map.has(repo)) { - map.set(repo, { - version: tool.version, - repository: repo, - // Just use the timestamp of the first one we find. - timestamp: tool.timestamp, - assets: [] - }); - } + if (!map.has(repo)) { + map.set(repo, { + version: tool.version, + repository: repo, + // Just use the timestamp of the first one we find. + timestamp: tool.timestamp, + assets: [] + }); + } - let value = map.get(repo); - value.assets.push({ - name: tool.name, - size: tool.size, - url: tool.browser_download_url, - content_type: tool.content_type - }); + let value = map.get(repo); + value.assets.push({ + name: tool.name, + size: tool.size, + url: tool.browser_download_url, + content_type: tool.content_type + }); - map.set(repo, value); - } + map.set(repo, value); + } - return Object.fromEntries(map); + return Object.fromEntries(map); }); -export const patches = new API("patches", { patches: [], packages: [] }, patches => { - let packages: string[] = []; +export const patches = new API('patches', { patches: [], packages: [] }, (patches) => { + const packagesWithCount: { [key: string]: number } = {}; - // gets packages + // gets packages and patch count for (let i = 0; i < patches.length; i++) { patches[i].compatiblePackages.forEach((pkg: Patch) => { - let index = packages.findIndex((x) => x == pkg.name); - if (index === -1) { - packages.push(pkg.name); - } + packagesWithCount[pkg.name] = (packagesWithCount[pkg.name] || 0) + 1; }); } + + // sort packages by patch count to get most relevant apps on top + const packages = Object.entries(packagesWithCount) + .sort((a, b) => b[1] - a[1]) + .map((pkg) => pkg[0]); + return { patches, packages }; }); diff --git a/src/lib/components/molecules/TreeMenu.svelte b/src/lib/components/molecules/TreeMenu.svelte index 91901ff..b0ea3d7 100644 --- a/src/lib/components/molecules/TreeMenu.svelte +++ b/src/lib/components/molecules/TreeMenu.svelte @@ -12,14 +12,16 @@