Skip to content

Commit

Permalink
feat(Tags): Add support for tags
Browse files Browse the repository at this point in the history
  • Loading branch information
Dlurak committed Jul 1, 2024
1 parent 4d81a21 commit fcbb207
Show file tree
Hide file tree
Showing 25 changed files with 567 additions and 40 deletions.
7 changes: 7 additions & 0 deletions src/lib/components/notes/CreationInner.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
import TextArea from '../input/TextArea.svelte';
import TextInput from '../input/Text.svelte';
import { priorities } from '$lib/constants/priorities';
import ChooseTag from '../tags/ChooseTag.svelte';
import type { Tag } from '../tags/types';
export let title = '';
export let summary = '';
export let priority: Note['priority'] | null = null;
export let editScope: Note['editScope'] | null = null;
export let className: string;
export let schoolName: string;
export let tags: Tag[] = [];
const scopes = ['self', 'class', 'school'] as const;
</script>
Expand Down Expand Up @@ -39,3 +44,5 @@
}))}
bind:value={editScope}
/>

<ChooseTag bind:selected={tags} {className} {schoolName} />
14 changes: 12 additions & 2 deletions src/lib/components/notes/Edit.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
export let note: Note;
export let id: string;
let { title, priority, editScope } = note;
let { title, priority, editScope, tags } = note;
let summary = note.summary ?? undefined;
const dispatch = createEventDispatcher<{ update: null }>();
Expand All @@ -24,6 +24,7 @@
title,
priority,
editScope,
tags,
summary: summary ?? null
});
</script>
Expand All @@ -32,7 +33,15 @@
<div slot="title">Edit</div>

<div slot="body" class="flex flex-col gap-3 py-3">
<CreationInner bind:title bind:summary bind:priority bind:editScope />
<CreationInner
bind:title
bind:summary
bind:priority
bind:editScope
bind:tags
className={note.class.name}
schoolName={note.class.school.name}
/>

<PrimaryButton
disabled={!hasChanges}
Expand All @@ -42,6 +51,7 @@
title,
summary,
editScope,
tags,
priority: priority ?? 'Minimal'
}).catch(sendDefaultErrorToast);

