Merge pull request #186 from WardPearce/fix/layout-and-code-layout
Fix/layout and code layout
This commit is contained in:
@@ -0,0 +1,456 @@
|
||||
<script lang="ts">
|
||||
import { bookmarkletSaveToUrl } from '$lib/bookmarklet';
|
||||
import 'beercss';
|
||||
import 'material-dynamic-colors';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import {
|
||||
darkModeStore,
|
||||
deArrowEnabledStore,
|
||||
deArrowInstanceStore,
|
||||
deArrowThumbnailInstanceStore,
|
||||
interfacePreviewVideoOnHoverStore,
|
||||
interfaceSearchSuggestionsStore,
|
||||
playerAlwaysLoopStore,
|
||||
playerAutoPlayStore,
|
||||
playerAutoplayNextByDefaultStore,
|
||||
playerDashStore,
|
||||
playerListenByDefaultStore,
|
||||
playerProxyVideosStore,
|
||||
playerSavePlaybackPositionStore,
|
||||
playerTheatreModeByDefaultStore,
|
||||
returnYTDislikesInstanceStore,
|
||||
returnYtDislikesStore,
|
||||
sponsorBlockCategoriesStore,
|
||||
sponsorBlockStore,
|
||||
sponsorBlockUrlStore,
|
||||
synciousInstanceStore,
|
||||
synciousStore,
|
||||
themeColorStore
|
||||
} from '../store';
|
||||
|
||||
let sponsorCategoriesList: string[] = [];
|
||||
sponsorBlockCategoriesStore.subscribe((value) => (sponsorCategoriesList = value));
|
||||
|
||||
let sponsorBlockInstance = get(sponsorBlockUrlStore);
|
||||
let synciousInstance = get(synciousInstanceStore);
|
||||
let returnYTInstance = get(returnYTDislikesInstanceStore);
|
||||
let deArrowUrl = get(deArrowInstanceStore);
|
||||
let deArrowThumbnailUrl = get(deArrowThumbnailInstanceStore);
|
||||
|
||||
const sponsorCategories = [
|
||||
{ name: $_('layout.sponsors.sponsor'), category: 'sponsor' },
|
||||
{ name: $_('layout.sponsors.unpaidSelfPromotion'), category: 'selfpromo' },
|
||||
{ name: $_('layout.sponsors.interactionReminder'), category: 'interaction' },
|
||||
{ name: $_('layout.sponsors.intermissionIntroAnimation'), category: 'intro' },
|
||||
{ name: $_('layout.sponsors.credits'), category: 'outro' },
|
||||
{ name: $_('layout.sponsors.preViewRecapHook'), category: 'preview' },
|
||||
{ name: $_('layout.sponsors.tangentJokes'), category: 'filler' }
|
||||
];
|
||||
|
||||
function toggleSponsor(category: string) {
|
||||
if (sponsorCategoriesList.includes(category)) {
|
||||
sponsorBlockCategoriesStore.set(sponsorCategoriesList.filter((value) => value !== category));
|
||||
} else {
|
||||
sponsorCategoriesList.push(category);
|
||||
sponsorBlockCategoriesStore.set(sponsorCategoriesList);
|
||||
}
|
||||
}
|
||||
|
||||
async function setColor(color: any) {
|
||||
const target = color.target;
|
||||
const hex = (target as { value: string }).value;
|
||||
await ui('theme', hex);
|
||||
themeColorStore.set(hex);
|
||||
}
|
||||
|
||||
function toggleDarkMode() {
|
||||
const isDark = get(darkModeStore);
|
||||
|
||||
if (isDark) {
|
||||
ui('mode', 'light');
|
||||
darkModeStore.set(false);
|
||||
} else {
|
||||
ui('mode', 'dark');
|
||||
darkModeStore.set(true);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<dialog class="right" id="dialog-settings">
|
||||
<nav>
|
||||
<h4 class="max">{$_('layout.settings')}</h4>
|
||||
<button class="circle transparent" data-ui="#dialog-settings"><i>close</i></button>
|
||||
</nav>
|
||||
<p>{$_('layout.customize')} Materialious</p>
|
||||
|
||||
<div class="settings">
|
||||
<h6>{$_('layout.theme.theme')}</h6>
|
||||
<button on:click={toggleDarkMode} class="no-margin">
|
||||
{#if !$darkModeStore}
|
||||
<i>dark_mode</i>
|
||||
<span>{$_('layout.theme.darkMode')}</span>
|
||||
{:else}
|
||||
<i>light_mode</i>
|
||||
<span>{$_('layout.theme.lightMode')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
<button>
|
||||
<i>palette</i>
|
||||
<span>{$_('layout.theme.color')}</span>
|
||||
<input on:change={setColor} type="color" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>Interface</h6>
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.searchSuggestions')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$interfaceSearchSuggestionsStore}
|
||||
on:click={() => interfaceSearchSuggestionsStore.set(!$interfaceSearchSuggestionsStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.previewVideoOnHover')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$interfacePreviewVideoOnHoverStore}
|
||||
on:click={() =>
|
||||
interfacePreviewVideoOnHoverStore.set(!$interfacePreviewVideoOnHoverStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>{$_('layout.dataPreferences.dataPreferences')}</h6>
|
||||
<p style="width: 240px;">
|
||||
<a
|
||||
href={`${import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE}/preferences`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link"
|
||||
>
|
||||
{$_('layout.dataPreferences.content')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>{$_('layout.player.title')}</h6>
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.autoPlay')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerAutoPlayStore}
|
||||
on:click={() => playerAutoPlayStore.set(!$playerAutoPlayStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.alwaysLoopVideo')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerAlwaysLoopStore}
|
||||
on:click={() => playerAlwaysLoopStore.set(!$playerAlwaysLoopStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.proxyVideos')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerProxyVideosStore}
|
||||
on:click={() => playerProxyVideosStore.set(!$playerProxyVideosStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.savePlaybackPosition')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerSavePlaybackPositionStore}
|
||||
on:click={() => playerSavePlaybackPositionStore.set(!$playerSavePlaybackPositionStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.listenByDefault')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerListenByDefaultStore}
|
||||
on:click={() => playerListenByDefaultStore.set(!$playerListenByDefaultStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin m l">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.theatreModeByDefault')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerTheatreModeByDefaultStore}
|
||||
on:click={() => playerTheatreModeByDefaultStore.set(!$playerTheatreModeByDefaultStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.autoPlayNextByDefault')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerAutoplayNextByDefaultStore}
|
||||
on:click={() =>
|
||||
playerAutoplayNextByDefaultStore.set(!$playerAutoplayNextByDefaultStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.dash')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerDashStore}
|
||||
on:click={() => playerDashStore.set(!$playerDashStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>Return YT Dislikes</h6>
|
||||
|
||||
<form on:submit|preventDefault={() => returnYTDislikesInstanceStore.set(returnYTInstance)}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input bind:value={returnYTInstance} name="returnyt-instance" type="text" />
|
||||
<label for="returnyt-instance">{$_('layout.instanceUrl')}</label>
|
||||
</div>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
</button>
|
||||
</nav>
|
||||
</form>
|
||||
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<p>{$_('enabled')}</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
bind:checked={$returnYtDislikesStore}
|
||||
on:click={() => returnYtDislikesStore.set(!$returnYtDislikesStore)}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>Syncious</h6>
|
||||
|
||||
<form on:submit|preventDefault={() => synciousInstanceStore.set(synciousInstance)}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input bind:value={synciousInstance} name="syncious-instance" type="text" />
|
||||
<label for="syncious-instance">{$_('layout.instanceUrl')}</label>
|
||||
</div>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
</button>
|
||||
</nav>
|
||||
</form>
|
||||
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<p>{$_('enabled')}</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
bind:checked={$synciousStore}
|
||||
on:click={() => synciousStore.set(!$synciousStore)}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>Sponsorblock</h6>
|
||||
|
||||
<form on:submit|preventDefault={() => sponsorBlockUrlStore.set(sponsorBlockInstance)}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input bind:value={sponsorBlockInstance} name="sponsorblock-instance" type="text" />
|
||||
<label for="sponsorblock-instance">{$_('layout.instanceUrl')}</label>
|
||||
</div>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
</button>
|
||||
</nav>
|
||||
</form>
|
||||
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<p>{$_('enabled')}</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
bind:checked={$sponsorBlockStore}
|
||||
on:click={() => sponsorBlockStore.set(!$sponsorBlockStore)}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
|
||||
{#each sponsorCategories as sponsor}
|
||||
<div class="field middle-align no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<p>{sponsor.name}</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={sponsorCategoriesList.includes(sponsor.category)}
|
||||
on:click={() => toggleSponsor(sponsor.category)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>{$_('layout.deArrow.title')}</h6>
|
||||
|
||||
<form on:submit|preventDefault={() => deArrowInstanceStore.set(deArrowUrl)}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input bind:value={deArrowUrl} name="dearrow-instance" type="text" />
|
||||
<label for="dearrow-instance">{$_('layout.instanceUrl')}</label>
|
||||
</div>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
</button>
|
||||
</nav>
|
||||
</form>
|
||||
|
||||
<form on:submit|preventDefault={() => deArrowThumbnailInstanceStore.set(deArrowThumbnailUrl)}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input bind:value={deArrowThumbnailUrl} name="dearrow-thumbnail-instance" type="text" />
|
||||
<label for="dearrow-thumbnail-instance">{$_('layout.deArrow.thumbnailInstanceUrl')}</label
|
||||
>
|
||||
</div>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
</button>
|
||||
</nav>
|
||||
</form>
|
||||
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<p>{$_('enabled')}</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
bind:checked={$deArrowEnabledStore}
|
||||
on:click={() => deArrowEnabledStore.set(!$deArrowEnabledStore)}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>{$_('layout.bookmarklet')}</h6>
|
||||
<button
|
||||
class="no-margin"
|
||||
on:click={async () => await navigator.clipboard.writeText(bookmarkletSaveToUrl())}
|
||||
>{$_('copyUrl')}</button
|
||||
>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<style>
|
||||
.settings h6 {
|
||||
margin: 1em 0 0.3em 0;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 1em 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,146 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { peerJsOptions, removeWindowQueryFlag, setWindowQueryFlag } from '$lib/misc';
|
||||
import type { PlayerEvents } from '$lib/player';
|
||||
import 'beercss';
|
||||
import 'material-dynamic-colors';
|
||||
import type { DataConnection } from 'peerjs';
|
||||
import Peer from 'peerjs';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { syncPartyConnectionsStore, syncPartyPeerStore } from '../store';
|
||||
|
||||
function changeVideoEvent(conn: DataConnection) {
|
||||
conn.on('data', (data) => {
|
||||
const events = data as PlayerEvents;
|
||||
const currentUrl = get(page).url;
|
||||
|
||||
events.events.forEach((event) => {
|
||||
if (event.type === 'change-video' && event.videoId) {
|
||||
currentUrl.pathname = `/watch/${event.videoId}`;
|
||||
goto(currentUrl);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function startWatchSync() {
|
||||
if ($syncPartyPeerStore) {
|
||||
$syncPartyPeerStore.destroy();
|
||||
syncPartyPeerStore.set(null);
|
||||
removeWindowQueryFlag('sync');
|
||||
return;
|
||||
}
|
||||
|
||||
const peerId = crypto.randomUUID();
|
||||
setWindowQueryFlag('sync', peerId);
|
||||
$syncPartyPeerStore = new Peer(peerId, peerJsOptions());
|
||||
|
||||
if ($syncPartyPeerStore) {
|
||||
$syncPartyPeerStore.on('connection', (conn) => {
|
||||
conn.on('open', () => {
|
||||
ui('#sync-party-connection-join');
|
||||
|
||||
if ($page.url.pathname.startsWith('/watch')) {
|
||||
const paths = $page.url.pathname.split('/');
|
||||
if (paths.length > 2) {
|
||||
conn.send({
|
||||
events: [
|
||||
{
|
||||
type: 'change-video',
|
||||
videoId: paths[2]
|
||||
}
|
||||
]
|
||||
} as PlayerEvents);
|
||||
}
|
||||
}
|
||||
changeVideoEvent(conn);
|
||||
syncPartyConnectionsStore.set([...($syncPartyConnectionsStore || []), conn]);
|
||||
});
|
||||
|
||||
conn.on('close', () => {
|
||||
ui('#sync-party-connection-left');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const currentUrl = get(page).url;
|
||||
const syncId = currentUrl.searchParams.get('sync');
|
||||
if (syncId) {
|
||||
$syncPartyPeerStore = new Peer(crypto.randomUUID(), peerJsOptions());
|
||||
$syncPartyPeerStore.on('open', () => {
|
||||
if (!$syncPartyPeerStore) return;
|
||||
|
||||
const conn = $syncPartyPeerStore.connect(syncId);
|
||||
syncPartyConnectionsStore.set([...($syncPartyConnectionsStore || []), conn]);
|
||||
|
||||
changeVideoEvent(conn);
|
||||
});
|
||||
|
||||
$syncPartyPeerStore.on('disconnected', () => {
|
||||
syncPartyPeerStore.set(null);
|
||||
syncPartyConnectionsStore.set(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if ($syncPartyPeerStore) {
|
||||
$syncPartyPeerStore.destroy();
|
||||
$syncPartyPeerStore = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<dialog id="sync-party">
|
||||
<h6>{$_('layout.syncParty')}</h6>
|
||||
<p>{$_('layout.syncPartyWarning')}</p>
|
||||
{#if $syncPartyPeerStore}
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input
|
||||
name="sync-share"
|
||||
readonly
|
||||
value={`${import.meta.env.VITE_DEFAULT_FRONTEND_URL}?sync=${$syncPartyPeerStore.id}`}
|
||||
type="text"
|
||||
/>
|
||||
<label class="active" for="sync-share">Share URL</label>
|
||||
</div>
|
||||
<button
|
||||
on:click={async () => {
|
||||
await navigator.clipboard.writeText(
|
||||
`${import.meta.env.VITE_DEFAULT_FRONTEND_URL}?sync=${$syncPartyPeerStore.id}`
|
||||
);
|
||||
}}
|
||||
class="square round"
|
||||
>
|
||||
<i>content_copy</i>
|
||||
</button>
|
||||
</nav>
|
||||
{/if}
|
||||
<div class="space"></div>
|
||||
<button
|
||||
class="no-margin"
|
||||
on:click={startWatchSync}
|
||||
data-ui={`${$syncPartyPeerStore ? '#sync-party' : ''}`}
|
||||
>{#if $syncPartyPeerStore}
|
||||
{$_('layout.endSyncParty')}
|
||||
{:else}
|
||||
{$_('layout.startSyncParty')}
|
||||
{/if}
|
||||
</button>
|
||||
</dialog>
|
||||
|
||||
<div class="snackbar" id="sync-party-connection-join">
|
||||
<i>person_add</i>
|
||||
<span>{$_('syncParty.userJoined')}</span>
|
||||
</div>
|
||||
|
||||
<div class="snackbar" id="sync-party-connection-left">
|
||||
<i>person_remove</i>
|
||||
<span>{$_('syncParty.userLeft')}</span>
|
||||
</div>
|
||||
@@ -1,20 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { navigating, page } from '$app/stores';
|
||||
import { navigating } from '$app/stores';
|
||||
import { getFeed } from '$lib/Api/index';
|
||||
import type { Notification } from '$lib/Api/model';
|
||||
import Logo from '$lib/Logo.svelte';
|
||||
import PageLoading from '$lib/PageLoading.svelte';
|
||||
import Search from '$lib/Search.svelte';
|
||||
import Settings from '$lib/Settings.svelte';
|
||||
import SyncParty from '$lib/SyncParty.svelte';
|
||||
import Thumbnail from '$lib/Thumbnail.svelte';
|
||||
import { bookmarkletLoadFromUrl, bookmarkletSaveToUrl } from '$lib/bookmarklet';
|
||||
import { peerJsOptions, removeWindowQueryFlag, setWindowQueryFlag } from '$lib/misc';
|
||||
import type { PlayerEvents } from '$lib/player';
|
||||
import { bookmarkletLoadFromUrl } from '$lib/bookmarklet';
|
||||
import 'beercss';
|
||||
import 'material-dynamic-colors';
|
||||
import type { DataConnection } from 'peerjs';
|
||||
import Peer from 'peerjs';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { pwaInfo } from 'virtual:pwa-info';
|
||||
@@ -22,28 +20,7 @@
|
||||
activePageStore,
|
||||
authStore,
|
||||
darkModeStore,
|
||||
deArrowEnabledStore,
|
||||
deArrowInstanceStore,
|
||||
deArrowThumbnailInstanceStore,
|
||||
interfacePreviewVideoOnHoverStore,
|
||||
interfaceSearchSuggestionsStore,
|
||||
playerAlwaysLoopStore,
|
||||
playerAutoPlayStore,
|
||||
playerAutoplayNextByDefaultStore,
|
||||
playerDashStore,
|
||||
playerListenByDefaultStore,
|
||||
playerProxyVideosStore,
|
||||
playerSavePlaybackPositionStore,
|
||||
playerTheatreModeByDefaultStore,
|
||||
returnYTDislikesInstanceStore,
|
||||
returnYtDislikesStore,
|
||||
sponsorBlockCategoriesStore,
|
||||
sponsorBlockStore,
|
||||
sponsorBlockUrlStore,
|
||||
syncPartyConnectionsStore,
|
||||
syncPartyPeerStore,
|
||||
synciousInstanceStore,
|
||||
synciousStore,
|
||||
themeColorStore
|
||||
} from '../store';
|
||||
|
||||
@@ -52,9 +29,6 @@
|
||||
let currentPage: string | null = '';
|
||||
activePageStore.subscribe((page) => (currentPage = page));
|
||||
|
||||
let sponsorCategoriesList: string[] = [];
|
||||
sponsorBlockCategoriesStore.subscribe((value) => (sponsorCategoriesList = value));
|
||||
|
||||
let isLoggedIn = false;
|
||||
authStore.subscribe((value) => {
|
||||
isLoggedIn = value !== null;
|
||||
@@ -62,12 +36,6 @@
|
||||
|
||||
let notifications: Notification[] = [];
|
||||
|
||||
let sponsorBlockInstance = get(sponsorBlockUrlStore);
|
||||
let synciousInstance = get(synciousInstanceStore);
|
||||
let returnYTInstance = get(returnYTDislikesInstanceStore);
|
||||
let deArrowUrl = get(deArrowInstanceStore);
|
||||
let deArrowThumbnailUrl = get(deArrowThumbnailInstanceStore);
|
||||
|
||||
const pages = [
|
||||
{
|
||||
icon: 'home',
|
||||
@@ -101,99 +69,6 @@
|
||||
}
|
||||
];
|
||||
|
||||
const sponsorCategories = [
|
||||
{ name: $_('layout.sponsors.sponsor'), category: 'sponsor' },
|
||||
{ name: $_('layout.sponsors.unpaidSelfPromotion'), category: 'selfpromo' },
|
||||
{ name: $_('layout.sponsors.interactionReminder'), category: 'interaction' },
|
||||
{ name: $_('layout.sponsors.intermissionIntroAnimation'), category: 'intro' },
|
||||
{ name: $_('layout.sponsors.credits'), category: 'outro' },
|
||||
{ name: $_('layout.sponsors.preViewRecapHook'), category: 'preview' },
|
||||
{ name: $_('layout.sponsors.tangentJokes'), category: 'filler' }
|
||||
];
|
||||
|
||||
function changeVideoEvent(conn: DataConnection) {
|
||||
conn.on('data', (data) => {
|
||||
const events = data as PlayerEvents;
|
||||
const currentUrl = get(page).url;
|
||||
|
||||
events.events.forEach((event) => {
|
||||
if (event.type === 'change-video' && event.videoId) {
|
||||
currentUrl.pathname = `/watch/${event.videoId}`;
|
||||
goto(currentUrl);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function startWatchSync() {
|
||||
if ($syncPartyPeerStore) {
|
||||
$syncPartyPeerStore.destroy();
|
||||
syncPartyPeerStore.set(null);
|
||||
removeWindowQueryFlag('sync');
|
||||
return;
|
||||
}
|
||||
|
||||
const peerId = crypto.randomUUID();
|
||||
setWindowQueryFlag('sync', peerId);
|
||||
$syncPartyPeerStore = new Peer(peerId, peerJsOptions());
|
||||
|
||||
if ($syncPartyPeerStore) {
|
||||
$syncPartyPeerStore.on('connection', (conn) => {
|
||||
conn.on('open', () => {
|
||||
ui('#sync-party-connection-join');
|
||||
|
||||
if ($page.url.pathname.startsWith('/watch')) {
|
||||
const paths = $page.url.pathname.split('/');
|
||||
if (paths.length > 2) {
|
||||
conn.send({
|
||||
events: [
|
||||
{
|
||||
type: 'change-video',
|
||||
videoId: paths[2]
|
||||
}
|
||||
]
|
||||
} as PlayerEvents);
|
||||
}
|
||||
}
|
||||
changeVideoEvent(conn);
|
||||
syncPartyConnectionsStore.set([...($syncPartyConnectionsStore || []), conn]);
|
||||
});
|
||||
|
||||
conn.on('close', () => {
|
||||
ui('#sync-party-connection-left');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSponsor(category: string) {
|
||||
if (sponsorCategoriesList.includes(category)) {
|
||||
sponsorBlockCategoriesStore.set(sponsorCategoriesList.filter((value) => value !== category));
|
||||
} else {
|
||||
sponsorCategoriesList.push(category);
|
||||
sponsorBlockCategoriesStore.set(sponsorCategoriesList);
|
||||
}
|
||||
}
|
||||
|
||||
async function setColor(color: any) {
|
||||
const target = color.target;
|
||||
const hex = (target as { value: string }).value;
|
||||
await ui('theme', hex);
|
||||
themeColorStore.set(hex);
|
||||
}
|
||||
|
||||
function toggleDarkMode() {
|
||||
const isDark = get(darkModeStore);
|
||||
|
||||
if (isDark) {
|
||||
ui('mode', 'light');
|
||||
darkModeStore.set(false);
|
||||
} else {
|
||||
ui('mode', 'dark');
|
||||
darkModeStore.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
darkModeStore.subscribe((isDark) => {
|
||||
if (isDark) {
|
||||
ui('mode', 'dark');
|
||||
@@ -253,32 +128,6 @@
|
||||
if (isLoggedIn) {
|
||||
loadNotifications().catch(() => authStore.set(null));
|
||||
}
|
||||
|
||||
const currentUrl = get(page).url;
|
||||
const syncId = currentUrl.searchParams.get('sync');
|
||||
if (syncId) {
|
||||
$syncPartyPeerStore = new Peer(crypto.randomUUID(), peerJsOptions());
|
||||
$syncPartyPeerStore.on('open', () => {
|
||||
if (!$syncPartyPeerStore) return;
|
||||
|
||||
const conn = $syncPartyPeerStore.connect(syncId);
|
||||
syncPartyConnectionsStore.set([...($syncPartyConnectionsStore || []), conn]);
|
||||
|
||||
changeVideoEvent(conn);
|
||||
});
|
||||
|
||||
$syncPartyPeerStore.on('disconnected', () => {
|
||||
syncPartyPeerStore.set(null);
|
||||
syncPartyConnectionsStore.set(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if ($syncPartyPeerStore) {
|
||||
$syncPartyPeerStore.destroy();
|
||||
$syncPartyPeerStore = null;
|
||||
}
|
||||
});
|
||||
|
||||
$: webManifestLink = pwaInfo ? pwaInfo.webManifest.linkTag : '';
|
||||
@@ -315,7 +164,7 @@
|
||||
>
|
||||
|
||||
<Logo classes="m l" />
|
||||
<h6 class="m l">Materialious</h6>
|
||||
<h6 class="l">Materialious</h6>
|
||||
|
||||
<div class="max m l"></div>
|
||||
|
||||
@@ -330,7 +179,7 @@
|
||||
{#if mobileSearchShow}
|
||||
<Search on:searchCancelled={() => (mobileSearchShow = false)} />
|
||||
{:else}
|
||||
<button data-ui="#sync-party" class="m l circle large transparent">
|
||||
<button data-ui="#sync-party" class="circle large transparent">
|
||||
<i class:primary-text={$syncPartyPeerStore}>group</i>
|
||||
<div class="tooltip bottom">{$_('layout.syncParty')}</div>
|
||||
</button>
|
||||
@@ -370,373 +219,7 @@
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
<dialog class="right" id="dialog-settings">
|
||||
<nav>
|
||||
<h4 class="max">{$_('layout.settings')}</h4>
|
||||
<button class="circle transparent" data-ui="#dialog-settings"><i>close</i></button>
|
||||
</nav>
|
||||
<p>{$_('layout.customize')} Materialious</p>
|
||||
|
||||
<div class="settings">
|
||||
<h6>{$_('layout.theme.theme')}</h6>
|
||||
<button on:click={toggleDarkMode} class="no-margin">
|
||||
{#if !$darkModeStore}
|
||||
<i>dark_mode</i>
|
||||
<span>{$_('layout.theme.darkMode')}</span>
|
||||
{:else}
|
||||
<i>light_mode</i>
|
||||
<span>{$_('layout.theme.lightMode')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
<button>
|
||||
<i>palette</i>
|
||||
<span>{$_('layout.theme.color')}</span>
|
||||
<input on:change={setColor} type="color" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>Interface</h6>
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.searchSuggestions')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$interfaceSearchSuggestionsStore}
|
||||
on:click={() => interfaceSearchSuggestionsStore.set(!$interfaceSearchSuggestionsStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.previewVideoOnHover')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$interfacePreviewVideoOnHoverStore}
|
||||
on:click={() =>
|
||||
interfacePreviewVideoOnHoverStore.set(!$interfacePreviewVideoOnHoverStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>{$_('layout.dataPreferences.dataPreferences')}</h6>
|
||||
<p style="width: 240px;">
|
||||
<a
|
||||
href={`${import.meta.env.VITE_DEFAULT_INVIDIOUS_INSTANCE}/preferences`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link"
|
||||
>
|
||||
{$_('layout.dataPreferences.content')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>{$_('layout.player.title')}</h6>
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.autoPlay')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerAutoPlayStore}
|
||||
on:click={() => playerAutoPlayStore.set(!$playerAutoPlayStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.alwaysLoopVideo')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerAlwaysLoopStore}
|
||||
on:click={() => playerAlwaysLoopStore.set(!$playerAlwaysLoopStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.proxyVideos')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerProxyVideosStore}
|
||||
on:click={() => playerProxyVideosStore.set(!$playerProxyVideosStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.savePlaybackPosition')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerSavePlaybackPositionStore}
|
||||
on:click={() => playerSavePlaybackPositionStore.set(!$playerSavePlaybackPositionStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.listenByDefault')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerListenByDefaultStore}
|
||||
on:click={() => playerListenByDefaultStore.set(!$playerListenByDefaultStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin m l">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.theatreModeByDefault')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerTheatreModeByDefaultStore}
|
||||
on:click={() => playerTheatreModeByDefaultStore.set(!$playerTheatreModeByDefaultStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.autoPlayNextByDefault')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerAutoplayNextByDefaultStore}
|
||||
on:click={() =>
|
||||
playerAutoplayNextByDefaultStore.set(!$playerAutoplayNextByDefaultStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="field no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<div>{$_('layout.player.dash')}</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$playerDashStore}
|
||||
on:click={() => playerDashStore.set(!$playerDashStore)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>Return YT Dislikes</h6>
|
||||
|
||||
<form on:submit|preventDefault={() => returnYTDislikesInstanceStore.set(returnYTInstance)}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input bind:value={returnYTInstance} name="returnyt-instance" type="text" />
|
||||
<label for="returnyt-instance">{$_('layout.instanceUrl')}</label>
|
||||
</div>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
</button>
|
||||
</nav>
|
||||
</form>
|
||||
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<p>{$_('enabled')}</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
bind:checked={$returnYtDislikesStore}
|
||||
on:click={() => returnYtDislikesStore.set(!$returnYtDislikesStore)}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>Syncious</h6>
|
||||
|
||||
<form on:submit|preventDefault={() => synciousInstanceStore.set(synciousInstance)}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input bind:value={synciousInstance} name="syncious-instance" type="text" />
|
||||
<label for="syncious-instance">{$_('layout.instanceUrl')}</label>
|
||||
</div>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
</button>
|
||||
</nav>
|
||||
</form>
|
||||
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<p>{$_('enabled')}</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
bind:checked={$synciousStore}
|
||||
on:click={() => synciousStore.set(!$synciousStore)}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>Sponsorblock</h6>
|
||||
|
||||
<form on:submit|preventDefault={() => sponsorBlockUrlStore.set(sponsorBlockInstance)}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input bind:value={sponsorBlockInstance} name="sponsorblock-instance" type="text" />
|
||||
<label for="sponsorblock-instance">{$_('layout.instanceUrl')}</label>
|
||||
</div>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
</button>
|
||||
</nav>
|
||||
</form>
|
||||
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<p>{$_('enabled')}</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
bind:checked={$sponsorBlockStore}
|
||||
on:click={() => sponsorBlockStore.set(!$sponsorBlockStore)}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
|
||||
{#each sponsorCategories as sponsor}
|
||||
<div class="field middle-align no-margin">
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<p>{sponsor.name}</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={sponsorCategoriesList.includes(sponsor.category)}
|
||||
on:click={() => toggleSponsor(sponsor.category)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>{$_('layout.deArrow.title')}</h6>
|
||||
|
||||
<form on:submit|preventDefault={() => deArrowInstanceStore.set(deArrowUrl)}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input bind:value={deArrowUrl} name="dearrow-instance" type="text" />
|
||||
<label for="dearrow-instance">{$_('layout.instanceUrl')}</label>
|
||||
</div>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
</button>
|
||||
</nav>
|
||||
</form>
|
||||
|
||||
<form on:submit|preventDefault={() => deArrowThumbnailInstanceStore.set(deArrowThumbnailUrl)}>
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input bind:value={deArrowThumbnailUrl} name="dearrow-thumbnail-instance" type="text" />
|
||||
<label for="dearrow-thumbnail-instance">{$_('layout.deArrow.thumbnailInstanceUrl')}</label
|
||||
>
|
||||
</div>
|
||||
<button class="square round">
|
||||
<i>done</i>
|
||||
</button>
|
||||
</nav>
|
||||
</form>
|
||||
|
||||
<nav class="no-padding">
|
||||
<div class="max">
|
||||
<p>{$_('enabled')}</p>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input
|
||||
bind:checked={$deArrowEnabledStore}
|
||||
on:click={() => deArrowEnabledStore.set(!$deArrowEnabledStore)}
|
||||
type="checkbox"
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="settings">
|
||||
<h6>{$_('layout.bookmarklet')}</h6>
|
||||
<button
|
||||
class="no-margin"
|
||||
on:click={async () => await navigator.clipboard.writeText(bookmarkletSaveToUrl())}
|
||||
>{$_('copyUrl')}</button
|
||||
>
|
||||
</div>
|
||||
</dialog>
|
||||
<Settings />
|
||||
|
||||
<dialog class="right" id="dialog-notifications">
|
||||
<nav>
|
||||
@@ -780,54 +263,7 @@
|
||||
{/each}
|
||||
</dialog>
|
||||
|
||||
<dialog id="sync-party">
|
||||
<h6>{$_('layout.syncParty')}</h6>
|
||||
<p>{$_('layout.syncPartyWarning')}</p>
|
||||
{#if $syncPartyPeerStore}
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input
|
||||
name="sync-share"
|
||||
readonly
|
||||
value={`${import.meta.env.VITE_DEFAULT_FRONTEND_URL}?sync=${$syncPartyPeerStore.id}`}
|
||||
type="text"
|
||||
/>
|
||||
<label class="active" for="sync-share">Share URL</label>
|
||||
</div>
|
||||
<button
|
||||
on:click={async () => {
|
||||
await navigator.clipboard.writeText(
|
||||
`${import.meta.env.VITE_DEFAULT_FRONTEND_URL}?sync=${$syncPartyPeerStore.id}`
|
||||
);
|
||||
}}
|
||||
class="square round"
|
||||
>
|
||||
<i>content_copy</i>
|
||||
</button>
|
||||
</nav>
|
||||
{/if}
|
||||
<div class="space"></div>
|
||||
<button
|
||||
class="no-margin"
|
||||
on:click={startWatchSync}
|
||||
data-ui={`${$syncPartyPeerStore ? '#sync-party' : ''}`}
|
||||
>{#if $syncPartyPeerStore}
|
||||
{$_('layout.endSyncParty')}
|
||||
{:else}
|
||||
{$_('layout.startSyncParty')}
|
||||
{/if}
|
||||
</button>
|
||||
</dialog>
|
||||
|
||||
<div class="snackbar" id="sync-party-connection-join">
|
||||
<i>person_add</i>
|
||||
<span>{$_('syncParty.userJoined')}</span>
|
||||
</div>
|
||||
|
||||
<div class="snackbar" id="sync-party-connection-left">
|
||||
<i>person_remove</i>
|
||||
<span>{$_('syncParty.userLeft')}</span>
|
||||
</div>
|
||||
<SyncParty />
|
||||
|
||||
<main class="responsive max root">
|
||||
{#if $navigating}
|
||||
@@ -841,18 +277,4 @@
|
||||
nav.left a {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.settings h6 {
|
||||
margin: 1em 0 0.3em 0;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 650px) {
|
||||
dialog.right {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,6 +31,11 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
main.root {
|
||||
max-height: 100vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 580px) {
|
||||
main {
|
||||
padding-left: 0.1em !important;
|
||||
@@ -38,7 +43,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
main.root {
|
||||
max-height: 100vh;
|
||||
overflow-y: scroll;
|
||||
@media screen and (max-width: 650px) {
|
||||
dialog.right {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user