Skip to content

Commit

Permalink
Merge branch 'fork-main' into fork-staging
Browse files Browse the repository at this point in the history
  • Loading branch information
Feyko committed Oct 16, 2023
2 parents bc12025 + cd782de commit ed4cc60
Show file tree
Hide file tree
Showing 4 changed files with 316 additions and 125 deletions.
3 changes: 2 additions & 1 deletion src/lib/components/mods/ModGrid.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import Select, { Option } from '@smui/select';
import { browser } from '$app/environment';
import { getTranslate } from '@tolgee/svelte';
import TagList from '$lib/components/utils/TagList.svelte';
export let colCount: 4 | 5 = 4;
export let newMod = false;
Expand Down Expand Up @@ -99,7 +100,7 @@
{#if showSearch}
<div class="search-container flex flex-wrap sm:px-4">
<div class="mr-3">
<Select bind:value={orderBy} label={$t('order-by')}>
<Select bind:value={orderBy} label="Order By">
{#each orderFields as orderField}
<Option value={orderField[1]}>{orderField[0]}</Option>
{/each}
Expand Down
231 changes: 107 additions & 124 deletions src/lib/components/utils/TagList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,43 @@
import type { Tag } from '$lib/generated/graphql';
import Chip, { Set, Text } from '@smui/chips';
import MenuSurface from '@smui/menu-surface';
import { queryStore, getContextClient } from '@urql/svelte';
import { operationStore, query } from '@urql/svelte';
import type { MenuSurfaceComponentDev } from '@smui/menu-surface/src/MenuSurface.types';
import Textfield, { Input } from '@smui/textfield';
import FloatingLabel from '@smui/floating-label';
import LineRipple from '@smui/line-ripple';
import type { InputComponentDev } from '@smui/textfield';
const client = getContextClient();
const getAllTags = queryStore({
query: GetTagsDocument,
client,
variables: {}
});
const getAllTags = operationStore(GetTagsDocument);
export let tags: Tag[] = [];
export let editable = false;
let inputA: Input;
let lineRippleA: LineRipple;
let inputA: InputComponentDev;
let shake = false;
const shake = false;
let allTags: Tag[] = [];
let filteredTagsMatched: Tag[] = [];
let filteredTagsUnmatched: Tag[] = [];
let newTagText: string;
let newTagText = '';
let newTag: HTMLInputElement;
let newTagContainer: HTMLElement = null;
let surface: MenuSurface;
let surface: MenuSurfaceComponentDev;
let focused = false;
$: newTag = inputA?.getElement();
function filterAvailableTags(tagList: Tag[], currentTags: Tag[], filterText: string): [Tag[], Tag[]] {
if (!tagList || !currentTags) {
return [tagList, tagList];
if (!tagList || !currentTags || !filterText) {
return [tagList, []];
}
console.log(filterText);
filterText = filterText.toLowerCase();
let unfiltered = tagList.filter((tag) => !currentTags.find((t) => t.id == tag.id));
const filtered = unfiltered.filter((tag) => !newTag || tag.name.startsWith(filterText));
const filtered = unfiltered.filter((tag) => !newTag || tag.name.toLowerCase().includes(filterText));
unfiltered = unfiltered.filter((tag) => filtered.findIndex((t) => t.id === tag.id) === -1);
return [filtered, unfiltered];
}
Expand All @@ -53,30 +49,14 @@
[filteredTagsMatched, filteredTagsUnmatched] = filterAvailableTags(allTags, tags, newTagText);
}
$: if (editable) {
if (!$getAllTags.fetching && !$getAllTags.error) {
allTags = $getAllTags.data?.getTags || [];
updateTags();
}
}
function setTagText(text: string) {
newTagText = text;
newTag.value = newTagText;
}
export function setTextRange(el: HTMLInputElement, start: number, end: number): void {
el.focus();
if (typeof window.getSelection != 'undefined' && typeof document.createRange != 'undefined') {
el.setSelectionRange(start, end);
}
}
export function placeCaretAtEnd(el: HTMLInputElement): void {
el.focus();
if (typeof window.getSelection != 'undefined' && typeof document.createRange != 'undefined') {
el.setSelectionRange(el.value.length, el.value.length);
}
if (editable) {
query(getAllTags);
getAllTags.subscribe(() => {
if (!getAllTags.fetching && !getAllTags.error) {
allTags = getAllTags.data.getTags;
updateTags();
}
});
}
function addTag(newTagObj: string | Tag) {
Expand Down Expand Up @@ -104,40 +84,6 @@
updateTags();
}
function newTagKeydown(e: Event) {
if (!(e instanceof KeyboardEvent)) {
return;
}
if (e.code == 'Backspace') {
if (newTag.value == '') {
setTagText(tags.pop().name);
placeCaretAtEnd(newTag);
tags = tags;
e.preventDefault();
updateTags();
}
} else if (e.code == 'Enter') {
e.preventDefault();
if (addTag(newTag.value)) {
setTagText('');
updateTags();
} else {
shake = true;
setTimeout(() => (shake = false), 500);
}
} else {
const newText = newTagText + e.key;
const [available] = filterAvailableTags(allTags, tags, newText);
if (available && available.length > 0) {
newTag.value = available[0].name;
setTextRange(newTag, newTagText.length + 1, newTag.value.length);
e.preventDefault();
newTagText = newText;
updateTags();
}
}
}
function onFocusLost() {
setTimeout(() => {
if (newTagContainer && !newTagContainer.contains(document.activeElement)) {
Expand All @@ -146,14 +92,21 @@
}, 200);
}
function onInput(e: Event) {
newTagText = newTag.value;
function onInput(e) {
//on:input is thrown before the inner text of the input is updated.
// The following if-else ensure the text we search with is up-to-date
if (e.data) {
newTagText += e.data;
} else {
newTagText = newTagText.substring(0, newTagText.length - 1);
}
updateTags();
e.preventDefault();
}
updateTags();
</script>

<div class="tags" on:focusin={() => (focused = true)} on:focusout={() => (focused = false)}>
<div class="tagHolder" on:focusin={() => (focused = true)} on:focusout={() => (focused = false)}>
{#if !editable}
{#if tags.length > 0}
<div class="flex flex-row flex-wrap text-sm gap-1">
Expand All @@ -165,66 +118,66 @@
</div>
{/if}
{:else}
<Textfield class="tags overflow-visible" bind:lineRipple={lineRippleA} bind:input={inputA} style="z-index: 9999">
<Textfield class="tags" bind:input={inputA} style="z-index: 9999">
<FloatingLabel
class="pb-2"
for="input-manual-a"
slot="label"
floatAbove={(newTag && newTag.value.length > 0) || focused || tags.length > 0}>Tags</FloatingLabel>
floatAbove={(newTag && newTag.value.length > 0) || focused || tags.length > 0}>
Tags
</FloatingLabel>
<div class="flex flex-row flex-wrap text-sm gap-1 mr-2">
{#each tags as tag}
<div class="text-neutral-300 whitespace-nowrap flex removable-tag">
<div class="text-neutral-300 flex removable-tag">
<span class="hashtag text-orange-500">#</span>
<span class="cancel">
<i
class="material-icons mdc-chip__icon mdc-chip__icon--trailing"
on:click={() => deleteTag(tag)}
on:keypress={() => deleteTag(tag)}>cancel</i>
<i class="material-icons mdc-chip__icon mdc-chip__icon--trailing" on:click={() => deleteTag(tag)}
>cancel</i>
</span>
<p>{tag.name}</p>
</div>
{/each}
<div
id="newTagContainer"
class="text-neutral-300 whitespace-nowrap flex"
bind:this={newTagContainer}
on:focusin={() => surface.setOpen(true)}
on:focusout={onFocusLost}>
<MenuSurface bind:this={surface} managed={true} anchorCorner="BOTTOM_LEFT" anchorElement={newTag}>
<div style="margin: 1rem">
<h1>Available Tags</h1>
<div class="flex flex-wrap m-1">
<Set chips={filteredTagsMatched} let:chip key={(tag) => tag.name}>
<Chip {chip} on:SMUIChip:interaction={() => addTag(chip.name)}>
<Text>{chip.name}</Text>
</Chip>
</Set>
</div>
<div class="flex flex-wrap m-1">
<Set chips={filteredTagsUnmatched} let:chip key={(tag) => tag.name}>
<Chip {chip} on:SMUIChip:interaction={() => addTag(chip.name)}>
<Text>{chip.name}</Text>
</Chip>
</Set>
</div>
</div>
</MenuSurface>
{#if focused}
<span class="text-orange-500">#</span>
{/if}
<Input
id="input-manual-a"
spellcheck="false"
autocomplete="off"
class="inline text-sm text-neutral-300 {shake ? 'shake' : ''}"
style="height: initial"
bind:this={inputA}
on:keydown={newTagKeydown}
on:input={onInput} />
</div>
</div>
<LineRipple bind:this={lineRippleA} slot="ripple" />
<div
id="newTagContainer"
class="text-neutral-300 flex"
bind:this={newTagContainer}
on:focusin={() => surface.setOpen(true)}
on:focusout={onFocusLost}>
<Input
id="input-manual-a"
spellcheck="false"
autocomplete="off"
class="addTag inline text-sm text-neutral-300 {shake ? 'shake' : ''}"
style="height: initial"
bind:this={inputA}
bind:value={newTagText}
on:input={onInput} />
</div>
</Textfield>
<MenuSurface bind:this={surface} managed={true} anchorCorner="BOTTOM_LEFT" anchorElement={newTag}>
<div style="margin: 1rem">
<h1>Available Tags</h1>
{#if filteredTagsMatched.length > 0}
<div class="flex flex-nowrap m-1">
<Set chips={filteredTagsMatched} let:chip key={(tag) => tag.name}>
<Chip {chip} on:SMUIChip:interaction={() => addTag(chip.name)}>
<Text>{chip.name}</Text>
</Chip>
</Set>
</div>
{/if}
{#if filteredTagsUnmatched.length > 0}
<div class="flex flex-nowrap m-1">
<Set class="unmatched-tag" chips={filteredTagsUnmatched} let:chip key={(tag) => tag.name}>
<Chip {chip} on:SMUIChip:interaction={() => addTag(chip.name)}>
<Text>{chip.name}</Text>
</Chip>
</Set>
</div>
{/if}
</div>
</MenuSurface>
{/if}
</div>

Expand All @@ -250,4 +203,34 @@
}
}
}
:global(.tags) {
overflow-y: visible;
}
.tagHolder {
min-width: 100px;
max-width: 400px;
overflow-y: visible;
/* width */
::-webkit-scrollbar {
width: 10px;
}
/* Track */
::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #888;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #555;
}
}
</style>
4 changes: 4 additions & 0 deletions src/routes/_global.postcss
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,7 @@ body.accessibility {
border-color: black !important;
color: black !important;
}

.unmatched-tag {
opacity: 0.6;
}
Loading

0 comments on commit ed4cc60

Please sign in to comment.