Expand Down
41 changes: 31 additions & 10 deletions src/lib/components/notes/New.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
import { createNote } from '$lib/dlool/notes/create';
import { sendDefaultErrorToast, sendToast } from '../layout/toasts';
import { createEventDispatcher } from 'svelte';
import type { Tag } from '../tags/types';
import { safePromise } from '$lib/utils/promises';
let isOpen = false;
let onGoing = false;
export let query: {
school: string;
Expand All @@ -33,6 +36,7 @@
let summary = '';
let priority: Note['priority'] = 'Minimal';
let editScope: Note['editScope'] = 'Self';
let tags: Tag[] = [];
const dispatch = createEventDispatcher<{ create: null }>();
</script>
Expand Down Expand Up @@ -72,21 +76,37 @@
/>
{/if}

<CreationInner bind:title bind:summary bind:priority bind:editScope />
<CreationInner
bind:title
bind:summary
bind:priority
bind:editScope
bind:tags
className={classInput}
schoolName={query.school}
/>

<hr class="border-zinc-300 dark:border-zinc-700" />

<PrimaryButton
disabled={!(classInput && title)}
disabled={!(classInput && title) || onGoing}
on:click={async () => {
await createNote({
title,
editScope,
summary,
priority: priority ?? undefined,
school: query.school,
class: classInput
}).catch(sendDefaultErrorToast);
onGoing = true;
const res = await safePromise(
createNote({
title,
editScope,
summary,
tags,
priority: priority ?? undefined,
school: query.school,
class: classInput
})
);

onGoing = false;

if (res.isError) return sendDefaultErrorToast();

sendToast({
type: 'success',
Expand All @@ -100,6 +120,7 @@
summary = '';
priority = 'Minimal';
editScope = 'Self';
tags = [];

dispatch('create', null);
}}
Expand Down
18 changes: 18 additions & 0 deletions src/lib/components/notes/NoteBox.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
<script lang="ts">
import type { Note } from '$lib/dlool/notes/list';
import { page } from '$app/stores';
import TagLabel from '../tags/TagLabel.svelte';
import { sortByPredicate } from '$lib/utils/arrays/sort';
import { svocal } from '$lib/utils/store/svocal';
import { slide } from 'svelte/transition';
import { animationLength } from '$lib/utils/store/animation';
export let note: Note & { id: string };
const tagsInOverview = svocal('settings.tagsInOverview');
</script>

<div
Expand All @@ -14,6 +21,17 @@
>
<h3>{note.title}</h3>

{#if $tagsInOverview}
<div
class="flex flex-wrap gap-1 py-1 text-sm empty:hidden"
transition:slide={{ duration: $animationLength }}
>
{#each sortByPredicate(note.tags, ({ tag }) => tag) as tag (tag.tag)}
<TagLabel {tag} />
{/each}
</div>
{/if}

{#if note.summary}
<p>{note.summary}</p>
{:else}
Expand Down
81 changes: 81 additions & 0 deletions src/lib/components/tags/ChooseTag.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<script lang="ts">
import { listTags } from '$lib/dlool/tags/list';
import { createEventDispatcher } from 'svelte';
import Modal from '../modal/Modal.svelte';
import NewTag from './NewTag.svelte';
import TagButton from './TagButton.svelte';
import type { Tag } from './types';
import Store from '../utils/Store.svelte';
import { i } from '$lib/i18n/store';
export let className: string;
export let schoolName: string;
export let selected: Tag[] = [];
let isOpen = false;
const dispatch = createEventDispatcher<{
change: Tag;
}>();
</script>

<div class="flex items-center justify-between gap-2">
<span><Store store={i('tags.choose')} /></span>

<div class="flex flex-wrap gap-1">
{#each selected as tag (tag.tag)}
<TagButton
{tag}
removable
on:click={() => (isOpen = !isOpen)}
on:remove={({ detail }) => {
selected = selected.filter(({ tag: t }) => t !== detail.tag);
}}
/>
{/each}
<button
class="rounded-full bg-zinc-200 px-2 py-0.5 outline outline-1 outline-zinc-400 dark:bg-zinc-700 dark:outline-zinc-600"
on:click={() => {
isOpen = !isOpen;
}}
>
<Store store={i('tags.choose.choose')} />
</button>
</div>
</div>

<Modal bind:isOpen>
<div slot="title">
<Store store={i('tags.choose')} />
</div>

<div slot="body">
<div class="flex flex-col">
<NewTag
{className}
on:create={({ detail }) => {
dispatch('change', detail);
selected = [...selected, { ...detail }];
isOpen = false;
}}
/>

<!-- TODO: reload the list when it gets opened again -->
{#await listTags({ school: schoolName, class: className }) then data}
<div class="flex flex-wrap gap-1 py-2">
{#each data as tag (tag.id)}
<TagButton
{tag}
disabled={selected.some(({ tag: t }) => t === tag.tag)}
on:click={({ detail }) => {
dispatch('change', detail);
selected = [...selected, { ...detail }];
isOpen = false;
}}
/>
{/each}
</div>
{/await}
</div>
</div>
</Modal>
70 changes: 70 additions & 0 deletions src/lib/components/tags/NewTag.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<script lang="ts">
import TextInput from '$lib/components/input/Text.svelte';
import ColorPreview from '../settings/color/ColorPreview.svelte';
import SettingsButton from '../buttons/SettingsButton.svelte';
import { createTag } from '$lib/dlool/tags/create';
import { sendDefaultErrorToast, sendToast } from '../layout/toasts';
import { createEventDispatcher } from 'svelte';
import { DEFAULT_SUBJECT_COLOR } from '$lib/constants/settings';
import { i } from '$lib/i18n/store';
import Store from '../utils/Store.svelte';
export let className: string;
let name = '';
let color = DEFAULT_SUBJECT_COLOR;
let disabled = false;
const dispatch = createEventDispatcher<{
create: {
tag: string;
color: string;
};
}>();
</script>

<div class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<TextInput placeholder={i('tags.new.placeholder')} bind:value={name} />
<ColorPreview subject={name} bind:hexColor={color} />
</div>

<div class="flex w-full">
<SettingsButton
{disabled}
on:click={async () => {
disabled = true;
await createTag({
tag: name,
class: className,
color: color.toLowerCase()
})
.then((e) => {
if (e.status === 'error') {
return sendToast({
type: 'error',
content: i('tags.new.error.alreadyTaken'),
timeout: 5_000
});
}

sendToast({
type: 'success',
content: i('tags.new.success', { tag: name }),
timeout: 5_000
});
dispatch('create', {
tag: name,
color: color.toLowerCase()
});
})
.catch(sendDefaultErrorToast);

disabled = false;
}}
>
<Store store={i('tags.new.create')} />
</SettingsButton>
</div>
</div>
31 changes: 31 additions & 0 deletions src/lib/components/tags/TagButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script context="module" lang="ts">
interface Tag {
tag: string;
color: string;
}
</script>

<script lang="ts">
import { createEventDispatcher } from 'svelte';
import TagLabel from './TagLabel.svelte';
export let tag: Tag;
export let removable = false;
export let disabled = false;
const dispatch = createEventDispatcher<{
click: Tag;
}>();
</script>

<button
{disabled}
class="disabled:opacity-50"
on:click={() => {
dispatch('click', { tag: tag.tag, color: tag.color });
}}
>
<TagLabel {tag} {removable} on:remove />
</button>
Loading

0 comments on commit fcbb207

Please sign in to comment.