WIP design and UI features
This commit is contained in:
parent
b07210507e
commit
048c8f1380
13 changed files with 214 additions and 105 deletions
14
package.json
14
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
31
src/lib/PostList.js
Normal 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
9
src/lib/Tags.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
export const TAG_TYPES = {
|
||||
artist: 'Artist',
|
||||
character: 'Character',
|
||||
copyright: 'Copyright',
|
||||
general: 'General',
|
||||
lore: 'Lore',
|
||||
meta: 'Meta',
|
||||
invalid: 'Invalid'
|
||||
};
|
|
@ -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>
|
|
@ -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}
|
|
@ -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>
|
||||
|
|
|
@ -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
14
src/lib/settings.js
Normal 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);
|
|
@ -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);
|
|
@ -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">
|
||||
{#if $showSearch}
|
||||
<AppBar>
|
||||
<GlobalSearch />
|
||||
</AppBar>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="sidebarLeft">
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
|
@ -1,49 +1,60 @@
|
|||
<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">
|
||||
{#if $showSearch}
|
||||
<AppBar>
|
||||
<GlobalSearch />
|
||||
</AppBar>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="sidebarRight">
|
||||
{#if post}
|
||||
<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}
|
||||
{#each post.tags[name] as tag}
|
||||
<p>{tag}</p>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="sidebarLeft">
|
||||
|
@ -54,17 +65,19 @@
|
|||
<SearchAppRailAnchor />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="trail">
|
||||
<AppRailAnchor on:click={() => {}}>
|
||||
<div class="flex flex-col gap-2 place-content-center">
|
||||
{#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} />
|
||||
<p>Copy link</p>
|
||||
</div>
|
||||
Copy link
|
||||
</button>
|
||||
</AppRailAnchor>
|
||||
{/if}
|
||||
<AppRailAnchor on:click={() => drawerStore.open()}>
|
||||
<div class="flex flex-col gap-2 place-content-center">
|
||||
<button class="w-full flex flex-col gap-2 place-content-center place-items-center">
|
||||
<Fa size={'2x'} icon={faGear} />
|
||||
<p>Settings</p>
|
||||
</div>
|
||||
Settings
|
||||
</button>
|
||||
</AppRailAnchor>
|
||||
</svelte:fragment>
|
||||
</AppRail>
|
||||
|
@ -72,19 +85,21 @@
|
|||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="default">
|
||||
{#if post}
|
||||
<div class="container mx-auto p-2">
|
||||
<div class="flex flex-row place-content-center">
|
||||
<PostMedia post={data.post} />
|
||||
<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">Description</p>
|
||||
<p class="font-bold" class:font-red={post.description === ''}>Description</p>
|
||||
</header>
|
||||
<section class="p-3">
|
||||
<p>{data.post.description}</p>
|
||||
<p>{post.description}</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</AppShell>
|
||||
|
||||
|
|
Loading…
Reference in a new issue