Skip to content

Commit b648bfe

Browse files
authored
Merge pull request #710 from Kiln-AI/leonard/kil-141-refactor-consolidate-tag-picking-ui
refactor: reusable tag picking
2 parents 015e4a4 + 3d626fd commit b648bfe

File tree

6 files changed

+207
-282
lines changed

6 files changed

+207
-282
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<script lang="ts">
2+
import TagDropdown from "./tag_dropdown.svelte"
3+
import { createEventDispatcher } from "svelte"
4+
5+
export let tags: string[] = []
6+
export let tag_type: "doc" | "task_run" = "doc"
7+
export let project_id: string | null = null
8+
export let task_id: string | null = null
9+
10+
const dispatch = createEventDispatcher<{
11+
tags_changed: { previous: string[]; current: string[] }
12+
}>()
13+
export let disabled: boolean = false
14+
15+
// controls whether the dropdown is initially visible
16+
// if not then we show the tags with the + button
17+
export let initial_expanded: boolean = false
18+
export let show_close_button = true
19+
export let hide_dropdown_after_select = true
20+
21+
let show_dropdown = initial_expanded
22+
let current_tag = ""
23+
24+
$: show_dropdown = initial_expanded
25+
26+
function tags_are_equal(tags1: string[], tags2: string[]): boolean {
27+
return (
28+
tags1.length === tags2.length && tags1.every((tag) => tags2.includes(tag))
29+
)
30+
}
31+
32+
function emit_tags_changed_if_different(
33+
previous: string[],
34+
current: string[],
35+
) {
36+
const deduped_current = [...new Set(current)]
37+
if (!tags_are_equal(previous, deduped_current)) {
38+
dispatch("tags_changed", { previous, current: deduped_current })
39+
}
40+
}
41+
42+
function handle_tag_select(tag: string) {
43+
if (!tags.includes(tag)) {
44+
const previous_tags = [...tags]
45+
const new_tags = [...tags, tag]
46+
47+
emit_tags_changed_if_different(previous_tags, new_tags)
48+
}
49+
current_tag = ""
50+
if (hide_dropdown_after_select) {
51+
show_dropdown = false
52+
}
53+
}
54+
55+
function handle_escape() {
56+
if (!initial_expanded) {
57+
show_dropdown = false
58+
}
59+
current_tag = ""
60+
}
61+
62+
function handle_remove_tag(tag: string) {
63+
const previous_tags = [...tags]
64+
const new_tags = tags.filter((t) => t !== tag)
65+
66+
emit_tags_changed_if_different(previous_tags, new_tags)
67+
}
68+
69+
function toggle_dropdown() {
70+
if (!disabled) {
71+
show_dropdown = !show_dropdown
72+
}
73+
}
74+
75+
function handle_close_dropdown() {
76+
show_dropdown = false
77+
current_tag = ""
78+
}
79+
</script>
80+
81+
<div class="w-full">
82+
{#if tags.length > 0}
83+
<div class="flex flex-row flex-wrap gap-2 mb-2">
84+
{#each (tags ?? []).slice().sort() as tag (tag)}
85+
<div class="badge bg-gray-200 text-gray-500 py-3 px-3 max-w-full">
86+
<span class="truncate">{tag}</span>
87+
<button
88+
class="pl-3 font-medium shrink-0"
89+
on:click={() => handle_remove_tag(tag)}
90+
{disabled}>✕</button
91+
>
92+
</div>
93+
{/each}
94+
95+
{#if !show_dropdown}
96+
<button
97+
class="badge bg-gray-200 text-gray-500 p-3 font-medium {disabled
98+
? 'opacity-50'
99+
: ''}"
100+
on:click={toggle_dropdown}
101+
{disabled}>+</button
102+
>
103+
{/if}
104+
</div>
105+
{/if}
106+
107+
{#if show_dropdown}
108+
<div class="flex flex-row gap-2 items-center">
109+
<TagDropdown
110+
bind:tag={current_tag}
111+
{project_id}
112+
{task_id}
113+
example_tag_set={tag_type}
114+
on_select={handle_tag_select}
115+
on_escape={handle_escape}
116+
focus_on_mount={true}
117+
/>
118+
{#if show_close_button}
119+
<div class="flex-none">
120+
<button
121+
class="btn btn-sm btn-circle text-xl font-medium"
122+
on:click={handle_close_dropdown}>✕</button
123+
>
124+
</div>
125+
{/if}
126+
</div>
127+
{/if}
128+
</div>

app/web_ui/src/routes/(app)/dataset/[project_id]/[task_id]/+page.svelte

Lines changed: 18 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import { page } from "$app/stores"
1111
import { formatDate } from "$lib/utils/formatters"
1212
import { replaceState } from "$app/navigation"
13-
import TagDropdown from "../../../../../lib/ui/tag_dropdown.svelte"
13+
import TagPicker from "$lib/ui/tag_picker.svelte"
1414
import Dialog from "$lib/ui/dialog.svelte"
1515
1616
let runs: RunSummary[] | null = null
@@ -390,27 +390,16 @@
390390
}
391391
}
392392
393-
let add_tags: Set<string> = new Set()
393+
let add_tags: string[] = []
394394
let remove_tags: Set<string> = new Set()
395-
let show_add_tag_dropdown = false
396-
let current_tag: string = ""
397395
398396
let add_tags_dialog: Dialog | null = null
399397
400398
function show_add_tags_modal() {
401-
// Show the dropdown
402-
show_add_tag_dropdown = true
403-
404399
add_tags_dialog?.show()
405400
}
406401
407402
async function add_selected_tags(): Promise<boolean> {
408-
// Special case for this UI - consider the partly filled tag in the input
409-
// as a tag to add
410-
if (current_tag.length > 0) {
411-
add_tags.add(current_tag)
412-
current_tag = ""
413-
}
414403
// Don't accidentially remove tags
415404
remove_tags = new Set()
416405
return await edit_tags()
@@ -443,7 +432,7 @@
443432
444433
async function remove_selected_tags(): Promise<boolean> {
445434
// Don't accidentially add tags
446-
add_tags = new Set()
435+
add_tags = []
447436
return await edit_tags()
448437
}
449438
@@ -455,7 +444,7 @@
455444
params: { path: { project_id, task_id } },
456445
body: {
457446
run_ids: Array.from(selected_runs),
458-
add_tags: Array.from(add_tags),
447+
add_tags: add_tags,
459448
remove_tags: Array.from(remove_tags),
460449
},
461450
},
@@ -465,14 +454,14 @@
465454
}
466455
467456
// Hide the dropdown (safari bug shows it when hidden)
468-
show_add_tag_dropdown = false
457+
add_tags = []
469458
470459
// Close modal on success
471460
return true
472461
} finally {
473462
// Reload UI, even on failure, as partial delete is possible
474463
selected_runs = new Set()
475-
add_tags = new Set()
464+
add_tags = []
476465
select_mode = false
477466
await get_runs()
478467
}
@@ -726,62 +715,25 @@
726715
{
727716
label: "Add Tags",
728717
asyncAction: add_selected_tags,
729-
disabled: add_tags.size == 0 && !current_tag,
718+
disabled: add_tags.length == 0,
730719
isPrimary: true,
731720
},
732721
]}
733722
>
734723
<div>
735724
<div class="text-sm font-light text-gray-500 mb-2">
736-
Tags can be used to organize you dataset.
737-
</div>
738-
<div class="flex flex-row flex-wrap gap-2 mt-2">
739-
{#each Array.from(add_tags).sort() as tag}
740-
<div class="badge bg-gray-200 text-gray-500 py-3 px-3 max-w-full">
741-
<span class="truncate">{tag}</span>
742-
<button
743-
class="pl-3 font-medium shrink-0"
744-
on:click={() => {
745-
add_tags.delete(tag)
746-
add_tags = add_tags
747-
}}>✕</button
748-
>
749-
</div>
750-
{/each}
751-
<button
752-
class="badge bg-gray-200 text-gray-500 p-3 font-medium {show_add_tag_dropdown
753-
? 'hidden'
754-
: ''}"
755-
on:click={() => (show_add_tag_dropdown = true)}>+</button
756-
>
725+
Tags can be used to organize your dataset.
757726
</div>
758-
{#if show_add_tag_dropdown}
759-
<div
760-
class="mt-3 flex flex-row gap-2 items-center {show_add_tag_dropdown
761-
? ''
762-
: 'hidden'}"
763-
>
764-
<TagDropdown
765-
bind:tag={current_tag}
766-
{project_id}
767-
{task_id}
768-
on_select={(tag) => {
769-
add_tags.add(tag)
770-
add_tags = add_tags
771-
show_add_tag_dropdown = false
772-
current_tag = ""
773-
}}
774-
on_escape={() => (show_add_tag_dropdown = false)}
775-
focus_on_mount={true}
776-
/>
777-
<div class="flex-none">
778-
<button
779-
class="btn btn-sm btn-circle text-xl font-medium"
780-
on:click={() => (show_add_tag_dropdown = false)}>✕</button
781-
>
782-
</div>
783-
</div>
784-
{/if}
727+
<TagPicker
728+
tags={add_tags}
729+
tag_type="task_run"
730+
{project_id}
731+
{task_id}
732+
initial_expanded={true}
733+
on:tags_changed={(event) => {
734+
add_tags = event.detail.current
735+
}}
736+
/>
785737
</div>
786738
</Dialog>
787739

0 commit comments

Comments
 (0)