mirror of
https://github.com/ReVanced/revanced-website.git
synced 2026-01-11 05:36:17 +00:00
feat: brand new Modal component (#41)
* feat: brand new Modal component * Apply suggestions from code review Co-authored-by: Ax333l <main@axelen.xyz>
This commit is contained in:
17
src/app.css
17
src/app.css
@@ -33,6 +33,17 @@ body {
|
||||
margin-top: 7rem;
|
||||
}
|
||||
|
||||
.button-reset {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-style: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:root {
|
||||
--main-font: "Manrope", sans-serif;
|
||||
--mono-font: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
|
||||
@@ -100,6 +111,12 @@ h6 {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--grey-five);
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/*---------------*/
|
||||
|
||||
::-webkit-scrollbar {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { browser } from "$app/environment";
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
import { dev_log } from "$lib/utils";
|
||||
import { dev_log } from '$lib/utils';
|
||||
|
||||
const CACHE_KEY_PREFIX = "revanced_api_cache_l1";
|
||||
const CACHE_KEY_PREFIX = 'revanced_api_cache_l1';
|
||||
const L1_CACHE_VALIDITY = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
function l1_key_name(endpoint: string) {
|
||||
@@ -16,16 +16,17 @@ export function get(endpoint: string) {
|
||||
}
|
||||
|
||||
const key_name = l1_key_name(endpoint);
|
||||
const ls_data: { valid_until: number; data: any } | null = JSON.parse(localStorage.getItem(key_name));
|
||||
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}"`);
|
||||
dev_log('Cache', `missed "${endpoint}"`);
|
||||
localStorage.removeItem(key_name);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
dev_log("Cache", `hit "${endpoint}"`);
|
||||
dev_log('Cache', `hit "${endpoint}"`);
|
||||
return ls_data.data;
|
||||
}
|
||||
|
||||
@@ -35,17 +36,21 @@ export function update(endpoint: string, data: any) {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(l1_key_name(endpoint), JSON.stringify({
|
||||
localStorage.setItem(
|
||||
l1_key_name(endpoint),
|
||||
JSON.stringify({
|
||||
data,
|
||||
valid_until: Date.now() + L1_CACHE_VALIDITY
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Clear the cache
|
||||
export function clear() {
|
||||
// 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();
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ import { browser } from "$app/environment";
|
||||
|
||||
const URL_KEY = "revanced_api_url";
|
||||
|
||||
export const default_base_url = "https://releases.revanced.app";
|
||||
|
||||
// Get base URL
|
||||
export function api_base_url(): string {
|
||||
const default_base_url = "https://releases.revanced.app";
|
||||
if (browser) {
|
||||
return localStorage.getItem(URL_KEY) || default_base_url;
|
||||
}
|
||||
|
||||
126
src/lib/components/atoms/ApiSettingsButton.svelte
Normal file
126
src/lib/components/atoms/ApiSettingsButton.svelte
Normal file
@@ -0,0 +1,126 @@
|
||||
<script lang="ts">
|
||||
import * as settings from '../../../data/api/settings';
|
||||
import Modal from '$lib/components/atoms/Modal.svelte';
|
||||
import { clear_and_reload } from '../../../data/api/cache';
|
||||
|
||||
let url = settings.api_base_url();
|
||||
|
||||
function save() {
|
||||
settings.set_api_base_url(url);
|
||||
clear_and_reload();
|
||||
}
|
||||
|
||||
function reset() {
|
||||
url = settings.default_base_url;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal icon="/icons/settings.svg" alt="Settings">
|
||||
<div class="settings-menu">
|
||||
<h3>Configure the backend</h3>
|
||||
<div class="url-stuff">
|
||||
<h4>API URL:</h4>
|
||||
<div class="input-wrapper">
|
||||
<input name="api-url" type="text" bind:value={url} />
|
||||
<button class="button-reset" on:click={reset}>
|
||||
<img src="/icons/reset.svg" alt="Reset" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btns">
|
||||
<button class="save-url-btn" on:click={save}>
|
||||
<h4>Save</h4>
|
||||
</button>
|
||||
<button class="clear-cache-btn" on:click={clear_and_reload}>
|
||||
<h4>Clear cache</h4>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
div.settings-menu {
|
||||
position: fixed;
|
||||
width: min(95%, 500px);
|
||||
/* width: clamp(45vw, 850px, 850px); */
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 40px;
|
||||
border-radius: 12px;
|
||||
background-color: var(--grey-six);
|
||||
display: flex;
|
||||
user-select: none;
|
||||
gap: 5%;
|
||||
z-index: 999;
|
||||
white-space: normal;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
box-shadow: 0 3rem 5rem rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
div.btns {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
text-align: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btns button {
|
||||
min-width: max-content;
|
||||
font-size: 1rem;
|
||||
color: var(--white);
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
flex-grow: 2;
|
||||
padding: 1rem 1.75rem;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
background-color: var(--grey-two);
|
||||
transition: transform 0.4s var(--bezier-one), filter 0.4s var(--bezier-one);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.btns .save-url-btn {
|
||||
background-color: var(--accent-color);
|
||||
box-shadow: 0px 0px 32px 1px var(--accent-color-glow);
|
||||
flex-grow: 1;
|
||||
}
|
||||
.save-url-btn h4 {
|
||||
color: var(--grey-four);
|
||||
}
|
||||
div.input-wrapper {
|
||||
display: flex;
|
||||
background-color: #2a2c33;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
margin: 10px 0 10px 0;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 780px) {
|
||||
div.settings-menu {
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -9,7 +9,9 @@
|
||||
|
||||
<a {href} {target}>
|
||||
<div class={type} style="width: {maxWidth ? '100%' : 'max-content'}">
|
||||
{#if icon}
|
||||
<img src="../icons/{icon}.svg" alt={icon} />
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
</a>
|
||||
@@ -46,8 +48,6 @@
|
||||
filter: brightness(85%);
|
||||
}
|
||||
|
||||
|
||||
|
||||
div,
|
||||
.button-secondary {
|
||||
display: flex;
|
||||
|
||||
71
src/lib/components/atoms/Modal.svelte
Normal file
71
src/lib/components/atoms/Modal.svelte
Normal file
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
import { click_outside } from '$lib/utils';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
let modalOpen = false;
|
||||
export let icon: string;
|
||||
export let alt: string;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<button class="modal-btn" on:click={() => (modalOpen = !modalOpen)}>
|
||||
<img src={icon} {alt} />
|
||||
</button>
|
||||
{#if modalOpen}
|
||||
<div class="overlay" />
|
||||
<div
|
||||
class="modal-container"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
use:click_outside
|
||||
on:click_outside={() => (modalOpen = false)}
|
||||
transition:fade={{ duration: 125 }}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
button.modal-btn {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
transition-timing-function: var(--bezier-one);
|
||||
transition-duration: 0.25s;
|
||||
padding: 10px 25px;
|
||||
border-radius: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
button.modal-btn:hover {
|
||||
color: var(--white);
|
||||
background-color: var(--grey-one);
|
||||
}
|
||||
|
||||
img {
|
||||
height: 1.75rem;
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
button.modal-btn {
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import Navigation from '../atoms/NavButton.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import Button from '../atoms/Button.svelte';
|
||||
import MobileDropdown from './MobileDropdown.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import RouterEvents from '../../../data/RouterEvents';
|
||||
import ApiSettingsButton from '$lib/components/atoms/ApiSettingsButton.svelte';
|
||||
|
||||
let menuOpen = false;
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
<img src="/logo.svg" class="logo-image" alt="ReVanced Logo" />
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<span class="desktop">
|
||||
<Navigation href="/">Home</Navigation>
|
||||
<Navigation href="/download">Download</Navigation>
|
||||
@@ -44,14 +43,12 @@
|
||||
<Navigation href="/contributors/">
|
||||
<img src="/icons/contrib.svg" alt="Contributors" />
|
||||
</Navigation>
|
||||
<Navigation href="/api-settings/">
|
||||
<img src="/icons/settings.svg" alt="Settings" />
|
||||
</Navigation>
|
||||
<ApiSettingsButton />
|
||||
</span>
|
||||
|
||||
<!-- Should probably be moved to its own component. -->
|
||||
<button
|
||||
class="menu-btn mobile"
|
||||
class="menu-btn button-reset mobile"
|
||||
class:open={menuOpen}
|
||||
on:click={() => {
|
||||
menuOpen = !menuOpen;
|
||||
@@ -76,9 +73,9 @@
|
||||
<Navigation href="/contributors/">
|
||||
<img src="/icons/contrib.svg" alt="Contributors" />
|
||||
</Navigation>
|
||||
<Navigation href="/api-settings/">
|
||||
<img src="/icons/settings.svg" alt="Settings" />
|
||||
</Navigation>
|
||||
<ApiSettingsButton on:click={() => {
|
||||
menuOpen = false;
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
</MobileDropdown>
|
||||
@@ -171,6 +168,7 @@
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
user-select: none;
|
||||
position: relative;
|
||||
@@ -181,17 +179,8 @@
|
||||
height: 60px;
|
||||
cursor: pointer;
|
||||
transition: all 0.5s var(--bezier-one);
|
||||
|
||||
/* We don't want it to look like a normal button. */
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-style: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.menu-btn__burger {
|
||||
width: 25px;
|
||||
height: 2px;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as settings from '../../data/api/settings';
|
||||
import { clear } from '../../data/api/cache';
|
||||
|
||||
let url = settings.api_base_url();
|
||||
|
||||
function handler() {
|
||||
clear();
|
||||
settings.set_api_base_url(url);
|
||||
location.reload(true);
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="settings">
|
||||
<input name="api-url" type="text" bind:value={url} />
|
||||
<button on:click={handler}>Save</button>
|
||||
</section>
|
||||
<section class="cache">
|
||||
<button on:click={clear}>Clear cache</button>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.settings {
|
||||
padding-top: 5rem;
|
||||
}
|
||||
</style>
|
||||
1
static/icons/reset.svg
Normal file
1
static/icons/reset.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#ACC1D2"><path d="M14 38v-3h14.45q3.5 0 6.025-2.325Q37 30.35 37 26.9t-2.525-5.775Q31.95 18.8 28.45 18.8H13.7l5.7 5.7-2.1 2.1L8 17.3 17.3 8l2.1 2.1-5.7 5.7h14.7q4.75 0 8.175 3.2Q40 22.2 40 26.9t-3.425 7.9Q33.15 38 28.4 38Z"/></svg>
|
||||
|
After Width: | Height: | Size: 299 B |
Reference in New Issue
Block a user