Skip to content

Commit

Permalink
v0.1.1
Browse files Browse the repository at this point in the history
* Added automatic counting and adding and removing of individual reoccuring tags
  * The most frequent tags are shown on the side, however it is possible to filter for tags (it can be filtered for multiple tags at once by separating them with `|`, if the first character is a `-`, the tag will be excluded from the results, if the first character (after the optional `-`) is `^` or the last character is `$` the current search term will be parsed as regex instead of a simple string search)
  * Commonly in the context of Stable Diffusion for example tags are comma separated, so this is also the default string which is used for splitting the tags
  • Loading branch information
NetroScript committed Dec 22, 2023
1 parent 549c209 commit b992850
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 15 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## 0.1.1

* Added automatic counting and adding and removing of individual reoccuring tags
* The most frequent tags are shown on the side, however it is possible to filter for tags (it can be filtered for multiple tags at once by separating them with `|`, if the first character is a `-`, the tag will be excluded from the results, if the first character (after the optional `-`) is `^` or the last character is `$` the current search term will be parsed as regex instead of a simple string search)
* Commonly in the context of Stable Diffusion for example tags are comma separated, so this is also the default string which is used for splitting the tags
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "image-set-tag-editor",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"scripts": {
"dev": "vite dev",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"package": {
"productName": "image-set-tag-editor",
"version": "0.1.0"
"version": "0.1.1"
},
"tauri": {
"allowlist": {
Expand Down
119 changes: 119 additions & 0 deletions src/lib/components/TagHelper.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<script lang="ts">
import { single_tag_delimiter, active_image, tagCounts, sortedTags } from '$lib/stores';
let custom_css_classes = '';
let tag_filter_string = '';
$: tag_white_list = tag_filter_string
.split('|')
.filter((tag) => tag != '' && !tag.startsWith('-'))
.map((tag) => {
if (tag.startsWith('^') || tag.endsWith('$')) {
return new RegExp(tag);
}
return tag;
});
$: tag_black_list = tag_filter_string
.split('|')
.filter((tag) => tag != '' && tag.startsWith('-'))
.map((tag) => {
tag = tag.slice(1);
if (tag.startsWith('^') || tag.endsWith('$')) {
return new RegExp(tag);
}
return tag;
});
$: fitting_tags = $sortedTags
.filter((tag) => {
// Or when the filter string is empty
if (tag_filter_string == '') return true;
// Only return if any keyword in the white list is in the tag
return (
tag_white_list.some((keyword) => {
if (keyword instanceof RegExp) {
return keyword.test(tag);
}
return tag.includes(keyword);
}) &&
!tag_black_list.some((keyword) => {
if (keyword instanceof RegExp) {
return keyword.test(tag);
}
return tag.includes(keyword);
})
);
})
.slice(0, 50)
.sort((a, b) => a.localeCompare(b));
</script>

<hr class="my-2" />
<div>
<label class="label">
<span>Single Tag Delimiter</span>
<input
class="input variant-form-material"
type="search"
placeholder="Single Tag Delimiter"
title="Single Tag Delimiter"
on:keydown={(event) => {
event.stopPropagation();
}}
bind:value={$single_tag_delimiter}
/>
</label>
</div>
<div>
<label class="label">
<span>Filter tags</span>
<input
class="input variant-form-material"
type="search"
placeholder="Filter tags"
title="Filter tags"
on:keydown={(event) => {
event.stopPropagation();
}}
bind:value={tag_filter_string}
/>
</label>
</div>
<div class="overflow-y-scroll pl-1">
{#each fitting_tags as tag}
<label class="flex items-center space-x-2">
<input
class="checkbox"
type="checkbox"
checked={($active_image?.caption.split($single_tag_delimiter) || [])
.map((tag) => tag.trim() + $single_tag_delimiter)
.includes(tag)}
on:click={(event) => {
if ($active_image) {
if (
($active_image?.caption.split($single_tag_delimiter) || [])
.map((tag) => tag.trim() + $single_tag_delimiter)
.includes(tag)
) {
$active_image.caption = $active_image.caption.replace(new RegExp(`${tag} ?`), '');
} else {
console.log('Adding tag ' + tag);
$active_image.caption = tag + ' ' + $active_image.caption;
$active_image = $active_image;
}
}
}}
/>
<div class="tag-display" data-tag={tag}>
{tag}
<div class="inline text-gray opacity-60">{$tagCounts[tag]}</div>
</div>
</label>
{/each}
</div>
<hr class="my-2" />

<svelte:head>
<!-- Dynamically insert a custom css style tag -->
<svelte:element this={'style'} type="text/css">{custom_css_classes}</svelte:element>
</svelte:head>
32 changes: 32 additions & 0 deletions src/lib/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { writable, derived, get } from 'svelte/store';
export const working_folder = writable('');
export const file_server_port = writable(0);
export const available_files = writable<string[]>([]);
export const single_tag_delimiter = writable(',');

const VALID_IMAGE_FORMATS = ['.jpg', '.png', '.gif', '.bmp', '.webp', '.jpeg', '.avif'];

Expand All @@ -11,6 +12,37 @@ export const available_images = writable<
{ image: string; caption: string; caption_file: string; viewed: boolean }[]
>([]);
export const is_loading_image_data = writable(false);
export const active_image = writable<
{ image: string; caption: string; caption_file: string; viewed: boolean } | undefined
>(undefined);

export const tagCounts = derived(
[available_images, single_tag_delimiter],
([$available_images, $single_tag_delimiter]) => {
console.log('counting tags');
const data: Record<string, number> = {};
$available_images.forEach((image) => {
const tags = (image.caption.split($single_tag_delimiter) || []).map(
(tag) => tag.trim() + get(single_tag_delimiter) // Ad the delimiter back for better searching, inserting and removing
);

tags.forEach((tag) => {
if (data[tag]) {
data[tag] += 1;
} else {
data[tag] = 1;
}
});
});
return data;
}
);

export const sortedTags = derived(tagCounts, ($tagCounts) => {
console.log('sorting tags');
return Object.keys($tagCounts).sort((a, b) => $tagCounts[b] - $tagCounts[a]);
});
export const current_caption = writable('');

const update_available_files = async () => {
const image_map = new Map<
Expand Down
5 changes: 5 additions & 0 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import { toastStore } from '@skeletonlabs/skeleton';
import { onMount } from 'svelte';
import { get } from 'svelte/store';
import TagHelper from '$lib/components/TagHelper.svelte';
async function select_new_folder() {
try {
Expand Down Expand Up @@ -122,6 +123,10 @@

<div class="flex-1" />

<TagHelper />

<div class="flex-1" />

<label class="label text-center">
<span>Apply all caption changes.</span>
<button
Expand Down
35 changes: 22 additions & 13 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
working_folder,
available_images,
is_loading_image_data,
file_server_port
file_server_port,
current_caption,
active_image
} from '$lib/stores';
import { ProgressRadial } from '@skeletonlabs/skeleton';
import { dragscroll } from '@svelte-put/dragscroll';
Expand Down Expand Up @@ -91,6 +93,11 @@
behavior: 'smooth'
});
};
$: {
$current_caption = $available_images[current_file]?.caption || '';
$active_image = $available_images[current_file];
}
</script>

<svelte:window on:keydown={keydownHandler} />
Expand Down Expand Up @@ -141,18 +148,20 @@

<div class="px-2">
<label class="label">
<span
>Caption <div class="inline text-gray-500 italic">
({$available_images[current_file].image})
</div></span
>
<textarea
class="textarea"
on:keydown|stopPropagation={(event) => keydownHandler(event, true)}
rows="2"
placeholder="Write your caption for the image here."
bind:value={$available_images[current_file].caption}
/>
{#if $active_image}
<span
>Caption <div class="inline text-gray-500 italic">
({$active_image.image})
</div></span
>
<textarea
class="textarea"
on:keydown|stopPropagation={(event) => keydownHandler(event, true)}
rows="2"
placeholder="Write your caption for the image here."
bind:value={$active_image.caption}
/>
{/if}
</label>
</div>

Expand Down

0 comments on commit b992850

Please sign in to comment.