Skip to content

Commit

Permalink
fix(systemtags): enhance create tag in tag picker UX
Browse files Browse the repository at this point in the history
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
  • Loading branch information
skjnldsv committed Nov 15, 2024
1 parent d61d62b commit 99a7346
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 23 deletions.
78 changes: 57 additions & 21 deletions apps/systemtags/src/components/SystemTagPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,16 @@

<template v-else>
<!-- Search or create input -->
<form class="systemtags-picker__create" @submit.stop.prevent="onNewTag">
<div class="systemtags-picker__input">
<NcTextField :value.sync="input"
:label="t('systemtags', 'Search or create tag')"
data-cy-systemtags-picker-input>
<TagIcon :size="20" />
</NcTextField>
<NcButton :disabled="status === Status.CREATING_TAG"
native-type="submit"
data-cy-systemtags-picker-input-submit>
{{ t('systemtags', 'Create tag') }}
</NcButton>
</form>
</div>

<!-- Tags list -->
<div v-if="filteredTags.length > 0"
class="systemtags-picker__tags"
<div class="systemtags-picker__tags"
data-cy-systemtags-picker-tags>
<NcCheckboxRadioSwitch v-for="tag in filteredTags"
:key="tag.id"
Expand All @@ -46,15 +40,25 @@
:indeterminate="isIndeterminate(tag)"
:disabled="!tag.canAssign"
:data-cy-systemtags-picker-tag="tag.id"
class="systemtags-picker__tag"
@update:checked="onCheckUpdate(tag, $event)">
{{ formatTagName(tag) }}
</NcCheckboxRadioSwitch>
<NcButton v-if="canCreateTag"
:disabled="status === Status.CREATING_TAG"
alignment="start"
class="systemtags-picker__tag-create"
native-type="submit"
type="tertiary"
data-cy-systemtags-picker-button-create
@click="onNewTag">
{{ input.trim() }}<br>
<span class="systemtags-picker__tag-create-subline">{{ t('systemtags', 'Create new tag') }}</span>
<template #icon>
<PlusIcon />
</template>
</NcButton>
</div>
<NcEmptyContent v-else :name="t('systemtags', 'No tags found')">
<template #icon>
<TagIcon />
</template>
</NcEmptyContent>

<!-- Note -->
<div class="systemtags-picker__note">
Expand Down Expand Up @@ -113,6 +117,7 @@ import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import TagIcon from 'vue-material-design-icons/Tag.vue'
import CheckIcon from 'vue-material-design-icons/CheckCircle.vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'

import { getNodeSystemTags, setNodeSystemTags } from '../utils'
import { createTag, fetchTag, fetchTags, getTagObjects, setTagObjects } from '../services/api'
Expand Down Expand Up @@ -143,6 +148,7 @@ export default defineComponent({
NcLoadingIcon,
NcNoteCard,
NcTextField,
PlusIcon,
TagIcon,
},

Expand Down Expand Up @@ -176,19 +182,29 @@ export default defineComponent({
},

computed: {
sortedTags(): TagWithId[] {
return [...this.tags]
.sort((a, b) => a.displayName.localeCompare(b.displayName, getLanguage(), { ignorePunctuation: true }))
},

filteredTags(): TagWithId[] {
if (this.input.trim() === '') {
return this.tags
return this.sortedTags
}

return this.tags
return this.sortedTags
.filter(tag => tag.displayName.normalize().includes(this.input.normalize()))
},

hasChanges(): boolean {
return this.toAdd.length > 0 || this.toRemove.length > 0
},

canCreateTag(): boolean {
return this.input.trim() !== ''
&& !this.tags.some(tag => tag.displayName.trim().toLocaleLowerCase() === this.input.trim().toLocaleLowerCase())
},

statusMessage(): string {
if (this.toAdd.length === 0 && this.toRemove.length === 0) {
// should not happen™
Expand All @@ -199,7 +215,7 @@ export default defineComponent({
return n(
'systemtags',
'{tag1} will be set and {tag2} will be removed from 1 file.',
'{tag1} and {tag2} will be set and removed from {count} files.',
'{tag1} will be set and {tag2} will be removed from {count} files.',
this.nodes.length,
{
tag1: this.formatTagChip(this.toAdd[0]),
Expand Down Expand Up @@ -368,6 +384,15 @@ export default defineComponent({

// Check the newly created tag
this.onCheckUpdate(tag, true)

// Scroll to the newly created tag
await this.$nextTick()
const newTagEl = this.$el.querySelector(`input[type="checkbox"][label="${tag.displayName}"]`)
newTagEl?.scrollIntoView({
behavior: 'instant',
block: 'center',
inline: 'center',
})
} catch (error) {
showError((error as Error)?.message || t('systemtags', 'Failed to create tag'))
} finally {
Expand Down Expand Up @@ -461,22 +486,33 @@ export default defineComponent({

<style scoped lang="scss">
// Common sticky properties
.systemtags-picker__create,
.systemtags-picker__input,
.systemtags-picker__note {
position: sticky;
z-index: 9;
background-color: var(--color-main-background);
}

.systemtags-picker__create {
.systemtags-picker__input {
display: flex;
top: 0;
gap: 8px;
padding-block-end: 8px;
align-items: flex-end;
}

button {
flex-shrink: 0;
.systemtags-picker__tags {
padding-block: 8px;
gap: var(--default-grid-baseline);
display: flex;
flex-direction: column;
.systemtags-picker__tag-create {
:deep(span) {
text-align: start;
}
&-subline {
font-weight: normal;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion apps/systemtags/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const fetchTag = async (tagId: number): Promise<TagWithId> => {
try {
const { data: tag } = await davClient.stat(path, {
data: fetchTagsPayload,
details: true
details: true,
}) as ResponseDataDetailed<Required<FileStat>>
return parseTags([tag])[0]
} catch (error) {
Expand Down
5 changes: 4 additions & 1 deletion cypress/e2e/systemtags/files-bulk-action.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,10 @@ describe('Systemtags: Files bulk action', { testIsolation: false }, () => {

const newTag = randomBytes(8).toString('base64').slice(0, 6)
cy.get('[data-cy-systemtags-picker-input]').type(newTag)
cy.get('[data-cy-systemtags-picker-input-submit]').click()

cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 0)
cy.get('[data-cy-systemtags-picker-button-create]').should('be.visible')
cy.get('[data-cy-systemtags-picker-button-create]').click()

cy.wait('@createTag')
cy.get('[data-cy-systemtags-picker-tag]').should('have.length', 6)
Expand Down

0 comments on commit 99a7346

Please sign in to comment.