1
0
Fork 0

WIP design and UI features

This commit is contained in:
Leon Grünewald 2024-03-21 20:54:25 +01:00
parent b07210507e
commit 048c8f1380
13 changed files with 214 additions and 105 deletions

View file

@ -20,18 +20,18 @@
"devDependencies": {
"@skeletonlabs/skeleton": "^2.9.0",
"@skeletonlabs/tw-plugin": "^0.3.1",
"@sveltejs/adapter-auto": "^3.1.1",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.5.4",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@tauri-apps/cli": "^1.5.11",
"autoprefixer": "^10.4.18",
"postcss": "^8.4.35",
"postcss-load-config": "^5.0.3",
"svelte": "^4.2.12",
"svelte-check": "^3.6.7",
"tailwindcss": "^3.4.1",
"typescript": "^5.4.2",
"vite": "^5.1.6"
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"svelte": "^4.2.7",
"svelte-check": "^3.6.0",
"typescript": "^5.0.0",
"vite": "^5.0.3"
}
}

View file

@ -32,16 +32,16 @@ devDependencies:
specifier: ^0.3.1
version: 0.3.1(tailwindcss@3.4.1)
'@sveltejs/adapter-auto':
specifier: ^3.1.1
specifier: ^3.0.0
version: 3.1.1(@sveltejs/kit@2.5.4)
'@sveltejs/adapter-static':
specifier: ^3.0.1
version: 3.0.1(@sveltejs/kit@2.5.4)
'@sveltejs/kit':
specifier: ^2.5.4
specifier: ^2.0.0
version: 2.5.4(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.6)
'@sveltejs/vite-plugin-svelte':
specifier: ^3.0.2
specifier: ^3.0.0
version: 3.0.2(svelte@4.2.12)(vite@5.1.6)
'@tauri-apps/cli':
specifier: ^1.5.11
@ -56,19 +56,19 @@ devDependencies:
specifier: ^5.0.3
version: 5.0.3(postcss@8.4.35)
svelte:
specifier: ^4.2.12
specifier: ^4.2.7
version: 4.2.12
svelte-check:
specifier: ^3.6.7
specifier: ^3.6.0
version: 3.6.7(postcss-load-config@5.0.3)(postcss@8.4.35)(svelte@4.2.12)
tailwindcss:
specifier: ^3.4.1
version: 3.4.1
typescript:
specifier: ^5.4.2
specifier: ^5.0.0
version: 5.4.2
vite:
specifier: ^5.1.6
specifier: ^5.0.3
version: 5.1.6
packages:

31
src/lib/PostList.js Normal file
View file

@ -0,0 +1,31 @@
import { TAG_TYPES } from '$lib/Tags';
export default class PostList {
posts = [];
constructor(posts) {
this.posts = posts;
}
filterPosts(tags = []) {
const filteredPosts = [];
postLoop: for (const post of this.posts) {
for (const tagType of Object.keys(TAG_TYPES)) {
if (post.tags[tagType].some((postTag) => tags.includes(postTag))) {
console.info('Skipping post', post);
continue postLoop;
}
}
filteredPosts.push(post);
}
this.posts = filteredPosts;
return this;
}
getPosts() {
return this.posts;
}
}

9
src/lib/Tags.js Normal file
View file

@ -0,0 +1,9 @@
export const TAG_TYPES = {
artist: 'Artist',
character: 'Character',
copyright: 'Copyright',
general: 'General',
lore: 'Lore',
meta: 'Meta',
invalid: 'Invalid'
};

View file

