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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
<script lang="ts">
|
||||
import "../styles/popup.scss";
|
||||
import Header from "$components/layout/Header.svelte";
|
||||
import Footer from "$components/layout/Footer.svelte";
|
||||
interface Props {
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
import "../styles/popup.scss";
|
||||
import Header from "$components/layout/Header.svelte";
|
||||
import Footer from "$components/layout/Footer.svelte";
|
||||
|
||||
let { children }: Props = $props();
|
||||
interface Props {
|
||||
children?: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
// Sort of a hack, detect if we rendered in the browser tab or in the popup.
|
||||
// Popup will always should have fixed 320px size, otherwise we consider it opened in the tab.
|
||||
document.body.classList.toggle('is-in-tab', window.innerWidth > 320);
|
||||
let { children }: Props = $props();
|
||||
|
||||
// Sort of a hack, detect if we rendered in the browser tab or in the popup.
|
||||
// Popup will always should have fixed 320px size, otherwise we consider it opened in the tab.
|
||||
document.body.classList.toggle('is-in-tab', window.innerWidth > 320);
|
||||
</script>
|
||||
|
||||
<Header/>
|
||||
<main>
|
||||
{@render children?.()}
|
||||
{@render children?.()}
|
||||
</main>
|
||||
<Footer/>
|
||||
|
||||
<style lang="scss" global>
|
||||
main {
|
||||
padding: .5em 24px;
|
||||
}
|
||||
<style global lang="scss">
|
||||
main {
|
||||
padding: .5em 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { activeProfileStore, maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import MenuCheckboxItem from "$components/ui/menu/MenuCheckboxItem.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { activeProfileStore, maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import MenuCheckboxItem from "$components/ui/menu/MenuCheckboxItem.svelte";
|
||||
|
||||
/** @type {import('$entities/MaintenanceProfile').default|undefined} */
|
||||
let activeProfile = $derived($maintenanceProfiles.find(profile => profile.id === $activeProfileStore));
|
||||
/** @type {import('$entities/MaintenanceProfile').default|undefined} */
|
||||
let activeProfile = $derived($maintenanceProfiles.find(profile => profile.id === $activeProfileStore));
|
||||
|
||||
|
||||
|
||||
function turnOffActiveProfile() {
|
||||
$activeProfileStore = null;
|
||||
}
|
||||
function turnOffActiveProfile() {
|
||||
$activeProfileStore = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
{#if activeProfile}
|
||||
<MenuCheckboxItem checked on:input={turnOffActiveProfile} href="/features/maintenance/{activeProfile.id}">
|
||||
Active Profile: {activeProfile.settings.name}
|
||||
</MenuCheckboxItem>
|
||||
<hr>
|
||||
{/if}
|
||||
<MenuItem href="/features/maintenance">Tagging Profiles</MenuItem>
|
||||
<MenuItem href="/features/groups">Tag Groups</MenuItem>
|
||||
{#if activeProfile}
|
||||
<MenuCheckboxItem checked on:input={turnOffActiveProfile} href="/features/maintenance/{activeProfile.id}">
|
||||
Active Profile: {activeProfile.settings.name}
|
||||
</MenuCheckboxItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences">Preferences</MenuItem>
|
||||
<MenuItem href="/about">About</MenuItem>
|
||||
{/if}
|
||||
<MenuItem href="/features/maintenance">Tagging Profiles</MenuItem>
|
||||
<MenuItem href="/features/groups">Tag Groups</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences">Preferences</MenuItem>
|
||||
<MenuItem href="/about">About</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem icon="arrow-left" href="/">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
<h1>
|
||||
Furbooru Tagging Assistant
|
||||
Furbooru Tagging Assistant
|
||||
</h1>
|
||||
<p>
|
||||
This is a tool made to help tag images on Furbooru more efficiently. It is currently in development and is not yet
|
||||
ready for use, but it still can provide some useful functionality.
|
||||
This is a tool made to help tag images on Furbooru more efficiently. It is currently in development and is not yet
|
||||
ready for use, but it still can provide some useful functionality.
|
||||
</p>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem icon="globe" href="https://furbooru.org" target="_blank">
|
||||
Visit Furbooru
|
||||
</MenuItem>
|
||||
<MenuItem icon="info-circle" href="https://github.com/koloml/furbooru-tagging-assistant" target="_blank">
|
||||
GitHub Repo
|
||||
</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="https://furbooru.org" icon="globe" target="_blank">
|
||||
Visit Furbooru
|
||||
</MenuItem>
|
||||
<MenuItem href="https://github.com/koloml/furbooru-tagging-assistant" icon="info-circle" target="_blank">
|
||||
GitHub Repo
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem href="/">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/maintenance">Tagging Profiles</MenuItem>
|
||||
<MenuItem href="/">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/maintenance">Tagging Profiles</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
<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 { tagGroups } from "$stores/entities/tag-groups";
|
||||
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
/** @type {import('$entities/TagGroup').default[]} */
|
||||
let groups = $state([]);
|
||||
|
||||
/** @type {import('$entities/TagGroup').default[]} */
|
||||
let groups = $state([]);
|
||||
|
||||
run(() => {
|
||||
groups = $tagGroups.sort((a, b) => a.settings.name.localeCompare(b.settings.name));
|
||||
});
|
||||
run(() => {
|
||||
groups = $tagGroups.sort((a, b) => a.settings.name.localeCompare(b.settings.name));
|
||||
});
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem href="/" icon="arrow-left">Back</MenuItem>
|
||||
<MenuItem href="/features/groups/new/edit" icon="plus">Create New</MenuItem>
|
||||
{#if groups.length}
|
||||
<hr>
|
||||
{#each groups as group}
|
||||
<MenuItem href="/features/groups/{group.id}">{group.settings.name}</MenuItem>
|
||||
{/each}
|
||||
{/if}
|
||||
<MenuItem href="/" icon="arrow-left">Back</MenuItem>
|
||||
<MenuItem href="/features/groups/new/edit" icon="plus">Create New</MenuItem>
|
||||
{#if groups.length}
|
||||
<hr>
|
||||
<MenuItem href="/features/groups/import">Import Group</MenuItem>
|
||||
{#each groups as group}
|
||||
<MenuItem href="/features/groups/{group.id}">{group.settings.name}</MenuItem>
|
||||
{/each}
|
||||
{/if}
|
||||
<hr>
|
||||
<MenuItem href="/features/groups/import">Import Group</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
<script>
|
||||
import { run } from 'svelte/legacy';
|
||||
import { run } from 'svelte/legacy';
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import GroupView from "$components/features/GroupView.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import GroupView from "$components/features/GroupView.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
const groupId = $page.params.id;
|
||||
/** @type {import('$entities/TagGroup').default|null} */
|
||||
let group = $state(null);
|
||||
|
||||
const groupId = $page.params.id;
|
||||
/** @type {import('$entities/TagGroup').default|null} */
|
||||
let group = $state(null);
|
||||
if (groupId === 'new') {
|
||||
goto('/features/groups/new/edit');
|
||||
}
|
||||
|
||||
if (groupId === 'new') {
|
||||
goto('/features/groups/new/edit');
|
||||
run(() => {
|
||||
group = $tagGroups.find(group => group.id === groupId) || null;
|
||||
|
||||
if (!group) {
|
||||
console.warn(`Group ${groupId} not found.`);
|
||||
goto('/features/groups');
|
||||
}
|
||||
|
||||
run(() => {
|
||||
group = $tagGroups.find(group => group.id === groupId) || null;
|
||||
|
||||
if (!group) {
|
||||
console.warn(`Group ${groupId} not found.`);
|
||||
goto('/features/groups');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem href="/features/groups" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/groups" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
{#if group}
|
||||
<GroupView {group}/>
|
||||
<GroupView {group}/>
|
||||
{/if}
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem href="/features/groups/{groupId}/edit" icon="wrench">Edit Group</MenuItem>
|
||||
<MenuItem href="/features/groups/{groupId}/export" icon="file-export">Export Group</MenuItem>
|
||||
<MenuItem href="/features/groups/{groupId}/delete" icon="trash">Delete Group</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/groups/{groupId}/edit" icon="wrench">Edit Group</MenuItem>
|
||||
<MenuItem href="/features/groups/{groupId}/export" icon="file-export">Export Group</MenuItem>
|
||||
<MenuItem href="/features/groups/{groupId}/delete" icon="trash">Delete Group</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<script>
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
|
||||
const groupId = $page.params.id;
|
||||
const targetGroup = $tagGroups.find(group => group.id === groupId);
|
||||
const groupId = $page.params.id;
|
||||
const targetGroup = $tagGroups.find(group => group.id === groupId);
|
||||
|
||||
if (!targetGroup) {
|
||||
void goto('/features/groups');
|
||||
}
|
||||
|
||||
async function deleteGroup() {
|
||||
if (!targetGroup) {
|
||||
void goto('/features/groups');
|
||||
console.warn('Attempting to delete the group, but the group is not loaded yet.');
|
||||
return;
|
||||
}
|
||||
|
||||
async function deleteGroup() {
|
||||
if (!targetGroup) {
|
||||
console.warn('Attempting to delete the group, but the group is not loaded yet.');
|
||||
return;
|
||||
}
|
||||
|
||||
await targetGroup.delete();
|
||||
await goto('/features/groups');
|
||||
}
|
||||
await targetGroup.delete();
|
||||
await goto('/features/groups');
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem icon="arrow-left" href="/features/groups/{groupId}">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/groups/{groupId}" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
{#if targetGroup}
|
||||
<p>
|
||||
Do you want to remove group "{targetGroup.settings.name}"? This action is irreversible.
|
||||
</p>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem on:click={deleteGroup}>Yes</MenuItem>
|
||||
<MenuItem href="/features/groups/{groupId}">No</MenuItem>
|
||||
</Menu>
|
||||
<p>
|
||||
Do you want to remove group "{targetGroup.settings.name}"? This action is irreversible.
|
||||
</p>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem on:click={deleteGroup}>Yes</MenuItem>
|
||||
<MenuItem href="/features/groups/{groupId}">No</MenuItem>
|
||||
</Menu>
|
||||
{:else}
|
||||
<p>Loading...</p>
|
||||
<p>Loading...</p>
|
||||
{/if}
|
||||
|
||||
@@ -1,82 +1,82 @@
|
||||
<script>
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import TagsColorContainer from "$components/tags/TagsColorContainer.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import TagCategorySelectField from "$components/ui/forms/TagCategorySelectField.svelte";
|
||||
import TextField from "$components/ui/forms/TextField.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import TagsEditor from "$components/tags/TagsEditor.svelte";
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import TagsColorContainer from "$components/tags/TagsColorContainer.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import TagCategorySelectField from "$components/ui/forms/TagCategorySelectField.svelte";
|
||||
import TextField from "$components/ui/forms/TextField.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import TagsEditor from "$components/tags/TagsEditor.svelte";
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
|
||||
const groupId = $page.params.id;
|
||||
/** @type {TagGroup|null} */
|
||||
let targetGroup = null;
|
||||
const groupId = $page.params.id;
|
||||
/** @type {TagGroup|null} */
|
||||
let targetGroup = null;
|
||||
|
||||
let groupName = $state('');
|
||||
/** @type {string[]} */
|
||||
let tagsList = $state([]);
|
||||
/** @type {string[]} */
|
||||
let prefixesList = $state([]);
|
||||
let tagCategory = $state('');
|
||||
let groupName = $state('');
|
||||
/** @type {string[]} */
|
||||
let tagsList = $state([]);
|
||||
/** @type {string[]} */
|
||||
let prefixesList = $state([]);
|
||||
let tagCategory = $state('');
|
||||
|
||||
if (groupId === 'new') {
|
||||
targetGroup = new TagGroup(crypto.randomUUID(), {});
|
||||
if (groupId === 'new') {
|
||||
targetGroup = new TagGroup(crypto.randomUUID(), {});
|
||||
} else {
|
||||
targetGroup = $tagGroups.find(group => group.id === groupId) || null;
|
||||
|
||||
if (targetGroup) {
|
||||
groupName = targetGroup.settings.name;
|
||||
tagsList = [...targetGroup.settings.tags].sort((a, b) => a.localeCompare(b));
|
||||
prefixesList = [...targetGroup.settings.prefixes].sort((a, b) => a.localeCompare(b));
|
||||
tagCategory = targetGroup.settings.category;
|
||||
} else {
|
||||
targetGroup = $tagGroups.find(group => group.id === groupId) || null;
|
||||
goto('/features/groups');
|
||||
}
|
||||
}
|
||||
|
||||
if (targetGroup) {
|
||||
groupName = targetGroup.settings.name;
|
||||
tagsList = [...targetGroup.settings.tags].sort((a, b) => a.localeCompare(b));
|
||||
prefixesList = [...targetGroup.settings.prefixes].sort((a, b) => a.localeCompare(b));
|
||||
tagCategory = targetGroup.settings.category;
|
||||
} else {
|
||||
goto('/features/groups');
|
||||
}
|
||||
async function saveGroup() {
|
||||
if (!targetGroup) {
|
||||
console.warn('Attempting to save group, but group is not loaded yet.');
|
||||
return;
|
||||
}
|
||||
|
||||
async function saveGroup() {
|
||||
if (!targetGroup) {
|
||||
console.warn('Attempting to save group, but group is not loaded yet.');
|
||||
return;
|
||||
}
|
||||
targetGroup.settings.name = groupName;
|
||||
targetGroup.settings.tags = [...tagsList];
|
||||
targetGroup.settings.prefixes = [...prefixesList];
|
||||
targetGroup.settings.category = tagCategory;
|
||||
|
||||
targetGroup.settings.name = groupName;
|
||||
targetGroup.settings.tags = [...tagsList];
|
||||
targetGroup.settings.prefixes = [...prefixesList];
|
||||
targetGroup.settings.category = tagCategory;
|
||||
|
||||
await targetGroup.save();
|
||||
await goto(`/features/groups/${targetGroup.id}`);
|
||||
}
|
||||
await targetGroup.save();
|
||||
await goto(`/features/groups/${targetGroup.id}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem href="/features/groups/${groupId}" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/groups/${groupId}" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
<FormContainer>
|
||||
<FormControl label="Group Name">
|
||||
<TextField bind:value={groupName} placeholder="Group Name"></TextField>
|
||||
<FormControl label="Group Name">
|
||||
<TextField bind:value={groupName} placeholder="Group Name"></TextField>
|
||||
</FormControl>
|
||||
<FormControl label="Group Color">
|
||||
<TagCategorySelectField bind:value={tagCategory}/>
|
||||
</FormControl>
|
||||
<TagsColorContainer targetCategory="{tagCategory}">
|
||||
<FormControl label="Tags">
|
||||
<TagsEditor bind:tags={tagsList}/>
|
||||
</FormControl>
|
||||
<FormControl label="Group Color">
|
||||
<TagCategorySelectField bind:value={tagCategory}/>
|
||||
</TagsColorContainer>
|
||||
<TagsColorContainer targetCategory="{tagCategory}">
|
||||
<FormControl label="Tag Prefixes">
|
||||
<TagsEditor bind:tags={prefixesList}/>
|
||||
</FormControl>
|
||||
<TagsColorContainer targetCategory="{tagCategory}">
|
||||
<FormControl label="Tags">
|
||||
<TagsEditor bind:tags={tagsList}/>
|
||||
</FormControl>
|
||||
</TagsColorContainer>
|
||||
<TagsColorContainer targetCategory="{tagCategory}">
|
||||
<FormControl label="Tag Prefixes">
|
||||
<TagsEditor bind:tags={prefixesList}/>
|
||||
</FormControl>
|
||||
</TagsColorContainer>
|
||||
</TagsColorContainer>
|
||||
</FormContainer>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem on:click={saveGroup}>Save Group</MenuItem>
|
||||
<hr>
|
||||
<MenuItem on:click={saveGroup}>Save Group</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
<script>
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
|
||||
const groupId = $page.params.id;
|
||||
const groupTransporter = new EntitiesTransporter(TagGroup);
|
||||
const group = $tagGroups.find(group => group.id === groupId);
|
||||
const groupId = $page.params.id;
|
||||
const groupTransporter = new EntitiesTransporter(TagGroup);
|
||||
const group = $tagGroups.find(group => group.id === groupId);
|
||||
|
||||
/** @type {string} */
|
||||
let rawExportedGroup = $state();
|
||||
/** @type {string} */
|
||||
let encodedExportedGroup = $state();
|
||||
/** @type {string} */
|
||||
let rawExportedGroup = $state();
|
||||
/** @type {string} */
|
||||
let encodedExportedGroup = $state();
|
||||
|
||||
if (!group) {
|
||||
goto('/features/groups');
|
||||
} else {
|
||||
rawExportedGroup = groupTransporter.exportToJSON(group);
|
||||
encodedExportedGroup = groupTransporter.exportToCompressedJSON(group);
|
||||
}
|
||||
if (!group) {
|
||||
goto('/features/groups');
|
||||
} else {
|
||||
rawExportedGroup = groupTransporter.exportToJSON(group);
|
||||
encodedExportedGroup = groupTransporter.exportToCompressedJSON(group);
|
||||
}
|
||||
|
||||
let isEncodedGroupShown = $state(true);
|
||||
let isEncodedGroupShown = $state(true);
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem href="/features/groups/{groupId}" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/groups/{groupId}" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
<FormContainer>
|
||||
<FormControl label="Export string">
|
||||
<textarea readonly rows="6">{isEncodedGroupShown ? encodedExportedGroup : rawExportedGroup}</textarea>
|
||||
</FormControl>
|
||||
<FormControl label="Export string">
|
||||
<textarea readonly rows="6">{isEncodedGroupShown ? encodedExportedGroup : rawExportedGroup}</textarea>
|
||||
</FormControl>
|
||||
</FormContainer>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem on:click={() => isEncodedGroupShown = !isEncodedGroupShown}>
|
||||
Switch Format:
|
||||
{#if isEncodedGroupShown}
|
||||
Base64-Encoded
|
||||
{:else}
|
||||
Raw JSON
|
||||
{/if}
|
||||
</MenuItem>
|
||||
<hr>
|
||||
<MenuItem on:click={() => isEncodedGroupShown = !isEncodedGroupShown}>
|
||||
Switch Format:
|
||||
{#if isEncodedGroupShown}
|
||||
Base64-Encoded
|
||||
{:else}
|
||||
Raw JSON
|
||||
{/if}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,134 +1,134 @@
|
||||
<script>
|
||||
import { goto } from "$app/navigation";
|
||||
import GroupView from "$components/features/GroupView.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
import { goto } from "$app/navigation";
|
||||
import GroupView from "$components/features/GroupView.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import TagGroup from "$entities/TagGroup";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
import { tagGroups } from "$stores/entities/tag-groups";
|
||||
|
||||
const groupTransporter = new EntitiesTransporter(TagGroup);
|
||||
const groupTransporter = new EntitiesTransporter(TagGroup);
|
||||
|
||||
/** @type {string} */
|
||||
let importedString = $state('');
|
||||
/** @type {string} */
|
||||
let errorMessage = $state('');
|
||||
/** @type {string} */
|
||||
let importedString = $state('');
|
||||
/** @type {string} */
|
||||
let errorMessage = $state('');
|
||||
|
||||
/** @type {TagGroup|null} */
|
||||
let candidateGroup = $state(null);
|
||||
/** @type {TagGroup|null} */
|
||||
let existingGroup = $state(null);
|
||||
/** @type {TagGroup|null} */
|
||||
let candidateGroup = $state(null);
|
||||
/** @type {TagGroup|null} */
|
||||
let existingGroup = $state(null);
|
||||
|
||||
function tryImportingGroup() {
|
||||
candidateGroup = null;
|
||||
existingGroup = null;
|
||||
function tryImportingGroup() {
|
||||
candidateGroup = null;
|
||||
existingGroup = null;
|
||||
|
||||
errorMessage = '';
|
||||
importedString = importedString.trim();
|
||||
errorMessage = '';
|
||||
importedString = importedString.trim();
|
||||
|
||||
if (!importedString) {
|
||||
errorMessage = 'Nothing to import.';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (importedString.trim().startsWith('{')) {
|
||||
candidateGroup = groupTransporter.importFromJSON(importedString);
|
||||
}
|
||||
|
||||
candidateGroup = groupTransporter.importFromCompressedJSON(importedString);
|
||||
} catch (error) {
|
||||
errorMessage = error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error';
|
||||
}
|
||||
|
||||
if (candidateGroup) {
|
||||
existingGroup = $tagGroups.find(group => group.id === candidateGroup?.id) ?? null;
|
||||
}
|
||||
if (!importedString) {
|
||||
errorMessage = 'Nothing to import.';
|
||||
return;
|
||||
}
|
||||
|
||||
function saveGroup() {
|
||||
if (!candidateGroup) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (importedString.trim().startsWith('{')) {
|
||||
candidateGroup = groupTransporter.importFromJSON(importedString);
|
||||
}
|
||||
|
||||
candidateGroup.save().then(() => {
|
||||
goto(`/features/groups`);
|
||||
});
|
||||
candidateGroup = groupTransporter.importFromCompressedJSON(importedString);
|
||||
} catch (error) {
|
||||
errorMessage = error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error';
|
||||
}
|
||||
|
||||
function cloneAndSaveGroup() {
|
||||
if (!candidateGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clonedProfile = new TagGroup(crypto.randomUUID(), candidateGroup.settings);
|
||||
clonedProfile.settings.name += ` (Clone ${new Date().toISOString()})`;
|
||||
clonedProfile.save().then(() => {
|
||||
goto(`/features/groups`);
|
||||
});
|
||||
if (candidateGroup) {
|
||||
existingGroup = $tagGroups.find(group => group.id === candidateGroup?.id) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveGroup() {
|
||||
if (!candidateGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
candidateGroup.save().then(() => {
|
||||
goto(`/features/groups`);
|
||||
});
|
||||
}
|
||||
|
||||
function cloneAndSaveGroup() {
|
||||
if (!candidateGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clonedProfile = new TagGroup(crypto.randomUUID(), candidateGroup.settings);
|
||||
clonedProfile.settings.name += ` (Clone ${new Date().toISOString()})`;
|
||||
clonedProfile.save().then(() => {
|
||||
goto(`/features/groups`);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem icon="arrow-left" href="/features/groups">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/groups" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
{#if errorMessage}
|
||||
<p class="error">Failed to import: {errorMessage}</p>
|
||||
<Menu>
|
||||
<hr>
|
||||
</Menu>
|
||||
<p class="error">Failed to import: {errorMessage}</p>
|
||||
<Menu>
|
||||
<hr>
|
||||
</Menu>
|
||||
{/if}
|
||||
{#if !candidateGroup}
|
||||
<FormContainer>
|
||||
<FormControl label="Import string">
|
||||
<textarea bind:value={importedString} rows="6"></textarea>
|
||||
</FormControl>
|
||||
</FormContainer>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem on:click={tryImportingGroup}>Import</MenuItem>
|
||||
</Menu>
|
||||
<FormContainer>
|
||||
<FormControl label="Import string">
|
||||
<textarea bind:value={importedString} rows="6"></textarea>
|
||||
</FormControl>
|
||||
</FormContainer>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem on:click={tryImportingGroup}>Import</MenuItem>
|
||||
</Menu>
|
||||
{:else}
|
||||
{#if existingGroup}
|
||||
<p class="warning">
|
||||
This group will replace the existing "{existingGroup.settings.name}" group, since it have the same ID.
|
||||
</p>
|
||||
{/if}
|
||||
<GroupView group="{candidateGroup}"></GroupView>
|
||||
<Menu>
|
||||
<hr>
|
||||
{#if existingGroup}
|
||||
<p class="warning">
|
||||
This group will replace the existing "{existingGroup.settings.name}" group, since it have the same ID.
|
||||
</p>
|
||||
<MenuItem on:click={saveGroup}>Replace Existing Group</MenuItem>
|
||||
<MenuItem on:click={cloneAndSaveGroup}>Save as New Group</MenuItem>
|
||||
{:else}
|
||||
<MenuItem on:click={saveGroup}>Import New Group</MenuItem>
|
||||
{/if}
|
||||
<GroupView group="{candidateGroup}"></GroupView>
|
||||
<Menu>
|
||||
<hr>
|
||||
{#if existingGroup}
|
||||
<MenuItem on:click={saveGroup}>Replace Existing Group</MenuItem>
|
||||
<MenuItem on:click={cloneAndSaveGroup}>Save as New Group</MenuItem>
|
||||
{:else}
|
||||
<MenuItem on:click={saveGroup}>Import New Group</MenuItem>
|
||||
{/if}
|
||||
<MenuItem on:click={() => candidateGroup = null}>Cancel</MenuItem>
|
||||
</Menu>
|
||||
<MenuItem on:click={() => candidateGroup = null}>Cancel</MenuItem>
|
||||
</Menu>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@use '$styles/colors';
|
||||
@use '$styles/colors';
|
||||
|
||||
.error, .warning {
|
||||
padding: 5px 24px;
|
||||
margin: {
|
||||
left: -24px;
|
||||
right: -24px;
|
||||
}
|
||||
.error, .warning {
|
||||
padding: 5px 24px;
|
||||
margin: {
|
||||
left: -24px;
|
||||
right: -24px;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
background: colors.$error-background;
|
||||
}
|
||||
.error {
|
||||
background: colors.$error-background;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background: colors.$warning-background;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
.warning {
|
||||
background: colors.$warning-background;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,50 +1,49 @@
|
||||
<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 MenuRadioItem from "$components/ui/menu/MenuRadioItem.svelte";
|
||||
import { activeProfileStore, maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import MenuRadioItem from "$components/ui/menu/MenuRadioItem.svelte";
|
||||
import { activeProfileStore, maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
/** @type {import('$entities/MaintenanceProfile').default[]} */
|
||||
let profiles = $state([]);
|
||||
|
||||
/** @type {import('$entities/MaintenanceProfile').default[]} */
|
||||
let profiles = $state([]);
|
||||
run(() => {
|
||||
profiles = $maintenanceProfiles.sort((a, b) => a.settings.name.localeCompare(b.settings.name));
|
||||
});
|
||||
|
||||
run(() => {
|
||||
profiles = $maintenanceProfiles.sort((a, b) => a.settings.name.localeCompare(b.settings.name));
|
||||
});
|
||||
function resetActiveProfile() {
|
||||
$activeProfileStore = null;
|
||||
}
|
||||
|
||||
function resetActiveProfile() {
|
||||
$activeProfileStore = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event
|
||||
*/
|
||||
function enableSelectedProfile(event) {
|
||||
const target = event.target;
|
||||
|
||||
if (target instanceof HTMLInputElement && target.checked) {
|
||||
activeProfileStore.set(target.value);
|
||||
}
|
||||
/**
|
||||
* @param {Event} event
|
||||
*/
|
||||
function enableSelectedProfile(event) {
|
||||
const target = event.target;
|
||||
|
||||
if (target instanceof HTMLInputElement && target.checked) {
|
||||
activeProfileStore.set(target.value);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem icon="arrow-left" href="/">Back</MenuItem>
|
||||
<MenuItem icon="plus" href="/features/maintenance/new/edit">Create New</MenuItem>
|
||||
{#if profiles.length}
|
||||
<hr>
|
||||
{/if}
|
||||
{#each profiles as profile}
|
||||
<MenuRadioItem href="/features/maintenance/{profile.id}"
|
||||
name="active-profile"
|
||||
value="{profile.id}"
|
||||
checked="{$activeProfileStore === profile.id}"
|
||||
on:input={enableSelectedProfile}>
|
||||
{profile.settings.name}
|
||||
</MenuRadioItem>
|
||||
{/each}
|
||||
<MenuItem href="/" icon="arrow-left">Back</MenuItem>
|
||||
<MenuItem href="/features/maintenance/new/edit" icon="plus">Create New</MenuItem>
|
||||
{#if profiles.length}
|
||||
<hr>
|
||||
<MenuItem href="#" on:click={resetActiveProfile}>Reset Active Profile</MenuItem>
|
||||
<MenuItem href="/features/maintenance/import">Import Profile</MenuItem>
|
||||
{/if}
|
||||
{#each profiles as profile}
|
||||
<MenuRadioItem href="/features/maintenance/{profile.id}"
|
||||
name="active-profile"
|
||||
value="{profile.id}"
|
||||
checked="{$activeProfileStore === profile.id}"
|
||||
on:input={enableSelectedProfile}>
|
||||
{profile.settings.name}
|
||||
</MenuRadioItem>
|
||||
{/each}
|
||||
<hr>
|
||||
<MenuItem href="#" on:click={resetActiveProfile}>Reset Active Profile</MenuItem>
|
||||
<MenuItem href="/features/maintenance/import">Import Profile</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,65 +1,64 @@
|
||||
<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 { page } from "$app/stores";
|
||||
import { goto } from "$app/navigation";
|
||||
import { activeProfileStore, maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import ProfileView from "$components/features/ProfileView.svelte";
|
||||
import MenuCheckboxItem from "$components/ui/menu/MenuCheckboxItem.svelte";
|
||||
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { page } from "$app/stores";
|
||||
import { goto } from "$app/navigation";
|
||||
import { activeProfileStore, maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import ProfileView from "$components/features/ProfileView.svelte";
|
||||
import MenuCheckboxItem from "$components/ui/menu/MenuCheckboxItem.svelte";
|
||||
const profileId = $page.params.id;
|
||||
/** @type {import('$entities/MaintenanceProfile').default|null} */
|
||||
let profile = $state(null);
|
||||
|
||||
const profileId = $page.params.id;
|
||||
/** @type {import('$entities/MaintenanceProfile').default|null} */
|
||||
let profile = $state(null);
|
||||
if (profileId === 'new') {
|
||||
goto('/features/maintenance/new/edit');
|
||||
}
|
||||
|
||||
if (profileId === 'new') {
|
||||
goto('/features/maintenance/new/edit');
|
||||
run(() => {
|
||||
const resolvedProfile = $maintenanceProfiles.find(profile => profile.id === profileId);
|
||||
|
||||
if (resolvedProfile) {
|
||||
profile = resolvedProfile;
|
||||
} else {
|
||||
console.warn(`Profile ${profileId} not found.`);
|
||||
goto('/features/maintenance');
|
||||
}
|
||||
});
|
||||
|
||||
let isActiveProfile = $state($activeProfileStore === profileId);
|
||||
|
||||
run(() => {
|
||||
if (isActiveProfile && $activeProfileStore !== profileId) {
|
||||
$activeProfileStore = profileId;
|
||||
}
|
||||
|
||||
run(() => {
|
||||
const resolvedProfile = $maintenanceProfiles.find(profile => profile.id === profileId);
|
||||
|
||||
if (resolvedProfile) {
|
||||
profile = resolvedProfile;
|
||||
} else {
|
||||
console.warn(`Profile ${profileId} not found.`);
|
||||
goto('/features/maintenance');
|
||||
}
|
||||
});
|
||||
|
||||
let isActiveProfile = $state($activeProfileStore === profileId);
|
||||
|
||||
run(() => {
|
||||
if (isActiveProfile && $activeProfileStore !== profileId) {
|
||||
$activeProfileStore = profileId;
|
||||
}
|
||||
|
||||
if (!isActiveProfile && $activeProfileStore === profileId) {
|
||||
$activeProfileStore = null;
|
||||
}
|
||||
});
|
||||
if (!isActiveProfile && $activeProfileStore === profileId) {
|
||||
$activeProfileStore = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem href="/features/maintenance" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/maintenance" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
{#if profile}
|
||||
<ProfileView {profile}/>
|
||||
<ProfileView {profile}/>
|
||||
{/if}
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem icon="wrench" href="/features/maintenance/{profileId}/edit">Edit Profile</MenuItem>
|
||||
<MenuCheckboxItem bind:checked={isActiveProfile}>
|
||||
Activate Profile
|
||||
</MenuCheckboxItem>
|
||||
<MenuItem icon="file-export" href="/features/maintenance/{profileId}/export">
|
||||
Export Profile
|
||||
</MenuItem>
|
||||
<MenuItem icon="trash" href="/features/maintenance/{profileId}/delete">
|
||||
Delete Profile
|
||||
</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/maintenance/{profileId}/edit" icon="wrench">Edit Profile</MenuItem>
|
||||
<MenuCheckboxItem bind:checked={isActiveProfile}>
|
||||
Activate Profile
|
||||
</MenuCheckboxItem>
|
||||
<MenuItem href="/features/maintenance/{profileId}/export" icon="file-export">
|
||||
Export Profile
|
||||
</MenuItem>
|
||||
<MenuItem href="/features/maintenance/{profileId}/delete" icon="trash">
|
||||
Delete Profile
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<script>
|
||||
import { goto } from "$app/navigation";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { page } from "$app/stores";
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import { goto } from "$app/navigation";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { page } from "$app/stores";
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
|
||||
const profileId = $page.params.id;
|
||||
const targetProfile = $maintenanceProfiles.find(profile => profile.id === profileId);
|
||||
const profileId = $page.params.id;
|
||||
const targetProfile = $maintenanceProfiles.find(profile => profile.id === profileId);
|
||||
|
||||
if (!targetProfile) {
|
||||
void goto('/features/maintenance');
|
||||
}
|
||||
|
||||
async function deleteProfile() {
|
||||
if (!targetProfile) {
|
||||
void goto('/features/maintenance');
|
||||
console.warn('Attempting to delete the profile, but the profile is not loaded yet.');
|
||||
return;
|
||||
}
|
||||
|
||||
async function deleteProfile() {
|
||||
if (!targetProfile) {
|
||||
console.warn('Attempting to delete the profile, but the profile is not loaded yet.');
|
||||
return;
|
||||
}
|
||||
|
||||
await targetProfile.delete();
|
||||
await goto('/features/maintenance');
|
||||
}
|
||||
await targetProfile.delete();
|
||||
await goto('/features/maintenance');
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem icon="arrow-left" href="/features/maintenance/{profileId}">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/maintenance/{profileId}" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
{#if targetProfile}
|
||||
<p>
|
||||
Do you want to remove profile "{targetProfile.settings.name}"? This action is irreversible.
|
||||
</p>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem on:click={deleteProfile}>Yes</MenuItem>
|
||||
<MenuItem href="/features/maintenance/{profileId}">No</MenuItem>
|
||||
</Menu>
|
||||
<p>
|
||||
Do you want to remove profile "{targetProfile.settings.name}"? This action is irreversible.
|
||||
</p>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem on:click={deleteProfile}>Yes</MenuItem>
|
||||
<MenuItem href="/features/maintenance/{profileId}">No</MenuItem>
|
||||
</Menu>
|
||||
{:else}
|
||||
<p>Loading...</p>
|
||||
<p>Loading...</p>
|
||||
{/if}
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import TagsEditor from "$components/tags/TagsEditor.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import TextField from "$components/ui/forms/TextField.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import { page } from "$app/stores";
|
||||
import { goto } from "$app/navigation";
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import TagsEditor from "$components/tags/TagsEditor.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import TextField from "$components/ui/forms/TextField.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import { page } from "$app/stores";
|
||||
import { goto } from "$app/navigation";
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
|
||||
/** @type {string} */
|
||||
let profileId = $page.params.id;
|
||||
/** @type {MaintenanceProfile|null} */
|
||||
let targetProfile = null;
|
||||
/** @type {string} */
|
||||
let profileId = $page.params.id;
|
||||
/** @type {MaintenanceProfile|null} */
|
||||
let targetProfile = null;
|
||||
|
||||
/** @type {string} */
|
||||
let profileName = $state('');
|
||||
/** @type {string[]} */
|
||||
let tagsList = $state([]);
|
||||
/** @type {string} */
|
||||
let profileName = $state('');
|
||||
/** @type {string[]} */
|
||||
let tagsList = $state([]);
|
||||
|
||||
if (profileId === 'new') {
|
||||
targetProfile = new MaintenanceProfile(crypto.randomUUID(), {});
|
||||
if (profileId === 'new') {
|
||||
targetProfile = new MaintenanceProfile(crypto.randomUUID(), {});
|
||||
} else {
|
||||
const maybeExistingProfile = $maintenanceProfiles.find(profile => profile.id === profileId);
|
||||
|
||||
if (maybeExistingProfile) {
|
||||
targetProfile = maybeExistingProfile;
|
||||
profileName = targetProfile.settings.name;
|
||||
tagsList = [...targetProfile.settings.tags].sort((a, b) => a.localeCompare(b));
|
||||
} else {
|
||||
const maybeExistingProfile = $maintenanceProfiles.find(profile => profile.id === profileId);
|
||||
goto('/features/maintenance');
|
||||
}
|
||||
}
|
||||
|
||||
if (maybeExistingProfile) {
|
||||
targetProfile = maybeExistingProfile;
|
||||
profileName = targetProfile.settings.name;
|
||||
tagsList = [...targetProfile.settings.tags].sort((a, b) => a.localeCompare(b));
|
||||
} else {
|
||||
goto('/features/maintenance');
|
||||
}
|
||||
async function saveProfile() {
|
||||
if (!targetProfile) {
|
||||
console.warn('Attempting to save the profile, but the profile is not loaded yet.');
|
||||
return;
|
||||
}
|
||||
|
||||
async function saveProfile() {
|
||||
if (!targetProfile) {
|
||||
console.warn('Attempting to save the profile, but the profile is not loaded yet.');
|
||||
return;
|
||||
}
|
||||
targetProfile.settings.name = profileName;
|
||||
targetProfile.settings.tags = [...tagsList];
|
||||
targetProfile.settings.temporary = false;
|
||||
|
||||
targetProfile.settings.name = profileName;
|
||||
targetProfile.settings.tags = [...tagsList];
|
||||
targetProfile.settings.temporary = false;
|
||||
|
||||
await targetProfile.save();
|
||||
await goto('/features/maintenance/' + targetProfile.id);
|
||||
}
|
||||
await targetProfile.save();
|
||||
await goto('/features/maintenance/' + targetProfile.id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem icon="arrow-left" href="/features/maintenance{profileId === 'new' ? '' : '/' + profileId}">
|
||||
Back
|
||||
</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/maintenance{profileId === 'new' ? '' : '/' + profileId}" icon="arrow-left">
|
||||
Back
|
||||
</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
<FormContainer>
|
||||
<FormControl label="Profile Name">
|
||||
<TextField bind:value={profileName} placeholder="Profile Name"></TextField>
|
||||
</FormControl>
|
||||
<FormControl label="Tags">
|
||||
<TagsEditor bind:tags={tagsList}></TagsEditor>
|
||||
</FormControl>
|
||||
<FormControl label="Profile Name">
|
||||
<TextField bind:value={profileName} placeholder="Profile Name"></TextField>
|
||||
</FormControl>
|
||||
<FormControl label="Tags">
|
||||
<TagsEditor bind:tags={tagsList}></TagsEditor>
|
||||
</FormControl>
|
||||
</FormContainer>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem href="#" on:click={saveProfile}>Save Profile</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="#" on:click={saveProfile}>Save Profile</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
<script>
|
||||
import { page } from "$app/stores";
|
||||
import { goto } from "$app/navigation";
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import { page } from "$app/stores";
|
||||
import { goto } from "$app/navigation";
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
|
||||
const profileId = $page.params.id;
|
||||
const profile = $maintenanceProfiles.find(profile => profile.id === profileId);
|
||||
const profileId = $page.params.id;
|
||||
const profile = $maintenanceProfiles.find(profile => profile.id === profileId);
|
||||
|
||||
const profilesTransporter = new EntitiesTransporter(MaintenanceProfile);
|
||||
/** @type {string} */
|
||||
let exportedProfile = $state('');
|
||||
/** @type {string} */
|
||||
let compressedProfile = $state('');
|
||||
const profilesTransporter = new EntitiesTransporter(MaintenanceProfile);
|
||||
/** @type {string} */
|
||||
let exportedProfile = $state('');
|
||||
/** @type {string} */
|
||||
let compressedProfile = $state('');
|
||||
|
||||
if (!profile) {
|
||||
goto('/features/maintenance/');
|
||||
} else {
|
||||
exportedProfile = profilesTransporter.exportToJSON(profile);
|
||||
compressedProfile = profilesTransporter.exportToCompressedJSON(profile);
|
||||
}
|
||||
if (!profile) {
|
||||
goto('/features/maintenance/');
|
||||
} else {
|
||||
exportedProfile = profilesTransporter.exportToJSON(profile);
|
||||
compressedProfile = profilesTransporter.exportToCompressedJSON(profile);
|
||||
}
|
||||
|
||||
let isCompressedProfileShown = $state(true);
|
||||
let isCompressedProfileShown = $state(true);
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem href="/features/maintenance/{profileId}" icon="arrow-left">
|
||||
Back
|
||||
</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/maintenance/{profileId}" icon="arrow-left">
|
||||
Back
|
||||
</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
<FormContainer>
|
||||
<FormControl label="Export string">
|
||||
<textarea readonly rows="6">{isCompressedProfileShown ? compressedProfile : exportedProfile}</textarea>
|
||||
</FormControl>
|
||||
<FormControl label="Export string">
|
||||
<textarea readonly rows="6">{isCompressedProfileShown ? compressedProfile : exportedProfile}</textarea>
|
||||
</FormControl>
|
||||
</FormContainer>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem on:click={() => isCompressedProfileShown = !isCompressedProfileShown}>
|
||||
Switch Format:
|
||||
{#if isCompressedProfileShown}
|
||||
Base64-Encoded
|
||||
{:else}
|
||||
Raw JSON
|
||||
{/if}
|
||||
</MenuItem>
|
||||
<hr>
|
||||
<MenuItem on:click={() => isCompressedProfileShown = !isCompressedProfileShown}>
|
||||
Switch Format:
|
||||
{#if isCompressedProfileShown}
|
||||
Base64-Encoded
|
||||
{:else}
|
||||
Raw JSON
|
||||
{/if}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,134 +1,134 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import ProfileView from "$components/features/ProfileView.svelte";
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import { goto } from "$app/navigation";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import MaintenanceProfile from "$entities/MaintenanceProfile";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import ProfileView from "$components/features/ProfileView.svelte";
|
||||
import { maintenanceProfiles } from "$stores/entities/maintenance-profiles";
|
||||
import { goto } from "$app/navigation";
|
||||
import EntitiesTransporter from "$lib/extension/EntitiesTransporter";
|
||||
|
||||
const profilesTransporter = new EntitiesTransporter(MaintenanceProfile);
|
||||
const profilesTransporter = new EntitiesTransporter(MaintenanceProfile);
|
||||
|
||||
/** @type {string} */
|
||||
let importedString = $state('');
|
||||
/** @type {string} */
|
||||
let errorMessage = $state('');
|
||||
/** @type {string} */
|
||||
let importedString = $state('');
|
||||
/** @type {string} */
|
||||
let errorMessage = $state('');
|
||||
|
||||
/** @type {MaintenanceProfile|null} */
|
||||
let candidateProfile = $state(null);
|
||||
/** @type {MaintenanceProfile|null} */
|
||||
let existingProfile = $state(null);
|
||||
/** @type {MaintenanceProfile|null} */
|
||||
let candidateProfile = $state(null);
|
||||
/** @type {MaintenanceProfile|null} */
|
||||
let existingProfile = $state(null);
|
||||
|
||||
function tryImportingProfile() {
|
||||
candidateProfile = null;
|
||||
existingProfile = null;
|
||||
function tryImportingProfile() {
|
||||
candidateProfile = null;
|
||||
existingProfile = null;
|
||||
|
||||
errorMessage = '';
|
||||
importedString = importedString.trim();
|
||||
errorMessage = '';
|
||||
importedString = importedString.trim();
|
||||
|
||||
if (!importedString) {
|
||||
errorMessage = 'Nothing to import.';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (importedString.trim().startsWith('{')) {
|
||||
candidateProfile = profilesTransporter.importFromJSON(importedString);
|
||||
}
|
||||
|
||||
candidateProfile = profilesTransporter.importFromCompressedJSON(importedString);
|
||||
} catch (error) {
|
||||
errorMessage = error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error';
|
||||
}
|
||||
|
||||
if (candidateProfile) {
|
||||
existingProfile = $maintenanceProfiles.find(profile => profile.id === candidateProfile?.id) ?? null;
|
||||
}
|
||||
if (!importedString) {
|
||||
errorMessage = 'Nothing to import.';
|
||||
return;
|
||||
}
|
||||
|
||||
function saveProfile() {
|
||||
if (!candidateProfile) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (importedString.trim().startsWith('{')) {
|
||||
candidateProfile = profilesTransporter.importFromJSON(importedString);
|
||||
}
|
||||
|
||||
candidateProfile.save().then(() => {
|
||||
goto(`/features/maintenance`);
|
||||
});
|
||||
candidateProfile = profilesTransporter.importFromCompressedJSON(importedString);
|
||||
} catch (error) {
|
||||
errorMessage = error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error';
|
||||
}
|
||||
|
||||
function cloneAndSaveProfile() {
|
||||
if (!candidateProfile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clonedProfile = new MaintenanceProfile(crypto.randomUUID(), candidateProfile.settings);
|
||||
clonedProfile.settings.name += ` (Clone ${new Date().toISOString()})`;
|
||||
clonedProfile.save().then(() => {
|
||||
goto(`/features/maintenance`);
|
||||
});
|
||||
if (candidateProfile) {
|
||||
existingProfile = $maintenanceProfiles.find(profile => profile.id === candidateProfile?.id) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveProfile() {
|
||||
if (!candidateProfile) {
|
||||
return;
|
||||
}
|
||||
|
||||
candidateProfile.save().then(() => {
|
||||
goto(`/features/maintenance`);
|
||||
});
|
||||
}
|
||||
|
||||
function cloneAndSaveProfile() {
|
||||
if (!candidateProfile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clonedProfile = new MaintenanceProfile(crypto.randomUUID(), candidateProfile.settings);
|
||||
clonedProfile.settings.name += ` (Clone ${new Date().toISOString()})`;
|
||||
clonedProfile.save().then(() => {
|
||||
goto(`/features/maintenance`);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem icon="arrow-left" href="/features/maintenance">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/features/maintenance" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
{#if errorMessage}
|
||||
<p class="error">Failed to import: {errorMessage}</p>
|
||||
<Menu>
|
||||
<hr>
|
||||
</Menu>
|
||||
<p class="error">Failed to import: {errorMessage}</p>
|
||||
<Menu>
|
||||
<hr>
|
||||
</Menu>
|
||||
{/if}
|
||||
{#if !candidateProfile}
|
||||
<FormContainer>
|
||||
<FormControl label="Import string">
|
||||
<textarea bind:value={importedString} rows="6"></textarea>
|
||||
</FormControl>
|
||||
</FormContainer>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem on:click={tryImportingProfile}>Import</MenuItem>
|
||||
</Menu>
|
||||
<FormContainer>
|
||||
<FormControl label="Import string">
|
||||
<textarea bind:value={importedString} rows="6"></textarea>
|
||||
</FormControl>
|
||||
</FormContainer>
|
||||
<Menu>
|
||||
<hr>
|
||||
<MenuItem on:click={tryImportingProfile}>Import</MenuItem>
|
||||
</Menu>
|
||||
{:else}
|
||||
{#if existingProfile}
|
||||
<p class="warning">
|
||||
This profile will replace the existing "{existingProfile.settings.name}" profile, since it have the same ID.
|
||||
</p>
|
||||
{/if}
|
||||
<ProfileView profile="{candidateProfile}"></ProfileView>
|
||||
<Menu>
|
||||
<hr>
|
||||
{#if existingProfile}
|
||||
<p class="warning">
|
||||
This profile will replace the existing "{existingProfile.settings.name}" profile, since it have the same ID.
|
||||
</p>
|
||||
<MenuItem on:click={saveProfile}>Replace Existing Profile</MenuItem>
|
||||
<MenuItem on:click={cloneAndSaveProfile}>Save as New Profile</MenuItem>
|
||||
{:else}
|
||||
<MenuItem on:click={saveProfile}>Import New Profile</MenuItem>
|
||||
{/if}
|
||||
<ProfileView profile="{candidateProfile}"></ProfileView>
|
||||
<Menu>
|
||||
<hr>
|
||||
{#if existingProfile}
|
||||
<MenuItem on:click={saveProfile}>Replace Existing Profile</MenuItem>
|
||||
<MenuItem on:click={cloneAndSaveProfile}>Save as New Profile</MenuItem>
|
||||
{:else}
|
||||
<MenuItem on:click={saveProfile}>Import New Profile</MenuItem>
|
||||
{/if}
|
||||
<MenuItem on:click={() => candidateProfile = null}>Cancel</MenuItem>
|
||||
</Menu>
|
||||
<MenuItem on:click={() => candidateProfile = null}>Cancel</MenuItem>
|
||||
</Menu>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@use '$styles/colors';
|
||||
@use '$styles/colors';
|
||||
|
||||
.error, .warning {
|
||||
padding: 5px 24px;
|
||||
margin: {
|
||||
left: -24px;
|
||||
right: -24px;
|
||||
}
|
||||
.error, .warning {
|
||||
padding: 5px 24px;
|
||||
margin: {
|
||||
left: -24px;
|
||||
right: -24px;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
background: colors.$error-background;
|
||||
}
|
||||
.error {
|
||||
background: colors.$error-background;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background: colors.$warning-background;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
.warning {
|
||||
background: colors.$warning-background;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem href="/" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences/tags">Tagging</MenuItem>
|
||||
<MenuItem href="/preferences/search">Search</MenuItem>
|
||||
<MenuItem href="/preferences/misc">Misc & Tools</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences/debug">Debug</MenuItem>
|
||||
<MenuItem href="/" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences/tags">Tagging</MenuItem>
|
||||
<MenuItem href="/preferences/search">Search</MenuItem>
|
||||
<MenuItem href="/preferences/misc">Misc & Tools</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences/debug">Debug</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem href="/preferences" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences/debug/storage">Inspect Storages</MenuItem>
|
||||
<MenuItem href="/preferences" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences/debug/storage">Inspect Storages</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script>
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import { storagesCollection } from "$stores/debug";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import { storagesCollection } from "$stores/debug";
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem href="/preferences/debug" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
{#each Object.keys($storagesCollection) as storageName}
|
||||
<MenuItem href="/preferences/debug/storage/{storageName}/">Storage: {storageName}</MenuItem>
|
||||
{/each}
|
||||
<MenuItem href="/preferences/debug" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
{#each Object.keys($storagesCollection) as storageName}
|
||||
<MenuItem href="/preferences/debug/storage/{storageName}/">Storage: {storageName}</MenuItem>
|
||||
{/each}
|
||||
</Menu>
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
<script>
|
||||
import { run } from 'svelte/legacy';
|
||||
import { run } from 'svelte/legacy';
|
||||
import StorageViewer from "$components/debugging/StorageViewer.svelte";
|
||||
import { page } from "$app/stores";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
import StorageViewer from "$components/debugging/StorageViewer.svelte";
|
||||
import { page } from "$app/stores";
|
||||
import { goto } from "$app/navigation";
|
||||
let pathString = $state('');
|
||||
/** @type {string[]} */
|
||||
let pathArray = $state([]);
|
||||
/** @type {string|undefined} */
|
||||
let storageName = $state(void 0);
|
||||
|
||||
let pathString = $state('');
|
||||
/** @type {string[]} */
|
||||
let pathArray = $state([]);
|
||||
/** @type {string|undefined} */
|
||||
let storageName = $state(void 0);
|
||||
run(() => {
|
||||
pathString = $page.params.path;
|
||||
pathArray = pathString.length ? pathString.split("/") : [];
|
||||
storageName = pathArray.shift()
|
||||
|
||||
run(() => {
|
||||
pathString = $page.params.path;
|
||||
pathArray = pathString.length ? pathString.split("/") : [];
|
||||
storageName = pathArray.shift()
|
||||
if (pathArray.length && pathArray[pathArray.length - 1] === '') {
|
||||
pathArray.pop();
|
||||
}
|
||||
|
||||
if (pathArray.length && pathArray[pathArray.length - 1] === '') {
|
||||
pathArray.pop();
|
||||
}
|
||||
|
||||
if (!storageName) {
|
||||
goto("/preferences/debug/storage");
|
||||
}
|
||||
});
|
||||
if (!storageName) {
|
||||
goto("/preferences/debug/storage");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if storageName}
|
||||
<StorageViewer storage="{storageName}" path="{pathArray}"></StorageViewer>
|
||||
<StorageViewer storage="{storageName}" path="{pathArray}"></StorageViewer>
|
||||
{/if}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script>
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
|
||||
import { fullScreenViewerEnabled } from "$stores/preferences/misc";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
|
||||
import { fullScreenViewerEnabled } from "$stores/preferences/misc";
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem icon="arrow-left" href="/preferences">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
<FormContainer>
|
||||
<FormControl>
|
||||
<CheckboxField bind:checked={$fullScreenViewerEnabled}>
|
||||
Enable fullscreen viewer button
|
||||
</CheckboxField>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<CheckboxField bind:checked={$fullScreenViewerEnabled}>
|
||||
Enable fullscreen viewer button
|
||||
</CheckboxField>
|
||||
</FormControl>
|
||||
</FormContainer>
|
||||
|
||||
@@ -1,35 +1,32 @@
|
||||
<script>
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import {
|
||||
searchPropertiesSuggestionsEnabled,
|
||||
searchPropertiesSuggestionsPosition
|
||||
} from "$stores/preferences/search";
|
||||
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
|
||||
import SelectField from "$components/ui/forms/SelectField.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import { searchPropertiesSuggestionsEnabled, searchPropertiesSuggestionsPosition } from "$stores/preferences/search";
|
||||
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
|
||||
import SelectField from "$components/ui/forms/SelectField.svelte";
|
||||
|
||||
const propertiesPositions = {
|
||||
start: "At the start of the list",
|
||||
end: "At the end of the list",
|
||||
}
|
||||
const propertiesPositions = {
|
||||
start: "At the start of the list",
|
||||
end: "At the end of the list",
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem icon="arrow-left" href="/preferences">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
<FormContainer>
|
||||
<FormControl>
|
||||
<CheckboxField bind:checked={$searchPropertiesSuggestionsEnabled}>
|
||||
Auto-complete properties
|
||||
</CheckboxField>
|
||||
<FormControl>
|
||||
<CheckboxField bind:checked={$searchPropertiesSuggestionsEnabled}>
|
||||
Auto-complete properties
|
||||
</CheckboxField>
|
||||
</FormControl>
|
||||
{#if $searchPropertiesSuggestionsEnabled}
|
||||
<FormControl label="Show completed properties:">
|
||||
<SelectField bind:value={$searchPropertiesSuggestionsPosition}
|
||||
options="{propertiesPositions}"></SelectField>
|
||||
</FormControl>
|
||||
{#if $searchPropertiesSuggestionsEnabled}
|
||||
<FormControl label="Show completed properties:">
|
||||
<SelectField bind:value={$searchPropertiesSuggestionsPosition}
|
||||
options="{propertiesPositions}"></SelectField>
|
||||
</FormControl>
|
||||
{/if}
|
||||
{/if}
|
||||
</FormContainer>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script>
|
||||
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { stripBlacklistedTagsEnabled } from "$stores/preferences/maintenance";
|
||||
import CheckboxField from "$components/ui/forms/CheckboxField.svelte";
|
||||
import FormContainer from "$components/ui/forms/FormContainer.svelte";
|
||||
import FormControl from "$components/ui/forms/FormControl.svelte";
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { stripBlacklistedTagsEnabled } from "$stores/preferences/maintenance";
|
||||
</script>
|
||||
|
||||
<Menu>
|
||||
<MenuItem icon="arrow-left" href="/preferences">Back</MenuItem>
|
||||
<hr>
|
||||
<MenuItem href="/preferences" icon="arrow-left">Back</MenuItem>
|
||||
<hr>
|
||||
</Menu>
|
||||
<FormContainer>
|
||||
<FormControl>
|
||||
<CheckboxField bind:checked={$stripBlacklistedTagsEnabled}>
|
||||
Automatically remove black-listed tags from the images
|
||||
</CheckboxField>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<CheckboxField bind:checked={$stripBlacklistedTagsEnabled}>
|
||||
Automatically remove black-listed tags from the images
|
||||
</CheckboxField>
|
||||
</FormControl>
|
||||
</FormContainer>
|
||||
|
||||
Reference in New Issue
Block a user