Browse Source

fix bug with tag fetching, add tags to item frontend.

master
Gisle Aune 1 year ago
parent
commit
30bc93f9ee
  1. 34
      frontend/src/lib/components/common/TagRow.svelte
  2. 82
      frontend/src/lib/components/controls/TagInput.svelte
  3. 2
      frontend/src/lib/components/project/ItemSubSection.svelte
  4. 13
      frontend/src/lib/modals/ItemCreateModal.svelte
  5. 4
      frontend/src/lib/models/item.ts
  6. 32
      ports/mysql/items.go

34
frontend/src/lib/components/common/TagRow.svelte

@ -0,0 +1,34 @@
<script lang="ts">
export let names: string[]
</script>
<div class="tag-row">
{#each names as name (name)}
<div class="tag">{name}<span class="comma">,</span></div>
{/each}
</div>
<style lang="sass">
@import "../../css/colors"
div.tag-row
display: flex
flex-direction: row
flex-wrap: wrap
&:empty
display: none
> div.tag
margin: 0.25em 0.5ch
background-color: $color-entry2
padding: 0.25em 1ch
border-radius: 0.25em
font-size: 0.75em
> span.comma
font-size: 0
&:first-child
margin-left: 0
</style>

82
frontend/src/lib/components/controls/TagInput.svelte

@ -0,0 +1,82 @@
<script lang="ts">
import Icon from "../layout/Icon.svelte";
export let value: string[];
let nextTag = "";
function onKey(ev: KeyboardEvent) {
if ((ev.metaKey||ev.ctrlKey) && nextTag === "" && ev.key === "Backspace") {
value = value.slice(0, -1);
}
if ((ev.metaKey||ev.ctrlKey) && nextTag !== "" && ev.key === "Enter") {
value = [...value, nextTag];
nextTag = "";
ev.preventDefault();
}
}
$: while (nextTag.includes(",")) {
const newTags = nextTag.split(",").map(t => t.trim());
value = [...value, ...newTags.slice(0, -1)];
nextTag = newTags[newTags.length - 1];
}
$: value = (value||[]).filter((e, i) => !value.slice(0, i).includes(e));
</script>
<div class="tag-input">
{#each value as tag (tag)}
<div class="tag">
<span>{tag}</span>
<span class="comma">,</span>
<span class="x" on:click={() => value = value.filter(v => v !== tag)}>
<Icon name="times" />
</span>
</div>
{/each}
<input placeholder="Add tag (type , or press Ctrl+Enter to add)" on:keyup={onKey} bind:value={nextTag} />
</div>
<style lang="sass">
@import "../../css/colors"
div.tag-input
width: calc(100% - 2ch)
margin-bottom: 1em
margin-top: 0.20em
min-height: 2em
background: $color-entryhalf
color: $color-entry8
border: none
outline: none
resize: vertical
padding: 0.5em 1ch
display: flex
flex-direction: row
flex-wrap: wrap
> div.tag
margin: 0.25em 0.5ch
background-color: $color-entry1
padding: 0.25em 1ch
border-radius: 0.25em
span.x
font-size: 0.75em
line-height: 1em
user-select: none
cursor: pointer
&:hover
color: $color-entry12
span.comma
font-size: 0
> input
margin-bottom: 0.125em
</style>

2
frontend/src/lib/components/project/ItemSubSection.svelte

@ -17,6 +17,7 @@
import RequirementReference from "./RequirementReference.svelte";
import ItemTimeInfo from "./ItemTimeInfo.svelte";
import { getTimeContext } from "../contexts/TimeContext.svelte";
import TagRow from "../common/TagRow.svelte";
const {now} = getTimeContext();
@ -68,6 +69,7 @@
{:else}
<ItemTimeInfo item={item} />
{/if}
<TagRow names={item.tags} />
{#if !compact}
<Markdown source={item.description} />
{/if}

13
frontend/src/lib/modals/ItemCreateModal.svelte

@ -15,6 +15,7 @@
import RequirementSelect from "$lib/components/controls/RequirementSelect.svelte";
import ScheduledDateInput from "$lib/components/controls/ScheduledDateInput.svelte";
import StatInput from "$lib/components/controls/StatInput.svelte";
import TagInput from "$lib/components/controls/TagInput.svelte";
import Checkbox from "$lib/components/layout/Checkbox.svelte";
import type Item from "$lib/models/item";
import type { ItemInput } from "$lib/models/item";
@ -39,6 +40,7 @@ import ScheduledDateInput from "$lib/components/controls/ScheduledDateInput.svel
let requirementName: string
let op: string
let initOnList: number[] = [];
let oldTags: string[] = [];
let error: string
let loading: boolean
@ -79,6 +81,7 @@ import ScheduledDateInput from "$lib/components/controls/ScheduledDateInput.svel
stats: stats,
acquiredTime: null,
scheduledDate: null,
tags: [],
}
op = "Create"
@ -108,6 +111,7 @@ import ScheduledDateInput from "$lib/components/controls/ScheduledDateInput.svel
stats: ctxStats.map(s => inputStats.find(s2 => s2.statId === s.statId) || s),
acquiredTime: formatFormTime(current.acquiredTime),
scheduledDate: current.scheduledDate,
tags: current.tags,
};
itemId = current.id;
scopeId = current.scopeId;
@ -117,6 +121,8 @@ import ScheduledDateInput from "$lib/components/controls/ScheduledDateInput.svel
openedDate = new Date();
requirementName = current.requirement?.name;
show = true;
oldTags = [...current.tags];
}
async function submit() {
@ -154,6 +160,11 @@ import ScheduledDateInput from "$lib/components/controls/ScheduledDateInput.svel
submission.clearAcquiredTime = true;
}
submission.addTags = submission.tags.filter(t => !oldTags.includes(t));
submission.removeTags = oldTags.filter(t => !submission.tags.includes(t));
delete submission.tags
submission.stats = statDiff(currentStats, submission.stats)
await sl3(fetch).updateItem(scopeId, itemId, submission);
break;
@ -199,6 +210,8 @@ import ScheduledDateInput from "$lib/components/controls/ScheduledDateInput.svel
<textarea name="description" bind:value={item.description} />
<label for="scheduledTime">Scheduled</label>
<ScheduledDateInput openDate={openedDate} bind:value={item.scheduledDate} />
<label for="tags">Tags</label>
<TagInput bind:value={item.tags} />
{#if op === "Create"}
<Checkbox bind:checked={addAnother} label="Add another." />
{/if}

4
frontend/src/lib/models/item.ts

@ -18,6 +18,7 @@ export default interface Item {
weightedRequired: number
project: ItemProject
requirement: ItemRequirement
tags: string[]
}
interface ItemProject {
@ -44,6 +45,9 @@ export interface ItemInput {
clearAcquiredTime?: boolean
clearScheduledDate?: boolean
stats: StatValueInput[]
tags: string[]
addTags?: string[]
removeTags?: string[]
}
export interface ItemFilter {

32
ports/mysql/items.go

@ -212,10 +212,42 @@ func (r *itemRepository) Fetch(ctx context.Context, filter models.ItemFilter) ([
item.ProjectID = intPtr(projectId)
item.AcquiredTime = timePtr(acquiredTime)
item.ScheduledDate = scheduledDate.AsPtr()
item.Tags = []string{}
res = append(res, item)
}
// Fill tags
ids := genutils.Map(res, func(i entities.Item) int {
return i.ID
})
query, args, err := squirrel.Select("object_id, tag_name").
From("tag").
Where(squirrel.Eq{"object_id": ids, "object_kind": tagObjectKindItem}).
ToSql()
if err != nil {
return nil, err
}
rows, err = r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
for rows.Next() {
var tagStr string
var objectID int
err := rows.Scan(&objectID, &tagStr)
if err != nil {
return nil, err
}
for i := range res {
if res[i].ID == objectID {
res[i].Tags = append(res[i].Tags, tagStr)
break
}
}
}
sort.Slice(res, func(i, j int) bool {
// Acquired time, descending
ati, atj := res[i].AcquiredTime, res[j].AcquiredTime

Loading…
Cancel
Save