@ -1,21 +1,29 @@
<script>
import {RangeSlider, Tab, TabGroup} from "@skeletonlabs/skeleton";
import {imageGridColumns, postGridPreviewQuality} from "$lib/stores.js";
import { RangeSlider, Tab, TabGroup, InputChip, SlideToggle } from '@skeletonlabs/skeleton';
import { imageGridColumns, postGridPreviewQuality, blacklist, limitImageHeight } from '$lib/settings';
let tabPos = 0;
let showApikey = false;
let apikey = '';
</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}>Search Grid</Tab>
<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>
<svelte:fragment slot="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" checked={$limitImageHeight}>Limit image height</SlideToggle>
<h3 class="h3 text-dark-token font-bold">Grid</h3>
<div class="basis-1/2">
<RangeSlider name="grid-columns" min={1} max={12} bind:value={$imageGridColumns} ticked={true}>
<div class="flex flex-grow justify-between items-center">
<div class="text-dark-token font-bold">Grid Columns</div>
<RangeSlider accent="text-dark-token" name="grid-columns" min={1} max={12} bind:value={$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">{$imageGridColumns} / {12}</div>
</div>
</RangeSlider>
@ -31,6 +39,40 @@
</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={$blacklist} />
</div>
</div>
{/if}
</svelte:fragment>
</TabGroup>
</div>

View file

@ -1,18 +1,19 @@
<script>
import { showSearch, lastSearch, loading } from '$lib/stores.js';
import { showSearch, loading } from '$lib/stores';
import { lastSearch } from '$lib/settings';
import { goto } from '$app/navigation';
let searchValue = $lastSearch;
</script>
{#if $showSearch}
<form class="w-80 pt-2 px-2 flex flex-row gap-2"
<form class="flex flex-row gap-2"
on:submit|preventDefault={() => {
$loading = true;
goto('/')
lastSearch.set(searchValue);
}}>
<input class="input py-1 px-2" type="text" placeholder="Search" bind:value={searchValue} />
<button class="btn btn-md variant-filled" type="submit">Sumbit</button>
<input class="input py-1 px-2 w-96" type="text" placeholder="Search" bind:value={searchValue} />
<button class="btn btn-md variant-filled" type="submit">Submit</button>
</form>
{/if}

View file

@ -1,5 +1,6 @@
<script>
import {postGridPreviewQuality, imageGridColumns, showSearch, loading} from '$lib/stores';
import { showSearch, loading } from '$lib/stores';
import { postGridPreviewQuality, imageGridColumns } from '$lib/settings';
export let posts = [];
</script>

View file

@ -1,17 +1,19 @@
<script>
import { limitImageHeight } from '$lib/settings.js';
export let post = null;
</script>
<div>
{#if post?.preview?.has}
<img class="mb-2" src={post.preview.url} alt={`Post ${post.id}`} />
<img class:h-dvh={$limitImageHeight} src={post.preview.url} alt={`Post ${post.id}`} />
{:else}
{#if ['webm', 'mp4'].includes(post.file.ext)}
<video class="mb-2" controls playsinline>
<video class:h-dvh={$limitImageHeight} controls playsinline>
<source src={post.file.url} />
</video>
{:else}
<img class="mb-2" src={post.file.url} alt={`Post ${post.id}}`} />
<img class:h-dvh={$limitImageHeight} src={post.file.url} alt={`Post ${post.id}}`} />
{/if}
{/if}
</div>

14
src/lib/settings.js Normal file
View file

@ -0,0 +1,14 @@
import { localStorageStore } from '@skeletonlabs/skeleton';
export const lastSearch = localStorageStore('lastSearch', '');
export const imageGridColumns = localStorageStore('gridColumns', 6);
export const postGridPreviewQuality = localStorageStore('gridPreviewQuality', 'preview');
export const blacklist = localStorageStore('blacklist', [
'gore',
'scat',
'watersports',
'young',
'loli',
'shota',
]);
export const limitImageHeight = localStorageStore('limitImageHeight', false);

View file

@ -1,9 +1,5 @@
import { localStorageStore } from '@skeletonlabs/skeleton';
import { writable } from 'svelte/store';
export const lastSearch = localStorageStore('lastSearch', '');
export const imageGridColumns = localStorageStore('gridColumns', 6);
export const postSearchResults = writable(null);
export const postGridPreviewQuality = localStorageStore('gridPreviewQuality', 'preview');
export const loading = writable(false);
export const showSearch = writable(true);

View file

@ -1,30 +1,26 @@
<script>
import { Fa } from 'svelte-fa';
import {GlobalSearch, ImageGrid, SearchAppRailAnchor} from '$lib/components';
import { GlobalSearch, ImageGrid, SearchAppRailAnchor} from '$lib/components';
import { faGear } from '@fortawesome/free-solid-svg-icons';
import { invoke } from '@tauri-apps/api/tauri'
import { lastSearch, postSearchResults, loading } from '$lib/stores';
import { lastSearch, blacklist } from '$lib/settings';
import { postSearchResults, loading, showSearch} from '$lib/stores';
import {
AppBar,
AppRail,
AppRailAnchor,
AppShell,
getDrawerStore
} from '@skeletonlabs/skeleton';
import {onMount} from 'svelte';
import PostList from '$lib/PostList';
const drawerStore = getDrawerStore();
onMount(() => {
$loading = false;
});
function onSearch() {
$loading = true;
invoke('get_posts', { query: $lastSearch }).then((resPosts) => {
postSearchResults.set(resPosts);
requestAnimationFrame(() => {
$loading = false;
});
postSearchResults.set(new PostList(resPosts).filterPosts($blacklist).getPosts());
requestAnimationFrame(() => $loading = false);
});
}
@ -39,7 +35,11 @@
<AppShell>
<svelte:fragment slot="pageHeader">
<GlobalSearch />
{#if $showSearch}
<AppBar>
<GlobalSearch />
</AppBar>
{/if}
</svelte:fragment>
<svelte:fragment slot="sidebarLeft">

View file

@ -5,10 +5,8 @@ export const ssr = false;
/** @type {import('./$types').PageLoad} */
export async function load({ params }) {
// const post = await invoke('get_post', {post_id: params.post_id});
const post = await invoke('get_post', {postId: +params.post_id});
return {
postId: params.post_id,
post
postId: params.post_id
};
}

View file

@ -1,90 +1,105 @@
<script>
import {
AppBar,
AppRail,
AppRailAnchor,
AppShell,
getDrawerStore,
clipboard,
getToastStore
} from '@skeletonlabs/skeleton';
import { faGear, faCopy } from '@fortawesome/free-solid-svg-icons';
import { Fa } from 'svelte-fa';
import { BackAppRailAnchor, GlobalSearch, PostMedia, SearchAppRailAnchor } from '$lib/components';
import {onMount} from 'svelte';
import {loading} from '$lib/stores.js';
import { onMount } from 'svelte';
import { loading, showSearch } from '$lib/stores';
import { limitImageHeight } from '$lib/settings.js';
import { TAG_TYPES } from '$lib/Tags';
import { invoke } from '@tauri-apps/api/tauri';
export let data;
let drawerStore = getDrawerStore();
const tagTypes = Object.entries({
artist: 'Artist',
character: 'Character',
copyright: 'Copyright',
general: 'General',
lore: 'Lore',
meta: 'Meta',
invalid: 'Invalid'
});
const drawerStore = getDrawerStore();
const tagTypes = Object.entries(TAG_TYPES);
const toastStore = getToastStore();
onMount(() => {
/** @type any|null */
let post = null;
onMount(async () => {
$loading = true;
const postRequest = await invoke('get_post', {postId: +data.postId});
$loading = false;
post = postRequest;
});
</script>
<AppShell>
<svelte:fragment slot="pageHeader">
<GlobalSearch />
{#if $showSearch}
<AppBar>
<GlobalSearch />
</AppBar>
{/if}
</svelte:fragment>
<svelte:fragment slot="sidebarRight">
<div class="p-2 h-dvh">
{#each tagTypes as [name, label]}
<div class="mb-2">
<p class="font-bold">{label}</p>
{#each data.post.tags[name] as tag}
<p>{tag}</p>
{/each}
</div>
{/each}
</div>
{#if post}
<div class="p-2 h-dvh">
{#each tagTypes as [name, label]}
<div class="mb-2">
<p class="font-bold">{label}</p>
{#each post.tags[name] as tag}
<p>{tag}</p>
{/each}
</div>
{/each}
</div>
{/if}
</svelte:fragment>
<svelte:fragment slot="sidebarLeft">
<div class="h-dvh">
<AppRail>
<svelte:fragment slot="lead">
<BackAppRailAnchor popCount={2} />
<SearchAppRailAnchor />
</svelte:fragment>
<svelte:fragment slot="trail">
<AppRailAnchor on:click={() => {}}>
<div class="flex flex-col gap-2 place-content-center">
<Fa size={'2x'} icon={faCopy} />
<p>Copy link</p>
</div>
</AppRailAnchor>
<AppRailAnchor on:click={() => drawerStore.open()}>
<div class="flex flex-col gap-2 place-content-center">
<Fa size={'2x'} icon={faGear} />
<p>Settings</p>
</div>
</AppRailAnchor>
</svelte:fragment>
</AppRail>
</div>
<div class="h-dvh">
<AppRail>
<svelte:fragment slot="lead">
<BackAppRailAnchor popCount={2} />
<SearchAppRailAnchor />
</svelte:fragment>
<svelte:fragment slot="trail">
{#if post}
<AppRailAnchor>
<button class="w-full flex flex-col gap-2 place-content-center place-items-center" use:clipboard={`https://e621.net/posts/${post.id}`} on:click={() => toastStore.trigger({message: 'Copied link'})}>
<Fa size={'2x'} icon={faCopy} />
Copy link
</button>
</AppRailAnchor>
{/if}
<AppRailAnchor on:click={() => drawerStore.open()}>
<button class="w-full flex flex-col gap-2 place-content-center place-items-center">
<Fa size={'2x'} icon={faGear} />
Settings
</button>
</AppRailAnchor>
</svelte:fragment>
</AppRail>
</div>
</svelte:fragment>
<svelte:fragment slot="default">
<div class="container mx-auto p-2">
<div class="flex flex-row place-content-center">
<PostMedia post={data.post} />
{#if post}
<div class="container mx-auto p-2">
<div class="flex flex-row place-content-center" >
<PostMedia post={post} />
</div>
<div class="card">
<header class="px-3 pt-2">
<p class="font-bold" class:font-red={post.description === ''}>Description</p>
</header>
<section class="p-3">
<p>{post.description}</p>
</section>
</div>
</div>
<div class="card">
<header class="px-3 pt-2">
<p class="font-bold">Description</p>
</header>
<section class="p-3">
<p>{data.post.description}</p>
</section>
</div>
</div>
{/if}
</svelte:fragment>
</AppShell>