1
0
mirror of https://github.com/koloml/furbooru-tagging-assistant.git synced 2025-12-24 07:12:57 +00:00

Reformatting all svelte templates to use new rules

This commit is contained in:
2025-02-17 02:07:55 +04:00
parent 762652f795
commit efdd9487ad
40 changed files with 1351 additions and 1383 deletions

View File

@@ -1,134 +1,131 @@
<script>
import { run } from 'svelte/legacy';
import { run } from 'svelte/legacy';
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import { storagesCollection } from "$stores/debug";
import { goto } from "$app/navigation";
import { findDeepObject } from "$lib/utils";
import Menu from "$components/ui/menu/Menu.svelte";
import MenuItem from "$components/ui/menu/MenuItem.svelte";
import { storagesCollection } from "$stores/debug";
import { goto } from "$app/navigation";
import { findDeepObject } from "$lib/utils";
/**
* @typedef {Object} Props
* @property {string} storage
* @property {string[]} path
*/
/**
* @typedef {Object} Props
* @property {string} storage
* @property {string[]} path
*/
/** @type {Props} */
let { storage, path } = $props();
/** @type {Props} */
let { storage, path } = $props();
/** @type {Object|null} */
let targetStorage = $state(null);
/** @type {[string, string][]} */
let breadcrumbs = $state([]);
/** @type {Object<string, any>|null} */
let targetObject = $state(null);
let targetPathString = $state('');
/** @type {Object|null} */
let targetStorage = $state(null);
run(() => {
/** @type {[string, string][]} */
let breadcrumbs = $state([]);
/** @type {Object<string, any>|null} */
let targetObject = $state(null);
let targetPathString = $state('');
const builtBreadcrumbs = [];
run(() => {
/** @type {[string, string][]} */
const builtBreadcrumbs = [];
breadcrumbs = path.reduce((resultCrumbs, entry) => {
let entryPath = entry;
breadcrumbs = path.reduce((resultCrumbs, entry) => {
let entryPath = entry;
if (resultCrumbs.length) {
entryPath = resultCrumbs[resultCrumbs.length - 1][1] + "/" + entryPath;
}
if (resultCrumbs.length) {
entryPath = resultCrumbs[resultCrumbs.length - 1][1] + "/" + entryPath;
}
resultCrumbs.push([entry, entryPath]);
resultCrumbs.push([entry, entryPath]);
return resultCrumbs;
}, builtBreadcrumbs);
return resultCrumbs;
}, builtBreadcrumbs);
targetPathString = path.join("/");
targetPathString = path.join("/");
if (targetPathString.length) {
targetPathString += "/";
}
});
if (targetPathString.length) {
targetPathString += "/";
}
});
run(() => {
targetStorage = $storagesCollection[storage];
run(() => {
targetStorage = $storagesCollection[storage];
if (!targetStorage) {
goto("/preferences/debug/storage");
}
});
if (!targetStorage) {
goto("/preferences/debug/storage");
}
});
run(() => {
targetObject = targetStorage
? findDeepObject(targetStorage, path)
: null;
});
run(() => {
targetObject = targetStorage
? findDeepObject(targetStorage, path)
: null;
});
/**
* Helper function to resolve type, including the null.
* @param {*} value Value to resolve type from.
* @return {string} Type of the value, including "null" for null.
*/
function resolveType(value) {
/** @type {string} */
let typeName = typeof value;
/**
* Helper function to resolve type, including the null.
* @param {*} value Value to resolve type from.
* @return {string} Type of the value, including "null" for null.
*/
function resolveType(value) {
/** @type {string} */
let typeName = typeof value;
if (typeName === 'object' && value === null) {
typeName = 'null';
}
return typeName;
if (typeName === 'object' && value === null) {
typeName = 'null';
}
/**
* Helper function to resolve value, including values like null or undefined.
* @param {*} value Value to resolve.
* @return {string} String representation of the value.
*/
function resolveValue(value) {
if (value === null) {
return "null";
}
return typeName;
}
if (value === undefined) {
return "undefined";
}
return value?.toString() ?? '';
/**
* Helper function to resolve value, including values like null or undefined.
* @param {*} value Value to resolve.
* @return {string} String representation of the value.
*/
function resolveValue(value) {
if (value === null) {
return "null";
}
if (value === undefined) {
return "undefined";
}
return value?.toString() ?? '';
}
</script>
<Menu>
<MenuItem href="/preferences/debug/storage" icon="arrow-left">Back</MenuItem>
<hr>
<MenuItem href="/preferences/debug/storage" icon="arrow-left">Back</MenuItem>
<hr>
</Menu>
<p class="path">
<span>/ <a href="/preferences/debug/storage/{storage}">{storage}</a></span>
{#each breadcrumbs as [name, entryPath]}
<span>/ <a href="/preferences/debug/storage/{storage}/{entryPath}/">{name}</a></span>
{/each}
<span>/ <a href="/preferences/debug/storage/{storage}">{storage}</a></span>
{#each breadcrumbs as [name, entryPath]}
<span>/ <a href="/preferences/debug/storage/{storage}/{entryPath}/">{name}</a></span>
{/each}
</p>
{#if targetObject}
<Menu>
<hr>
{#each Object.entries(targetObject) as [key, value]}
{#if targetObject[key] && typeof targetObject[key] === 'object'}
<MenuItem href="/preferences/debug/storage/{storage}/{targetPathString}{key}">
{key}: Object
</MenuItem>
{:else}
<MenuItem>
{key}: {resolveType(targetObject[key])} = {resolveValue(targetObject[key])}
</MenuItem>
{/if}
{/each}
</Menu>
<Menu>
<hr>
{#each Object.entries(targetObject) as [key, value]}
{#if targetObject[key] && typeof targetObject[key] === 'object'}
<MenuItem href="/preferences/debug/storage/{storage}/{targetPathString}{key}">
{key}: Object
</MenuItem>
{:else}
<MenuItem>
{key}: {resolveType(targetObject[key])} = {resolveValue(targetObject[key])}
</MenuItem>
{/if}
{/each}
</Menu>
{/if}
<style lang="scss">
.path {
display: flex;
flex-wrap: wrap;
column-gap: .5em;
}
.path {
display: flex;
flex-wrap: wrap;
column-gap: .5em;
}
</style>

View File

@@ -1,63 +1,63 @@
<script>
import TagsColorContainer from "$components/tags/TagsColorContainer.svelte";
import TagsColorContainer from "$components/tags/TagsColorContainer.svelte";
/**
* @typedef {Object} Props
* @property {import('$entities/TagGroup').default} group
*/
/** @type {Props} */
let { group } = $props();
/**
* @typedef {Object} Props
* @property {import('$entities/TagGroup').default} group
*/
/** @type {Props} */
let { group } = $props();
let sortedTagsList = $derived(group.settings.tags.sort((a, b) => a.localeCompare(b))),
sortedPrefixes = $derived(group.settings.prefixes.sort((a, b) => a.localeCompare(b)));
let sortedTagsList = $derived(group.settings.tags.sort((a, b) => a.localeCompare(b))), sortedPrefixes = $derived(group.settings.prefixes.sort((a, b) => a.localeCompare(b)));
</script>
<div class="block">
<strong>Group Name:</strong>
<div>{group.settings.name}</div>
<strong>Group Name:</strong>
<div>{group.settings.name}</div>
</div>
{#if sortedTagsList.length}
<div class="block">
<strong>Tags:</strong>
<TagsColorContainer targetCategory="{group.settings.category}">
<div class="tags-list">
{#each sortedTagsList as tagName}
<span class="tag">{tagName}</span>
{/each}
</div>
</TagsColorContainer>
</div>
<div class="block">
<strong>Tags:</strong>
<TagsColorContainer targetCategory="{group.settings.category}">
<div class="tags-list">
{#each sortedTagsList as tagName}
<span class="tag">{tagName}</span>
{/each}
</div>
</TagsColorContainer>
</div>
{/if}
{#if sortedPrefixes.length}
<div class="block">
<strong>Prefixes:</strong>
<TagsColorContainer targetCategory="{group.settings.category}">
<div class="tags-list">
{#each sortedPrefixes as prefixName}
<span class="tag">{prefixName}*</span>
{/each}
</div>
</TagsColorContainer>
</div>
<div class="block">
<strong>Prefixes:</strong>
<TagsColorContainer targetCategory="{group.settings.category}">
<div class="tags-list">
{#each sortedPrefixes as prefixName}
<span class="tag">{prefixName}*</span>
{/each}
</div>
</TagsColorContainer>
</div>
{/if}
<style lang="scss">
.tags-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.tags-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.block + .block {
margin-top: .5em;
.block + .block {
margin-top: .5em;
strong {
display: block;
margin-bottom: .25em;
}
strong {
display: block;
margin-bottom: .25em;
}
}
</style>

View File

@@ -1,42 +1,42 @@
<script>
/**
* @typedef {Object} Props
* @property {import('$entities/MaintenanceProfile').default} profile
*/
/** @type {Props} */
let { profile } = $props();
/**
* @typedef {Object} Props
* @property {import('$entities/MaintenanceProfile').default} profile
*/
const sortedTagsList = profile.settings.tags.sort((a, b) => a.localeCompare(b));
/** @type {Props} */
let { profile } = $props();
const sortedTagsList = profile.settings.tags.sort((a, b) => a.localeCompare(b));
</script>
<div class="block">
<strong>Profile:</strong>
<div>{profile.settings.name}</div>
<strong>Profile:</strong>
<div>{profile.settings.name}</div>
</div>
<div class="block">
<strong>Tags:</strong>
<div class="tags-list">
{#each sortedTagsList as tagName}
<span class="tag">{tagName}</span>
{/each}
</div>
<strong>Tags:</strong>
<div class="tags-list">
{#each sortedTagsList as tagName}
<span class="tag">{tagName}</span>
{/each}
</div>
</div>
<style lang="scss">
.tags-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.tags-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.block + .block {
margin-top: .5em;
.block + .block {
margin-top: .5em;
strong {
display: block;
margin-bottom: .25em;
}
strong {
display: block;
margin-bottom: .25em;
}
}
</style>

View File

@@ -1,32 +1,32 @@
<script>
import { version } from "$app/environment";
import { version } from "$app/environment";
</script>
<footer>
<a href="https://github.com/koloml/furbooru-tagging-assistant/releases/tag/{version}" target="_blank">
v{version}
</a>
<span>, made with ♥ by KoloMl.</span>
<a href="https://github.com/koloml/furbooru-tagging-assistant/releases/tag/{version}" target="_blank">
v{version}
</a>
<span>, made with ♥ by KoloMl.</span>
</footer>
<style lang="scss">
@use '$styles/colors';
@use '$styles/colors';
footer {
display: flex;
width: 100%;
background: colors.$footer;
color: colors.$footer-text;
padding: 0 24px;
font-size: 12px;
line-height: 36px;
footer {
display: flex;
width: 100%;
background: colors.$footer;
color: colors.$footer-text;
padding: 0 24px;
font-size: 12px;
line-height: 36px;
a {
color: inherit;
a {
color: inherit;
&:hover {
text-decoration: underline;
}
}
&:hover {
text-decoration: underline;
}
}
}
</style>

View File

@@ -1,28 +1,28 @@
<header>
<a href="/">Furbooru Tagging Assistant</a>
<a href="/">Furbooru Tagging Assistant</a>
</header>
<style lang="scss">
@use "$styles/colors";
@use "$styles/colors";
header {
background: colors.$header;
padding: 0 24px;
display: flex;
position: sticky;
top: 0;
left: 0;
right: 0;
header {
background: colors.$header;
padding: 0 24px;
display: flex;
position: sticky;
top: 0;
left: 0;
right: 0;
a {
color: colors.$text;
line-height: 36px;
padding: 0 12px;
margin-left: -12px;
a {
color: colors.$text;
line-height: 36px;
padding: 0 12px;
margin-left: -12px;
&:hover {
background: colors.$header-hover-background;
}
}
&:hover {
background: colors.$header-hover-background;
}
}
}
</style>

View File

@@ -1,69 +1,69 @@
<script>
/**
* @typedef {Object} Props
* @property {string} [targetCategory]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { targetCategory = '', children } = $props();
/**
* @typedef {Object} Props
* @property {string} [targetCategory]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { targetCategory = '', children } = $props();
</script>
<div class="tag-color-container tag-color-container--{targetCategory || 'default'}">
{@render children?.()}
{@render children?.()}
</div>
<style lang="scss">
@use '$styles/colors';
@use '$styles/colors';
.tag-color-container:is(:global(.tag-color-container--rating)) :global(.tag) {
background-color: colors.$tag-rating-background;
color: colors.$tag-rating-text;
}
.tag-color-container:is(:global(.tag-color-container--rating)) :global(.tag) {
background-color: colors.$tag-rating-background;
color: colors.$tag-rating-text;
}
.tag-color-container:is(:global(.tag-color-container--spoiler)) :global(.tag) {
background-color: colors.$tag-spoiler-background;
color: colors.$tag-spoiler-text;
}
.tag-color-container:is(:global(.tag-color-container--spoiler)) :global(.tag) {
background-color: colors.$tag-spoiler-background;
color: colors.$tag-spoiler-text;
}
.tag-color-container:is(:global(.tag-color-container--origin)) :global(.tag) {
background-color: colors.$tag-origin-background;
color: colors.$tag-origin-text;
}
.tag-color-container:is(:global(.tag-color-container--origin)) :global(.tag) {
background-color: colors.$tag-origin-background;
color: colors.$tag-origin-text;
}
.tag-color-container:is(:global(.tag-color-container--oc)) :global(.tag) {
background-color: colors.$tag-oc-background;
color: colors.$tag-oc-text;
}
.tag-color-container:is(:global(.tag-color-container--oc)) :global(.tag) {
background-color: colors.$tag-oc-background;
color: colors.$tag-oc-text;
}
.tag-color-container:is(:global(.tag-color-container--error)) :global(.tag) {
background-color: colors.$tag-error-background;
color: colors.$tag-error-text;
}
.tag-color-container:is(:global(.tag-color-container--error)) :global(.tag) {
background-color: colors.$tag-error-background;
color: colors.$tag-error-text;
}
.tag-color-container:is(:global(.tag-color-container--character)) :global(.tag) {
background-color: colors.$tag-character-background;
color: colors.$tag-character-text;
}
.tag-color-container:is(:global(.tag-color-container--character)) :global(.tag) {
background-color: colors.$tag-character-background;
color: colors.$tag-character-text;
}
.tag-color-container:is(:global(.tag-color-container--content-official)) :global(.tag) {
background-color: colors.$tag-content-official-background;
color: colors.$tag-content-official-text;
}
.tag-color-container:is(:global(.tag-color-container--content-official)) :global(.tag) {
background-color: colors.$tag-content-official-background;
color: colors.$tag-content-official-text;
}
.tag-color-container:is(:global(.tag-color-container--content-fanmade)) :global(.tag) {
background-color: colors.$tag-content-fanmade-background;
color: colors.$tag-content-fanmade-text;
}
.tag-color-container:is(:global(.tag-color-container--content-fanmade)) :global(.tag) {
background-color: colors.$tag-content-fanmade-background;
color: colors.$tag-content-fanmade-text;
}
.tag-color-container:is(:global(.tag-color-container--species)) :global(.tag) {
background-color: colors.$tag-species-background;
color: colors.$tag-species-text;
}
.tag-color-container:is(:global(.tag-color-container--species)) :global(.tag) {
background-color: colors.$tag-species-background;
color: colors.$tag-species-text;
}
.tag-color-container:is(:global(.tag-color-container--body-type)) :global(.tag) {
background-color: colors.$tag-body-type-background;
color: colors.$tag-body-type-text;
}
.tag-color-container:is(:global(.tag-color-container--body-type)) :global(.tag) {
background-color: colors.$tag-body-type-background;
color: colors.$tag-body-type-text;
}
</style>

View File

@@ -1,113 +1,113 @@
<script>
import { run } from 'svelte/legacy';
import { run } from 'svelte/legacy';
/**
* @typedef {Object} Props
* @property {string[]} [tags] - List of tags to edit. Any duplicated tags present in the array will be removed on the first edit.
*/
/** @type {Props} */
let { tags = $bindable([]) } = $props();
/**
* @typedef {Object} Props
* @property {string[]} [tags] - List of tags to edit. Any duplicated tags present in the array will be removed on the first edit.
*/
/** @type {Set<string>} */
let uniqueTags = $state(new Set());
/** @type {Props} */
let { tags = $bindable([]) } = $props();
run(() => {
uniqueTags = new Set(tags);
});
/** @type {Set<string>} */
let uniqueTags = $state(new Set());
/** @type {string} */
let addedTagName = $state('');
run(() => {
uniqueTags = new Set(tags);
});
/**
* Create a callback function to pass into both mouse & keyboard events for tag removal.
* @param {string} tagName
* @return {function(Event)} Callback to pass as event listener.
*/
function createTagRemoveHandler(tagName) {
return event => {
if (event.type === 'click') {
removeTag(tagName);
}
/** @type {string} */
let addedTagName = $state('');
if (event instanceof KeyboardEvent && (event.code === 'Enter' || event.code === 'Space')) {
// To be more comfortable, automatically focus next available tag's remove button in the list.
if (event.currentTarget instanceof HTMLElement) {
const currenTagElement = event.currentTarget.closest('.tag');
const nextTagElement = currenTagElement?.previousElementSibling ?? currenTagElement?.parentElement?.firstElementChild;
const nextRemoveButton = nextTagElement?.querySelector('.remove');
/**
* Create a callback function to pass into both mouse & keyboard events for tag removal.
* @param {string} tagName
* @return {function(Event)} Callback to pass as event listener.
*/
function createTagRemoveHandler(tagName) {
return event => {
if (event.type === 'click') {
removeTag(tagName);
}
if (nextRemoveButton instanceof HTMLElement) {
nextRemoveButton.focus();
}
}
if (event instanceof KeyboardEvent && (event.code === 'Enter' || event.code === 'Space')) {
// To be more comfortable, automatically focus next available tag's remove button in the list.
if (event.currentTarget instanceof HTMLElement) {
const currenTagElement = event.currentTarget.closest('.tag');
const nextTagElement = currenTagElement?.previousElementSibling ?? currenTagElement?.parentElement?.firstElementChild;
const nextRemoveButton = nextTagElement?.querySelector('.remove');
removeTag(tagName);
}
}
}
/**
* @param {string} tagName
*/
function removeTag(tagName) {
uniqueTags.delete(tagName);
tags = Array.from(uniqueTags);
}
/**
* @param {string} tagName
*/
function addTag(tagName) {
uniqueTags.add(tagName);
tags = Array.from(uniqueTags);
}
/**
* Handle adding new tags to the list or removing them when backspace is pressed.
*
* Additional note: For some reason, mobile Chrome breaks the usual behaviour inside extension. `code` is becoming
* empty, while usually it should contain proper button code.
*
* @param {KeyboardEvent} event
*/
function handleKeyPresses(event) {
if ((event.code === 'Enter' || event.key === 'Enter') && addedTagName.length) {
addTag(addedTagName)
addedTagName = '';
if (nextRemoveButton instanceof HTMLElement) {
nextRemoveButton.focus();
}
}
if ((event.code === 'Backspace' || event.key === 'Backspace') && !addedTagName.length && tags?.length) {
removeTag(tags[tags.length - 1]);
}
removeTag(tagName);
}
}
}
/**
* @param {string} tagName
*/
function removeTag(tagName) {
uniqueTags.delete(tagName);
tags = Array.from(uniqueTags);
}
/**
* @param {string} tagName
*/
function addTag(tagName) {
uniqueTags.add(tagName);
tags = Array.from(uniqueTags);
}
/**
* Handle adding new tags to the list or removing them when backspace is pressed.
*
* Additional note: For some reason, mobile Chrome breaks the usual behaviour inside extension. `code` is becoming
* empty, while usually it should contain proper button code.
*
* @param {KeyboardEvent} event
*/
function handleKeyPresses(event) {
if ((event.code === 'Enter' || event.key === 'Enter') && addedTagName.length) {
addTag(addedTagName)
addedTagName = '';
}
if ((event.code === 'Backspace' || event.key === 'Backspace') && !addedTagName.length && tags?.length) {
removeTag(tags[tags.length - 1]);
}
}
</script>
<div class="tags-editor">
{#each uniqueTags.values() as tagName}
<div class="tag">
{tagName}
<span class="remove" onclick={createTagRemoveHandler(tagName)}
onkeydown={createTagRemoveHandler(tagName)}
role="button" tabindex="0">x</span>
</div>
{/each}
<input type="text"
bind:value={addedTagName}
onkeydown={handleKeyPresses}
autocomplete="off"
autocapitalize="none"/>
{#each uniqueTags.values() as tagName}
<div class="tag">
{tagName}
<span class="remove" onclick={createTagRemoveHandler(tagName)}
onkeydown={createTagRemoveHandler(tagName)}
role="button" tabindex="0">x</span>
</div>
{/each}
<input autocapitalize="none"
autocomplete="off"
bind:value={addedTagName}
onkeydown={handleKeyPresses}
type="text"/>
</div>
<style lang="scss">
.tags-editor {
display: flex;
flex-wrap: wrap;
gap: 6px;
.tags-editor {
display: flex;
flex-wrap: wrap;
gap: 6px;
input {
width: 100%;
}
input {
width: 100%;
}
}
</style>

View File

@@ -1,19 +1,18 @@
<script>
/**
* @typedef {Object} Props
* @property {string|undefined} [name]
* @property {boolean} checked
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { name = undefined, checked = $bindable(), children } = $props();
/**
* @typedef {Object} Props
* @property {string|undefined} [name]
* @property {boolean} checked
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { name = undefined, checked = $bindable(), children } = $props();
</script>
<input type="checkbox" {name} bind:checked={checked}>
<input bind:checked={checked} {name} type="checkbox">
<span>
{@render children?.()}
</span>

View File

@@ -1,19 +1,19 @@
<script lang="ts">
interface Props {
children?: import('svelte').Snippet;
}
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
let { children }: Props = $props();
</script>
<form>
{@render children?.()}
{@render children?.()}
</form>
<style lang="scss">
form {
display: grid;
grid-template-columns: 1fr;
gap: 6px;
}
form {
display: grid;
grid-template-columns: 1fr;
gap: 6px;
}
</style>

View File

@@ -1,34 +1,34 @@
<script>
/**
* @typedef {Object} Props
* @property {string|undefined} [label]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { label = undefined, children } = $props();
/**
* @typedef {Object} Props
* @property {string|undefined} [label]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { label = undefined, children } = $props();
</script>
<label class="control">
{#if label}
<div class="label">{label}</div>
{/if}
{@render children?.()}
{#if label}
<div class="label">{label}</div>
{/if}
{@render children?.()}
</label>
<style lang="scss">
.label {
margin-bottom: .5em;
}
.label {
margin-bottom: .5em;
}
.control {
padding: 5px 0;
.control {
padding: 5px 0;
:global(textarea) {
width: 100%;
resize: vertical;
}
:global(textarea) {
width: 100%;
resize: vertical;
}
}
</style>

View File

@@ -1,49 +1,44 @@
<script>
/**
* @typedef {Object} Props
* @property {string[]|Record<string, string>} [options]
* @property {string|undefined} [name]
* @property {string|undefined} [id]
* @property {string|undefined} [value]
*/
/**
* @typedef {Object} Props
* @property {string[]|Record<string, string>} [options]
* @property {string|undefined} [name]
* @property {string|undefined} [id]
* @property {string|undefined} [value]
*/
/** @type {Props} */
let {
options = [],
name = undefined,
id = undefined,
value = $bindable(undefined)
} = $props();
/** @type {Props} */
let {
options = [],
name = undefined,
id = undefined,
value = $bindable(undefined)
} = $props();
/** @type {Record<string, string>} */
const optionPairs = $state({});
/** @type {Record<string, string>} */
const optionPairs = $state({});
if (Array.isArray(options)) {
for (let option of options) {
optionPairs[option] = option;
}
} else if (options && typeof options === 'object') {
Object.keys(options).forEach((key) => {
optionPairs[key] = options[key];
})
if (Array.isArray(options)) {
for (let option of options) {
optionPairs[option] = option;
}
} else if (options && typeof options === 'object') {
Object.keys(options).forEach((key) => {
optionPairs[key] = options[key];
})
}
</script>
<select {name} {id} bind:value={value}>
{#each Object.entries(optionPairs) as [value, label]}
<option {value}>{label}</option>
{/each}
<select bind:value={value} {id} {name}>
{#each Object.entries(optionPairs) as [value, label]}
<option {value}>{label}</option>
{/each}
</select>
<style lang="scss">
select {
width: 100%;
}
select {
width: 100%;
}
</style>

View File

@@ -1,86 +1,86 @@
<script>
import SelectField from "$components/ui/forms/SelectField.svelte";
import { categories } from "$lib/booru/tag-categories";
import SelectField from "$components/ui/forms/SelectField.svelte";
import { categories } from "$lib/booru/tag-categories";
/**
* @typedef {Object} Props
* @property {string} [value]
*/
/** @type {Props} */
let { value = $bindable('') } = $props();
/**
* @typedef {Object} Props
* @property {string} [value]
*/
/** @type {Record<string, string>} */
let tagCategoriesOptions = $state({
'': 'Default'
});
/** @type {Props} */
let { value = $bindable('') } = $props();
tagCategoriesOptions = categories.reduce((options, category) => {
options[category] = category
.replace('-', ' ')
.replace(/(?<=\s|^)\w/g, (matchedCharacter) => matchedCharacter.toUpperCase());
/** @type {Record<string, string>} */
let tagCategoriesOptions = $state({
'': 'Default'
});
return options;
}, tagCategoriesOptions);
tagCategoriesOptions = categories.reduce((options, category) => {
options[category] = category
.replace('-', ' ')
.replace(/(?<=\s|^)\w/g, (matchedCharacter) => matchedCharacter.toUpperCase());
return options;
}, tagCategoriesOptions);
</script>
<SelectField bind:value={value} options={tagCategoriesOptions} name="tag_color"/>
<SelectField bind:value={value} name="tag_color" options={tagCategoriesOptions}/>
<style lang="scss">
@use '$styles/colors';
@use '$styles/colors';
:global(select[name=tag_color]) {
:global(option) {
&:is(:global([value=rating])) {
background-color: colors.$tag-rating-background;
color: colors.$tag-rating-text;
}
:global(select[name=tag_color]) {
:global(option) {
&:is(:global([value=rating])) {
background-color: colors.$tag-rating-background;
color: colors.$tag-rating-text;
}
&:is(:global([value=spoiler])) {
background-color: colors.$tag-spoiler-background;
color: colors.$tag-spoiler-text;
}
&:is(:global([value=spoiler])) {
background-color: colors.$tag-spoiler-background;
color: colors.$tag-spoiler-text;
}
&:is(:global([value=origin])) {
background-color: colors.$tag-origin-background;
color: colors.$tag-origin-text;
}
&:is(:global([value=origin])) {
background-color: colors.$tag-origin-background;
color: colors.$tag-origin-text;
}
&:is(:global([value=oc])) {
background-color: colors.$tag-oc-background;
color: colors.$tag-oc-text;
}
&:is(:global([value=oc])) {
background-color: colors.$tag-oc-background;
color: colors.$tag-oc-text;
}
&:is(:global([value=error])) {
background-color: colors.$tag-error-background;
color: colors.$tag-error-text;
}
&:is(:global([value=error])) {
background-color: colors.$tag-error-background;
color: colors.$tag-error-text;
}
&:is(:global([value=character])) {
background-color: colors.$tag-character-background;
color: colors.$tag-character-text;
}
&:is(:global([value=character])) {
background-color: colors.$tag-character-background;
color: colors.$tag-character-text;
}
&:is(:global([value=content-official])) {
background-color: colors.$tag-content-official-background;
color: colors.$tag-content-official-text;
}
&:is(:global([value=content-official])) {
background-color: colors.$tag-content-official-background;
color: colors.$tag-content-official-text;
}
&:is(:global([value=content-fanmade])) {
background-color: colors.$tag-content-fanmade-background;
color: colors.$tag-content-fanmade-text;
}
&:is(:global([value=content-fanmade])) {
background-color: colors.$tag-content-fanmade-background;
color: colors.$tag-content-fanmade-text;
}
&:is(:global([value=species])) {
background-color: colors.$tag-species-background;
color: colors.$tag-species-text;
}
&:is(:global([value=species])) {
background-color: colors.$tag-species-background;
color: colors.$tag-species-text;
}
&:is(:global([value=body-type])) {
background-color: colors.$tag-body-type-background;
color: colors.$tag-body-type-text;
}
}
&:is(:global([value=body-type])) {
background-color: colors.$tag-body-type-background;
color: colors.$tag-body-type-text;
}
}
}
</style>

View File

@@ -1,24 +1,21 @@
<script>
/**
* @typedef {Object} Props
* @property {string|undefined} [name]
* @property {string|undefined} [placeholder]
* @property {string} [value]
*/
/**
* @typedef {Object} Props
* @property {string|undefined} [name]
* @property {string|undefined} [placeholder]
* @property {string} [value]
*/
/** @type {Props} */
let { name = undefined, placeholder = undefined, value = $bindable('') } = $props();
/** @type {Props} */
let { name = undefined, placeholder = undefined, value = $bindable('') } = $props();
</script>
<input type="text" {name} {placeholder} bind:value={value}>
<input bind:value={value} {name} {placeholder} type="text">
<style lang="scss">
:global(.control) input {
width: 100%;
}
:global(.control) input {
width: 100%;
}
</style>

View File

@@ -1,46 +1,46 @@
<script lang="ts">
interface Props {
children?: import('svelte').Snippet;
}
interface Props {
children?: import('svelte').Snippet;
}
let { children }: Props = $props();
let { children }: Props = $props();
</script>
<nav>
{@render children?.()}
{@render children?.()}
</nav>
<style lang="scss">
@use '$styles/colors';
@use '$styles/colors';
nav {
display: flex;
flex-direction: column;
nav {
display: flex;
flex-direction: column;
& > :global(.menu-item) {
padding: 5px 24px;
}
:global(.menu-item) {
color: colors.$text;
&:hover {
background: colors.$header-mobile-link-hover;
}
}
:global(hr) {
background: colors.$block-border;
margin: .5em 24px;
border: 0;
height: 1px;
}
:global(main) > & {
margin: {
left: -24px;
right: -24px;
}
}
& > :global(.menu-item) {
padding: 5px 24px;
}
:global(.menu-item) {
color: colors.$text;
&:hover {
background: colors.$header-mobile-link-hover;
}
}
:global(hr) {
background: colors.$block-border;
margin: .5em 24px;
border: 0;
height: 1px;
}
:global(main) > & {
margin: {
left: -24px;
right: -24px;
}
}
}
</style>

View File

@@ -1,45 +1,41 @@
<script>
import { createBubbler, stopPropagation } from 'svelte/legacy';
import { createBubbler, stopPropagation } from 'svelte/legacy';
import MenuLink from "$components/ui/menu/MenuItem.svelte";
const bubble = createBubbler();
import MenuLink from "$components/ui/menu/MenuItem.svelte";
const bubble = createBubbler();
/**
* @typedef {Object} Props
* @property {boolean} checked
* @property {string|undefined} [name]
* @property {string|undefined} [value]
* @property {string|null} [href]
* @property {import('svelte').Snippet} [children]
*/
/**
* @typedef {Object} Props
* @property {boolean} checked
* @property {string|undefined} [name]
* @property {string|undefined} [value]
* @property {string|null} [href]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let {
checked = $bindable(),
name = undefined,
value = undefined,
href = null,
children
} = $props();
/** @type {Props} */
let {
checked = $bindable(),
name = undefined,
value = undefined,
href = null,
children
} = $props();
</script>
<MenuLink {href}>
<input type="checkbox" {name} {value} bind:checked={checked} oninput={bubble('input')} onclick={stopPropagation(bubble('click'))}>
{@render children?.()}
<input bind:checked={checked} {name} onclick={stopPropagation(bubble('click'))} oninput={bubble('input')}
type="checkbox"
{value}>
{@render children?.()}
</MenuLink>
<style lang="scss">
:global(.menu-item) input {
width: 16px;
height: 16px;
margin-right: 6px;
flex-shrink: 0;
}
:global(.menu-item) input {
width: 16px;
height: 16px;
margin-right: 6px;
flex-shrink: 0;
}
</style>

View File

@@ -1,50 +1,48 @@
<script>
import { createBubbler } from 'svelte/legacy';
import { createBubbler } from 'svelte/legacy';
const bubble = createBubbler();
const bubble = createBubbler();
/**
* @typedef {Object} Props
* @property {string|null} [href]
* @property {App.IconName|null} [icon]
* @property {App.LinkTarget|undefined} [target]
* @property {import('svelte').Snippet} [children]
*/
/**
* @typedef {Object} Props
* @property {string|null} [href]
* @property {App.IconName|null} [icon]
* @property {App.LinkTarget|undefined} [target]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let {
href = null,
icon = null,
target = undefined,
children
} = $props();
/** @type {Props} */
let {
href = null,
icon = null,
target = undefined,
children
} = $props();
</script>
<svelte:element this="{href ? 'a': 'span'}" class="menu-item" {href} {target} onclick={bubble('click')} role="link" tabindex="0">
{#if icon}
<i class="icon icon-{icon}"></i>
{/if}
{@render children?.()}
<svelte:element class="menu-item" {href} onclick={bubble('click')} role="link" tabindex="0" {target}
this="{href ? 'a': 'span'}">
{#if icon}
<i class="icon icon-{icon}"></i>
{/if}
{@render children?.()}
</svelte:element>
<style lang="scss">
@use '$styles/colors';
@use '$styles/colors';
.menu-item {
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
.menu-item {
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
i {
width: 16px;
height: 16px;
background: colors.$text;
margin-right: 6px;
}
i {
width: 16px;
height: 16px;
background: colors.$text;
margin-right: 6px;
}
}
</style>

View File

@@ -1,45 +1,39 @@
<script>
import { createBubbler, stopPropagation } from 'svelte/legacy';
import { createBubbler, stopPropagation } from 'svelte/legacy';
import MenuLink from "$components/ui/menu/MenuItem.svelte";
const bubble = createBubbler();
import MenuLink from "$components/ui/menu/MenuItem.svelte";
const bubble = createBubbler();
/**
* @typedef {Object} Props
* @property {boolean} checked
* @property {string} name
* @property {string} value
* @property {string|null} [href]
* @property {import('svelte').Snippet} [children]
*/
/**
* @typedef {Object} Props
* @property {boolean} checked
* @property {string} name
* @property {string} value
* @property {string|null} [href]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let {
checked,
name,
value,
href = null,
children
} = $props();
/** @type {Props} */
let {
checked,
name,
value,
href = null,
children
} = $props();
</script>
<MenuLink {href}>
<input type="radio" {name} {value} {checked} oninput={bubble('input')} onclick={stopPropagation(bubble('click'))}>
{@render children?.()}
<input {checked} {name} onclick={stopPropagation(bubble('click'))} oninput={bubble('input')} type="radio" {value}>
{@render children?.()}
</MenuLink>
<style lang="scss">
:global(.menu-item) input {
width: 16px;
height: 16px;
margin-right: 6px;
flex-shrink: 0;
}
:global(.menu-item) input {
width: 16px;
height: 16px;
margin-right: 6px;
flex-shrink: 0;
}
</style>