Improve TagsPopup (#4879)

Signed-off-by: Stepan Grigorovich <gsdstr@gmail.com>
This commit is contained in:
Stepan Grigorovich 2024-03-14 05:50:36 +03:00 committed by GitHub
parent 9ebdb9a985
commit 768d57348b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 98 additions and 34 deletions

View File

@ -16,6 +16,7 @@
"TagCreateLabel": "Tag",
"CancelLabel": "Cancel",
"SearchCreate": "Search/Name ...",
"QuickAddItems": "Quick Add {word} \"{title}\"",
"NoItems": "There is no {word} added ...",
"TagDescriptionLabel": "Description",
"TagDescriptionPlaceholder": "Please type description here",

View File

@ -16,6 +16,7 @@
"TagCreateLabel": "Etiqueta",
"CancelLabel": "Cancelar",
"SearchCreate": "Buscar/Nombre...",
"QuickAddItems": "Agregar {word} rápidamente \"{title}\"",
"NoItems": "No hay ninguna {word} añadida...",
"TagDescriptionLabel": "Descripción",
"TagDescriptionPlaceholder": "Escriba la descripción aquí",

View File

@ -16,6 +16,7 @@
"TagCreateLabel": "Etiqueta",
"CancelLabel": "Cancelar",
"SearchCreate": "Pesquisar/Nome...",
"QuickAddItems": "{word} de adição rápida \"{title}\"",
"NoItems": "Não há nenhuma {word} adicionada...",
"TagDescriptionLabel": "Descrição",
"TagDescriptionPlaceholder": "Escreva a descrição aqui",

View File

@ -16,6 +16,7 @@
"TagCreateLabel": "Тег",
"CancelLabel": "Отмена",
"SearchCreate": "Название...",
"QuickAddItems": "Быстрое добавление {word} \"{title}\"",
"NoItems": "Отсутствует добавленный {word}",
"TagDescriptionLabel": "Описание",
"TagDescriptionPlaceholder": "Пожалуйста, введите описание",

View File

@ -13,9 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import { Class, Data, Doc, generateId, Ref } from '@hcengineering/core'
import { Card, createQuery, getClient } from '@hcengineering/presentation'
import { findTagCategory, TagCategory, TagElement } from '@hcengineering/tags'
import { Class, Doc, Ref } from '@hcengineering/core'
import { Card, createQuery } from '@hcengineering/presentation'
import { findTagCategory, TagCategory } from '@hcengineering/tags'
import {
Button,
DropdownLabels,
@ -31,14 +31,14 @@
import { ColorsPopup } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import tags from '../plugin'
import { getTagStyle } from '../utils'
import { createTagElement, getTagStyle } from '../utils'
export let targetClass: Ref<Class<Doc>>
export let keyTitle: string = ''
export let title: string = ''
let title = ''
let description = ''
let color: number = 0
let color: number = getColorNumberByText(title)
let categoryWasSet = false
let category: Ref<TagCategory> | undefined
@ -61,8 +61,6 @@
}
const dispatch = createEventDispatcher()
const client = getClient()
const tagElementId = generateId()
const query = createQuery()
@ -78,19 +76,12 @@
categoryItems = newItems
})
async function createTagElenent () {
const tagElement: Data<TagElement> = {
title,
description,
targetClass,
color,
category: category ?? tags.category.NoCategory
}
await client.createDoc(tags.class.TagElement, tags.space.Tags, tagElement, tagElementId)
dispatch('close')
async function createTagElementFnc (): Promise<void> {
const res = await createTagElement(title, targetClass, category, description, color)
dispatch('close', res)
}
const showColorPopup = (evt: MouseEvent) => {
const showColorPopup = (evt: MouseEvent): void => {
showPopup(
ColorsPopup,
{ selected: getPlatformColorDef(color, $themeStore.dark).name },
@ -108,7 +99,7 @@
<Card
label={tags.string.AddTag}
labelProps={{ word: keyTitle }}
okAction={createTagElenent}
okAction={createTagElementFnc}
canSave={title.length > 0}
on:close={() => {
dispatch('close')

View File

@ -16,7 +16,7 @@
import { Class, Doc, Ref } from '@hcengineering/core'
import type { IntlString } from '@hcengineering/platform'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { TagCategory, TagElement } from '@hcengineering/tags'
import { findTagCategory, TagCategory, TagElement } from '@hcengineering/tags'
import {
Button,
EditWithIcon,
@ -36,6 +36,7 @@
import CreateTagElement from './CreateTagElement.svelte'
import IconView from './icons/View.svelte'
import IconViewHide from './icons/ViewHide.svelte'
import { createTagElement } from '../utils'
export let newElements: TagElement[] = []
export let targetClass: Ref<Class<Doc>>
@ -52,12 +53,13 @@
let objects: TagElement[] = []
let categories: TagCategory[] = []
let isSingleCategory = true
let inProcess = false
const dispatch = createEventDispatcher()
const query = createQuery()
const client = getClient()
client.findAll(tags.class.TagCategory, { targetClass }).then((res) => {
void client.findAll(tags.class.TagCategory, { targetClass }).then((res) => {
categories = res
isSingleCategory = categories.length <= 1
})
@ -67,8 +69,24 @@
objects = newElements.concat(result)
})
async function createTagElement (): Promise<void> {
showPopup(CreateTagElement, { targetClass }, 'top')
async function onCreateTagElement (res: any): Promise<void> {
if (res === null) return
setTimeout(() => {
const tag = objects.findLast((e) => e._id === res)
if (tag === undefined) return
selected = [...selected, tag._id]
dispatch('update', { action: 'add', tag })
inProcess = false
}, 1)
}
async function createTagElementPopup (): Promise<void> {
showPopup(CreateTagElement, { targetClass, title: search }, 'top', onCreateTagElement)
}
async function createTagElementQuick (): Promise<void> {
const res = await createTagElement(search, targetClass, findTagCategory(search, categories))
await onCreateTagElement(res)
}
const isSelected = (selected: Ref<TagElement>[], element: TagElement): boolean => {
@ -94,13 +112,23 @@
if (count > 0) return count.toString()
return ''
}
const tagSort = (a: TagElement, b: TagElement) => {
const tagSort = (a: TagElement, b: TagElement): number => {
const r = (b.refCount ?? 0) - (a.refCount ?? 0)
if (r === 0) {
return b.title.localeCompare(a.title)
}
return r
}
async function onSearchKeydown (ev: KeyboardEvent): Promise<void> {
if (ev.code !== 'Enter') return
if (!inProcess && objects.length < 1) {
inProcess = true
await createTagElementQuick()
ev.preventDefault()
}
// TODO add first element or group?
}
</script>
<div class="selectPopup maxHeight" use:resizeObserver={() => dispatch('changeContent')}>
@ -114,6 +142,7 @@
{placeholder}
{placeholderParam}
on:change
on:keydown={onSearchKeydown}
/>
{#if !isSingleCategory}
<Button
@ -125,7 +154,7 @@
}}
/>
{/if}
{#if !hideAdd}<Button kind={'ghost'} size={'large'} icon={IconAdd} on:click={createTagElement} />{/if}
{#if !hideAdd}<Button kind={'ghost'} size={'large'} icon={IconAdd} on:click={createTagElementPopup} />{/if}
</div>
<div class="scroll">
<div class="box">
@ -190,6 +219,11 @@
{/if}
{/each}
{#if objects.length === 0}
{#if !hideAdd}
<button class="menu-item focus flex-row-center" on:click={createTagElementQuick}>
<Label label={tags.string.QuickAddItems} params={{ word: keyLabel, title: search }} />
</button>
{/if}
<div class="empty">
<Label label={tags.string.NoItems} params={{ word: keyLabel }} />
</div>

View File

@ -29,6 +29,7 @@ export default mergeIds(tagsId, tags, {
AddNowTooltip: '' as IntlString,
CancelLabel: '' as IntlString,
SearchCreate: '' as IntlString,
QuickAddItems: '' as IntlString,
NoItems: '' as IntlString,
TagDescriptionLabel: '' as IntlString,
TagDescriptionPlaceholder: '' as IntlString,

View File

@ -1,13 +1,22 @@
// Copyright © 2022 Hardcore Engineering Inc.
import { type Doc, type DocumentQuery, type FindResult, type Ref } from '@hcengineering/core'
import {
type Class,
type Data,
type Doc,
type DocumentQuery,
type FindResult,
generateId,
type Ref
} from '@hcengineering/core'
import { type Asset } from '@hcengineering/platform'
import { type TagElement, type InitialKnowledge, type TagReference } from '@hcengineering/tags'
import { type ColorDefinition } from '@hcengineering/ui'
import { type TagElement, type InitialKnowledge, type TagReference, type TagCategory } from '@hcengineering/tags'
import { type ColorDefinition, getColorNumberByText } from '@hcengineering/ui'
import { type Filter } from '@hcengineering/view'
import { FilterQuery } from '@hcengineering/view-resources'
import tags from './plugin'
import { writable } from 'svelte/store'
import { getClient } from '@hcengineering/presentation'
export function getTagStyle (color: ColorDefinition, selected = false): string {
return `
@ -60,3 +69,26 @@ export interface TagElementInfo {
* @public
*/
export const selectedTagElements = writable<Array<Ref<TagElement>>>([])
/**
* @public
*/
export async function createTagElement (
title: string,
targetClass: Ref<Class<Doc>>,
category?: Ref<TagCategory> | null,
description?: string | null,
color?: number | null
): Promise<any> {
const tagElement: Data<TagElement> = {
title,
description: description ?? '',
targetClass,
color: color ?? getColorNumberByText(title),
category: category ?? tags.category.NoCategory
}
const client = getClient()
const tagElementId = generateId()
return await client.createDoc(tags.class.TagElement, tags.space.Tags, tagElement, tagElementId)
}

View File

@ -39,7 +39,6 @@ export class TalentDetailsPage extends CommonRecruitingPage {
await this.addNewTagPopup(this.page, skillTag, skillDescription)
await this.pressShowAllButtonSelectPopup(this.page)
await this.checkFromDropdown(this.page, skillTag)
await this.page.keyboard.press('Escape')
}

View File

@ -72,9 +72,10 @@ export class IssuesDetailsPage extends CommonTrackerPage {
await this.buttonAddLabel.click()
await this.pressCreateButtonSelectPopup(this.page)
await this.addNewTagPopup(this.page, data.labels, 'Tag from editIssue')
} else {
await this.checkFromDropdownWithSearch(this.page, data.labels)
}
await this.checkFromDropdownWithSearch(this.page, data.labels)
await this.inputTitle.click({ force: true })
await this.inputTitle.press('Escape')
}
if (data.component != null) {
await this.buttonComponent.click()

View File

@ -110,8 +110,10 @@ export class IssuesPage extends CommonTrackerPage {
if (data.createLabel) {
await this.pressCreateButtonSelectPopup(this.page)
await this.addNewTagPopup(this.page, data.labels, 'Tag from createNewIssue')
} else {
await this.checkFromDropdown(this.page, data.labels)
}
await this.checkFromDropdown(this.page, data.labels)
await this.inputPopupCreateNewIssueTitle.press('Escape')
await this.inputPopupCreateNewIssueTitle.click({ force: true })
}
if (data.component != null) {