Compare commits
2 commits
b55f6e2543
...
507ea37f74
Author | SHA1 | Date | |
---|---|---|---|
|
507ea37f74 | ||
|
35a9b95c8e |
21 changed files with 570 additions and 300 deletions
18
src/app.pcss
18
src/app.pcss
|
@ -1,18 +0,0 @@
|
|||
/* Write your global styles here, in PostCSS syntax */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
|
@ -5,5 +5,6 @@ export const TAG_TYPES = {
|
|||
general: 'General',
|
||||
lore: 'Lore',
|
||||
meta: 'Meta',
|
||||
invalid: 'Invalid'
|
||||
invalid: 'Invalid',
|
||||
species: 'Species'
|
||||
};
|
|
@ -1,88 +0,0 @@
|
|||
<script>
|
||||
import useSettings from '$lib/useSettings.svelte.js';
|
||||
const settings = useSettings();
|
||||
|
||||
let tabPos = $state(0);
|
||||
let showApikey = $state(false);
|
||||
let apikey = $state('');
|
||||
</script>
|
||||
<div class="flex flex-col">
|
||||
<h2 class="h2 mb-2 text-dark-token">Settings</h2>
|
||||
<!--<TabGroup>
|
||||
<Tab class="text-dark-token" bind:group={tabPos} name="tab1" value={0}>Display</Tab>
|
||||
<Tab class="text-dark-token" bind:group={tabPos} name="tab2" value={1}>API</Tab>
|
||||
<Tab class="text-dark-token" bind:group={tabPos} name="tab3" value={2}>User</Tab>
|
||||
<Tab class="text-dark-token" bind:group={tabPos} name="tab4" value={3}>Chats</Tab>
|
||||
|
||||
{#snippet panel()}
|
||||
{#if tabPos === 0}
|
||||
<div class="flex flex-col gap-3">
|
||||
<h3 class="h3 text-dark-token font-bold">Image</h3>
|
||||
<SlideToggle name="image-limit-height" bind:checked={settings.limitImageHeight}><span class="text-dark-token">Limit image height</span></SlideToggle>
|
||||
|
||||
<h3 class="h3 text-dark-token font-bold">Grid</h3>
|
||||
<div class="basis-1/2">
|
||||
<RangeSlider accent="text-dark-token" name="grid-columns" min={1} max={12} bind:value={settings.imageGridColumns} ticked={true}>
|
||||
<div class="flex flex-grow justify-between items-center dark">
|
||||
<div class="text-dark-token font-bold mb-1">Grid Columns</div>
|
||||
<div class="text-xs text-dark-token">{settings.imageGridColumns} / {12}</div>
|
||||
</div>
|
||||
</RangeSlider>
|
||||
</div>
|
||||
<div class="basis-1/2">
|
||||
<label class="label">
|
||||
<span class="text-dark-token font-bold">Preview type</span>
|
||||
<select class="select" bind:value={settings.postGridPreviewQuality}>
|
||||
<option value="preview">Preview</option>
|
||||
<option value="sample">Sample</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if tabPos === 1}
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="">
|
||||
<label class="label">
|
||||
<span class="text-dark-token font-bold">e621 Username</span>
|
||||
<input class="input" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="">
|
||||
<label class="label">
|
||||
<span class="text-dark-token font-bold">e621 Apikey</span>
|
||||
{#if showApikey}
|
||||
<input class="input" type="text" bind:value={apikey} />
|
||||
{:else}
|
||||
<input class="input" type="password" bind:value={apikey} />
|
||||
{/if}
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="text-dark-token font-bold">Show apikey</span>
|
||||
<input class="checkbox" type="checkbox" bind:checked={showApikey} />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if tabPos === 2}
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="">
|
||||
<p class="text-dark-token font-bold">Blacklist</p>
|
||||
<InputChip name="blacklist" label="Blacklist" bind:value={settings.blacklist} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if tabPos === 3}
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="">
|
||||
<p class="text-dark-token font-bold">Chats</p>
|
||||
<InputChip name="blacklist" label="Blacklist" bind:value={settings.blacklist} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</TabGroup>-->
|
||||
</div>
|
|
@ -2,6 +2,7 @@
|
|||
import {faArrowLeft} from '@fortawesome/free-solid-svg-icons';
|
||||
import {Fa} from 'svelte-fa';
|
||||
import {onMount} from "svelte";
|
||||
import Button from "$lib/components/Button.svelte";
|
||||
|
||||
let { popCount = 1 } = $props();
|
||||
let href = $state();
|
||||
|
@ -28,6 +29,6 @@
|
|||
|
||||
</script>
|
||||
|
||||
<a {href} class="flex flex-row aspect-square place-content-center justify-center items-center bg-gray-600 h-10">
|
||||
<Button {href} >
|
||||
<Fa color="white" size={'1.5x'} icon={faArrowLeft} />
|
||||
</a>
|
||||
</Button>
|
||||
|
|
30
src/lib/components/Button.svelte
Normal file
30
src/lib/components/Button.svelte
Normal file
|
@ -0,0 +1,30 @@
|
|||
<script>
|
||||
let { children, class: className, fill = false, type = 'button', onclick = null} = $props();
|
||||
</script>
|
||||
|
||||
<button class={className}
|
||||
style:width={fill ? '100%' : 'auto'}
|
||||
{type}
|
||||
onclick={() => onclick?.()}>
|
||||
{@render children?.()}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
button {
|
||||
aspect-ratio: 1 / 1;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
place-content: center;
|
||||
height: 40px;
|
||||
padding: 10px;
|
||||
|
||||
background-color: #4b5563;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: #434b58;
|
||||
}
|
||||
}
|
||||
</style>
|
58
src/lib/components/Chips.svelte
Normal file
58
src/lib/components/Chips.svelte
Normal file
|
@ -0,0 +1,58 @@
|
|||
<script>
|
||||
import { Button } from '$lib/components';
|
||||
|
||||
let { values = $bindable([]), onAdd = () => {} } = $props();
|
||||
let newValue = $state('');
|
||||
|
||||
function onAddNewValue() {
|
||||
onAdd?.(newValue);
|
||||
newValue = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="chips-container">
|
||||
<form>
|
||||
<input bind:value={newValue} type="text" />
|
||||
<Button type="submit" onclick={onAddNewValue}>Add</Button>
|
||||
</form>
|
||||
<div class="chips">
|
||||
{#if values}
|
||||
{#each values as chip}
|
||||
<div class="chip">
|
||||
<Button onclick={() => {
|
||||
values = values.filter(value => value !== chip);
|
||||
}}>{chip}</Button>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.chips-container {
|
||||
display: grid;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chip {
|
||||
color: white;
|
||||
|
||||
background-color: #4b5563;
|
||||
border-radius: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: #434b58;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
|
@ -1,22 +1,29 @@
|
|||
<script>
|
||||
import useSettings from '$lib/useSettings.svelte.js';
|
||||
import {loading} from "$lib/stores.js";
|
||||
import {loading} from '$lib/stores.js';
|
||||
import {Button} from '$lib/components';
|
||||
import {lastSearch} from '$lib/stores.js';
|
||||
|
||||
const settings = useSettings();
|
||||
let searchString = $state('');
|
||||
const {onsubmit = null} = $props();
|
||||
$effect(() => {
|
||||
searchString = settings.lastSearch
|
||||
searchString = $lastSearch
|
||||
});
|
||||
</script>
|
||||
|
||||
<form class="flex flex-row gap-2"
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
settings.lastSearch = searchString;
|
||||
$lastSearch = searchString;
|
||||
loading.set(true);
|
||||
onsubmit?.(searchString, true);
|
||||
}}>
|
||||
<input class="h-10 p-2" type="text" placeholder="tags" bind:value={searchString} />
|
||||
<button class="h-10 p-2 text-white bg-gray-600" type="submit">Search</button>
|
||||
<input type="text" placeholder="tags" bind:value={searchString} />
|
||||
<Button type="submit">Search</Button>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
form {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
|
@ -1,11 +1,12 @@
|
|||
<script>
|
||||
import {BackButton, GlobalSearch} from "$lib/components";
|
||||
import {faArrowLeft, faCogs} from "@fortawesome/free-solid-svg-icons";
|
||||
import {BackButton, GlobalSearch, LinkButton} from "$lib/components";
|
||||
import {faCogs} from "@fortawesome/free-solid-svg-icons";
|
||||
import {Fa} from "svelte-fa";
|
||||
let {onsubmit = null, back = null, class: className, showSearch = true} = $props();
|
||||
let {onsubmit = null, back = null, class: className, showSearch = true, gridArea = ""} = $props();
|
||||
</script>
|
||||
|
||||
<header class="absolute w-full top-0 flex bg-transparent py-2 px-4 gap-1 {className}">
|
||||
<header class={className} style:grid-area={gridArea}>
|
||||
<div class="left">
|
||||
{#if back}
|
||||
<BackButton popCount={back} />
|
||||
{/if}
|
||||
|
@ -14,7 +15,25 @@
|
|||
onsubmit?.(val, explicitRefresh);
|
||||
}} />
|
||||
{/if}
|
||||
<a href="/settings" class="flex flex-row aspect-square place-content-center justify-center items-center bg-gray-600 h-10">
|
||||
</div>
|
||||
<div class="right">
|
||||
<LinkButton href="/settings/main">
|
||||
<Fa color="white" size={'1.5x'} icon={faCogs} />
|
||||
</a>
|
||||
</LinkButton>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
grid-area: header;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
|
@ -1,25 +1,36 @@
|
|||
<script>
|
||||
import useSettings from "$lib/useSettings.svelte.js";
|
||||
import { loading } from "$lib/stores.js";
|
||||
import { loading, imageGridColumns, postGridPreviewQuality } from "$lib/stores.js";
|
||||
|
||||
let { posts = [] } = $props();
|
||||
const settings = useSettings();
|
||||
</script>
|
||||
|
||||
<div class="grid gap-3" style={`grid-template-columns: repeat(${settings.imageGridColumns}, minmax(0, 1fr))`}>
|
||||
<div class="post-grid" style={`grid-template-columns: repeat(${$imageGridColumns}, minmax(0, 1fr))`}>
|
||||
{#each posts as post}
|
||||
<a href={`/posts/${post.id}`} class="card p-3 flex flex-col justify-end items-center" onclick={() => {
|
||||
<a href={`/posts/${post.id}`} class="post-grid-item" onclick={() => {
|
||||
loading.set(true);
|
||||
}}>
|
||||
<header class="card-header p-0"></header>
|
||||
<section class="flex flex-col">
|
||||
<img src={post[settings.postGridPreviewQuality].url}
|
||||
<img src={post[$postGridPreviewQuality].url}
|
||||
loading="lazy"
|
||||
alt={`Post ${post.id}`} />
|
||||
<div class="bg-black px-2 py-1">
|
||||
<p class="text-center text-white">{post.id} {post.file.ext} {post.rating}</p>
|
||||
</div>
|
||||
</section>
|
||||
<p class="post-info-bar">{post.id} {post.file.ext} {post.rating}</p>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.post-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.post-grid-item {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.post-info-bar {
|
||||
color: white;
|
||||
text-align: center;
|
||||
background-color: #000;
|
||||
}
|
||||
</style>
|
27
src/lib/components/LinkButton.svelte
Normal file
27
src/lib/components/LinkButton.svelte
Normal file
|
@ -0,0 +1,27 @@
|
|||
<script>
|
||||
let { children, class: className, href = $bindable(""), fill = false} = $props();
|
||||
</script>
|
||||
|
||||
<a class={className} style:width={fill ? "100%" : "auto"} {href}>
|
||||
{@render children?.()}
|
||||
</a>
|
||||
|
||||
<style>
|
||||
a {
|
||||
aspect-ratio: 1 / 1;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
place-content: center;
|
||||
height: 40px;
|
||||
padding: 10px;
|
||||
|
||||
background-color: #4b5563;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: #434b58;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import useSettings from '$lib/useSettings.svelte.js';
|
||||
const settings = useSettings();
|
||||
import {limitImageHeight} from '$lib/stores.js';
|
||||
import {faCaretDown, faCaretUp, faCopy, faHeart, faStar} from "@fortawesome/free-solid-svg-icons";
|
||||
import {Fa} from "svelte-fa";
|
||||
|
||||
/**
|
||||
* @typedef {Object} Props
|
||||
|
@ -13,7 +14,7 @@
|
|||
</script>
|
||||
|
||||
{#if post?.preview?.has}
|
||||
<img class:max-h-dvh={settings.limitImageHeight} class={className} src={post.preview.url} alt={`Post ${post.id}`} />
|
||||
<img style:max-height={$limitImageHeight ? '100vh' : 'none'} class={className} src={post.preview.url} alt={`Post ${post.id}`} />
|
||||
{:else}
|
||||
{#if ['webm', 'mp4'].includes(post.file.ext)}
|
||||
<video class={className} controls playsinline onclick={async e => {
|
||||
|
@ -29,3 +30,72 @@
|
|||
}}/>
|
||||
{/if}
|
||||
{/if}
|
||||
<section class="post-info-bar">
|
||||
<div class="post-info-bar-left">
|
||||
<button class="btn-favorited">
|
||||
<span class:accent-yellow-50={!post.is_favorited} class:accent-yellow-500={post.is_favorited}>
|
||||
<Fa size={'1.5x'} icon={faHeart} />
|
||||
</span>
|
||||
|
||||
<span>
|
||||
{post.fav_count}
|
||||
</span>
|
||||
</button>
|
||||
<button class="btn-score">
|
||||
<span class="text-secondary-500">
|
||||
<Fa size={'1.5x'} icon={faStar} />
|
||||
</span>
|
||||
|
||||
{post.score.total}
|
||||
</button>
|
||||
<button class="btn-like">
|
||||
<Fa size={'1.5x'} icon={faCaretUp} />
|
||||
</button>
|
||||
<button class="btn-dislike">
|
||||
<Fa size={'1.5x'} icon={faCaretDown} />
|
||||
</button>
|
||||
</div>
|
||||
<div class="post-info-bar-right">
|
||||
<button class="btn-url"
|
||||
onclick={async () => {await navigator.clipboard.writeText(`https://e621.net/posts/${post.id}`)}}>
|
||||
<Fa size={'1.5x'} icon={faCopy} />
|
||||
</button>
|
||||
<button class="btn-id">
|
||||
ID: {post.id}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
video, img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.post-info-bar {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: space-between;
|
||||
|
||||
color: white;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.btn-like, .btn-dislike, .btn-favorited, .btn-id, .btn-score, .btn-url {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
color: black;
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.post-info-bar-left, .post-info-bar-right {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
gap: 2px;
|
||||
}
|
||||
</style>
|
|
@ -1,15 +1,19 @@
|
|||
import AppSettings from './AppSettings.svelte';
|
||||
import BackButton from './BackButton.svelte';
|
||||
import Button from './Button.svelte';
|
||||
import Chips from './Chips.svelte';
|
||||
import GlobalSearch from './GlobalSearch.svelte';
|
||||
import ImageGrid from './ImageGrid.svelte';
|
||||
import PostMedia from './PostMedia.svelte';
|
||||
import Header from './Header.svelte';
|
||||
import ImageGrid from './ImageGrid.svelte';
|
||||
import LinkButton from './LinkButton.svelte';
|
||||
import PostMedia from './PostMedia.svelte';
|
||||
|
||||
export {
|
||||
AppSettings,
|
||||
BackButton,
|
||||
Button,
|
||||
Chips,
|
||||
GlobalSearch,
|
||||
ImageGrid,
|
||||
PostMedia,
|
||||
Header,
|
||||
ImageGrid,
|
||||
LinkButton,
|
||||
PostMedia,
|
||||
}
|
|
@ -1,4 +1,22 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
function localStorageStore(storageKey, defaultValue = null) {
|
||||
if (!browser) return defaultValue;
|
||||
let value = window.localStorage.getItem(storageKey);
|
||||
if (value === null) {
|
||||
if (defaultValue !== null) window.localStorage.setItem(storageKey, defaultValue);
|
||||
value = defaultValue;
|
||||
}
|
||||
return writable(value)
|
||||
}
|
||||
|
||||
export const postSearchResults = writable(null);
|
||||
export const loading = writable(false);
|
||||
export const lastSearch = localStorageStore('lastSearch', '');
|
||||
export const showSearch = writable(true);
|
||||
export const imageGridColumns = localStorageStore('gridColumns', 6);
|
||||
export const postGridPreviewQuality = localStorageStore('gridPreviewQuality', 'preview');
|
||||
export const blacklist = localStorageStore('blacklist', ['gore', 'scat'])
|
||||
|
||||
export const limitImageHeight = localStorageStore('limitImageHeight', false)
|
|
@ -1,52 +0,0 @@
|
|||
import {onMount} from "svelte";
|
||||
|
||||
export default function useSettings() {
|
||||
let lastSearch = $state(null),
|
||||
imageGridColumns = $state(6),
|
||||
limitImageHeight = $state(true),
|
||||
postGridPreviewQuality = $state('preview'),
|
||||
blacklist = $state([
|
||||
'gore',
|
||||
'scat',
|
||||
'watersports',
|
||||
'young',
|
||||
'loli',
|
||||
'shota',
|
||||
]);
|
||||
|
||||
onMount(() => {
|
||||
lastSearch = localStorage.getItem('lastSearch') ?? lastSearch;
|
||||
imageGridColumns = localStorage.getItem('gridColumns') ?? imageGridColumns;
|
||||
limitImageHeight = localStorage.getItem('limitImageHeight') ?? limitImageHeight;
|
||||
postGridPreviewQuality = localStorage.getItem('gridPreviewQuality') ?? postGridPreviewQuality;
|
||||
blacklist = localStorage.getItem('blacklist') ?? blacklist;
|
||||
})
|
||||
|
||||
return {
|
||||
get lastSearch() {return lastSearch},
|
||||
get imageGridColumns() {return imageGridColumns},
|
||||
get postGridPreviewQuality() {return postGridPreviewQuality},
|
||||
get blacklist() {return blacklist},
|
||||
get limitImageHeight() {return limitImageHeight},
|
||||
set lastSearch(val) {
|
||||
lastSearch = val;
|
||||
localStorage.setItem('lastSearch', val);
|
||||
},
|
||||
set imageGridColumns(val) {
|
||||
imageGridColumns = val;
|
||||
localStorage.setItem('gridColumns', val);
|
||||
},
|
||||
set postGridPreviewQuality(val) {
|
||||
postGridPreviewQuality = val
|
||||
localStorage.setItem('gridPreviewQuality', val);
|
||||
},
|
||||
set blacklist(val) {
|
||||
blacklist = val
|
||||
localStorage.setItem('blacklist', val);
|
||||
},
|
||||
set limitImageHeight(val) {
|
||||
limitImageHeight = val
|
||||
localStorage.setItem('limitImageHeight', val);
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
import '../app.pcss';
|
||||
import { loading } from '$lib/stores.js';
|
||||
import { Fa } from 'svelte-fa';
|
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
||||
|
@ -7,16 +6,59 @@
|
|||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<div class="bg-gray-200">
|
||||
{@render children?.()}
|
||||
{#if $loading}
|
||||
<div class="absolute bottom-4 right-4 spin">
|
||||
{@render children?.()}
|
||||
{#if $loading}
|
||||
<div class="loading spin">
|
||||
<Fa size={'2x'} icon={faSpinner}></Fa>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
:global(*) {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:global(body) {
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #9ca3af;
|
||||
}
|
||||
|
||||
:global(h1, h2, h3, h4, h5, h6) {
|
||||
font: inherit;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
:global(p) {
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:global(h1) {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
:global(input) {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0 4px;
|
||||
min-height: 40px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
:global(button) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.loading {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.spin {
|
||||
animation: spin 2s infinite;
|
||||
}
|
||||
|
|
|
@ -2,16 +2,14 @@
|
|||
import { Header, ImageGrid } from '$lib/components';
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import PostList from '$lib/PostList';
|
||||
import useSettings from "$lib/useSettings.svelte.js";
|
||||
import {onMount} from "svelte";
|
||||
import {loading, postSearchResults} from "$lib/stores.js";
|
||||
import {onMount} from 'svelte';
|
||||
import {loading, postSearchResults, blacklist, lastSearch} from '$lib/stores.js';
|
||||
|
||||
const settings = useSettings();
|
||||
|
||||
function onSearch(val = '', explicitRefresh = false) {
|
||||
loading.set(true);
|
||||
invoke('get_posts', { query: val }).then((resPosts) => {
|
||||
postSearchResults.set(new PostList(resPosts).filterPosts(settings.blacklist).getPosts())
|
||||
postSearchResults.set(new PostList(resPosts).filterPosts($blacklist).getPosts())
|
||||
loading.set(false)
|
||||
});
|
||||
}
|
||||
|
@ -24,18 +22,45 @@
|
|||
}
|
||||
|
||||
if ($postSearchResults === null) {
|
||||
onSearch(settings.lastSearch, true);
|
||||
onSearch($lastSearch, true);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Header onsubmit={(val = '', explicitRefresh = false) => {onSearch(val, explicitRefresh);}}></Header>
|
||||
|
||||
<div class="container">
|
||||
<Header gridArea="header" onsubmit={(val = '', explicitRefresh = false) => {onSearch(val, explicitRefresh);}}></Header>
|
||||
<main>
|
||||
<div class="overflow-scroll p-2">
|
||||
{#if $postSearchResults}
|
||||
{#if !$loading}
|
||||
{#if $postSearchResults && $postSearchResults.length > 0}
|
||||
<ImageGrid posts={$postSearchResults} />
|
||||
{:else}
|
||||
<p>No search results found</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
padding: 0 10px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-areas: "header header header" "main main main";
|
||||
}
|
||||
|
||||
aside {
|
||||
grid-area: aside;
|
||||
}
|
||||
|
||||
main {
|
||||
grid-area: main;
|
||||
}
|
||||
|
||||
@media (min-width: 1210px) {
|
||||
.container {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,4 @@
|
|||
<script>
|
||||
import { faStar, faCaretUp, faCaretDown, faHeart, faCopy } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Fa } from 'svelte-fa';
|
||||
import { PostMedia, Header } from '$lib/components';
|
||||
import { onMount } from 'svelte';
|
||||
import { TAG_TYPES } from '$lib/Tags';
|
||||
|
@ -19,77 +17,80 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div class="grid [grid-template-areas:'header_header_header''main_main_aside']">
|
||||
<Header class="[grid-area:header]" back={2} showSearch={false} onsubmit={(val = '') => {
|
||||
<div class="container">
|
||||
<Header back={2} showSearch={false} onsubmit={(val = '') => {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.set('search', val);
|
||||
goto("/?" + queryParams.toString())
|
||||
}}></Header>
|
||||
|
||||
<main class="[grid-area:main]">
|
||||
<main id="container-main">
|
||||
{#if post}
|
||||
<div class="flex flex-col">
|
||||
<PostMedia class="max-h-[95vh] w-auto" {post} />
|
||||
<section class="flex flex-row justify-between bg-black">
|
||||
<div class="flex flex-row px-2 gap-2">
|
||||
<button class="py-2 px-3 flex flex-row place-items-center gap-1 text-white hover:text-black hover:bg-white">
|
||||
<span class:accent-yellow-50={!post.is_favorited} class:accent-yellow-500={post.is_favorited}>
|
||||
<Fa size={'1.5x'} icon={faHeart} />
|
||||
</span>
|
||||
|
||||
<span>
|
||||
{post.fav_count}
|
||||
</span>
|
||||
</button>
|
||||
<button class="py-2 px-3 flex flex-row place-items-center text-white hover:text-black hover:bg-white">
|
||||
<span class="text-secondary-500">
|
||||
<Fa size={'1.5x'} icon={faStar} />
|
||||
</span>
|
||||
|
||||
{post.score.total}
|
||||
</button>
|
||||
<button class="py-2 px-3 flex flex-row place-items-center text-white hover:text-black hover:bg-white">
|
||||
<Fa size={'1.5x'} icon={faCaretUp} />
|
||||
</button>
|
||||
<button class="py-2 px-3 flex flex-row place-items-center text-white hover:text-black hover:bg-white">
|
||||
<Fa size={'1.5x'} icon={faCaretDown} />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-row px-2 gap-2">
|
||||
<button class="py-2 px-3 flex flex-row place-items-center text-white hover:text-black hover:bg-white"
|
||||
onclick={async () => {await navigator.clipboard.writeText(`https://e621.net/posts/${post.id}`)}}>
|
||||
<Fa size={'1.5x'} icon={faCopy} />
|
||||
</button>
|
||||
<button class="py-2 px-3 flex flex-row place-items-center text-white hover:text-black hover:bg-white">
|
||||
ID: {post.id}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="card-footer py-4 px-2">
|
||||
<h3 class="font-bold mb-2" class:line-through={post.description === ''} >Description</h3>
|
||||
<p class="text-wrap">{post.description}</p>
|
||||
<!--<pre>{JSON.stringify(post, null, 4)}</pre>-->
|
||||
<div class="post-main">
|
||||
<PostMedia {post} />
|
||||
<footer id="post-footer">
|
||||
<h3 style:text-decoration={post.description === '' ? 'strike-trough' : 'none' } >Description</h3>
|
||||
<p>{post.description}</p>
|
||||
</footer>
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<aside class="[grid-area:aside]">
|
||||
<aside id="container-aside">
|
||||
{#if post}
|
||||
<div class="p-2">
|
||||
{#each tagTypes as [name, label]}
|
||||
{#if post.tags[name].length > 0}
|
||||
<div class="mb-2">
|
||||
<p class="font-bold">{label}</p>
|
||||
<div class="tags-type-wrapper">
|
||||
<p class="tag-type">{label}</p>
|
||||
{#each post.tags[name] as tag}
|
||||
<p>{tag}</p>
|
||||
<p class="tag">{tag}</p>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</aside>
|
||||
|
||||
<footer id="container-footer">
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<style lang="postcss"></style>
|
||||
<style>
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-areas: "header header header" "aside main main" "footer footer footer";
|
||||
grid-gap: 20px;
|
||||
}
|
||||
|
||||
.tag-type {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.post-main {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
#container-main {
|
||||
grid-area: main;
|
||||
}
|
||||
|
||||
#container-aside {
|
||||
min-width: 300px;
|
||||
grid-area: aside;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#container-footer {
|
||||
grid-area: footer;
|
||||
}
|
||||
|
||||
#post-footer {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
<script>
|
||||
import {goto} from "$app/navigation";
|
||||
import {Header} from "$lib/components/index.js";
|
||||
</script>
|
||||
|
||||
<div class="grid [grid-template-areas:'header_header_header''main_main_aside']">
|
||||
<Header class="[grid-area:header]" back={2} showSearch={false} onsubmit={(val = '') => {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.set('search', val);
|
||||
goto("/?" + queryParams.toString())
|
||||
}}></Header>
|
||||
</div>
|
35
src/routes/settings/SettingsNav.svelte
Normal file
35
src/routes/settings/SettingsNav.svelte
Normal file
|
@ -0,0 +1,35 @@
|
|||
<script>
|
||||
import {LinkButton} from "$lib/components";
|
||||
</script>
|
||||
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<LinkButton href="/settings/main" fill={true}>
|
||||
Main
|
||||
</LinkButton>
|
||||
</li>
|
||||
<li>
|
||||
<LinkButton href="/settings/display" fill={true}>
|
||||
Display
|
||||
</LinkButton>
|
||||
</li>
|
||||
<li>
|
||||
<LinkButton href="/settings/blacklist" fill={true}>
|
||||
Blacklist
|
||||
</LinkButton>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
ul {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
li {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
48
src/routes/settings/blacklist/+page.svelte
Normal file
48
src/routes/settings/blacklist/+page.svelte
Normal file
|
@ -0,0 +1,48 @@
|
|||
<script>
|
||||
import {goto} from '$app/navigation';
|
||||
import {Chips, Header} from '$lib/components';
|
||||
import SettingsNav from '../SettingsNav.svelte';
|
||||
import {blacklist} from '$lib/stores.js';
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<Header gridArea="header" back={2} showSearch={false} onsubmit={(val = '') => {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.set('search', val);
|
||||
goto("/?" + queryParams.toString());
|
||||
}}></Header>
|
||||
<aside>
|
||||
<SettingsNav></SettingsNav>
|
||||
</aside>
|
||||
<main>
|
||||
{JSON.stringify($blacklist)}
|
||||
<!--<Chips bind:values={$blacklist} onAdd={val => {
|
||||
$blacklist.push(val);
|
||||
}}></Chips>-->
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
padding: 0 10px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-areas: "header header header" "aside main main";
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
aside {
|
||||
grid-area: aside;
|
||||
}
|
||||
|
||||
main {
|
||||
grid-area: main;
|
||||
}
|
||||
|
||||
@media (min-width: 1210px) {
|
||||
.container {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
43
src/routes/settings/main/+page.svelte
Normal file
43
src/routes/settings/main/+page.svelte
Normal file
|
@ -0,0 +1,43 @@
|
|||
<script>
|
||||
import {goto} from "$app/navigation";
|
||||
import {Header, Chips} from "$lib/components";
|
||||
import SettingsNav from "../SettingsNav.svelte";
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<Header gridArea="header" back={2} showSearch={false} onsubmit={(val = '') => {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.set('search', val);
|
||||
goto("/?" + queryParams.toString());
|
||||
}}></Header>
|
||||
<aside>
|
||||
<SettingsNav></SettingsNav>
|
||||
</aside>
|
||||
<main>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
padding: 0 10px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-areas: "header header header" "aside main main";
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
aside {
|
||||
grid-area: aside;
|
||||
}
|
||||
|
||||
main {
|
||||
grid-area: main;
|
||||
}
|
||||
|
||||
@media (min-width: 1210px) {
|
||||
.container {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in a new issue