UBERF-5348: Fix new status creation (#4567)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-02-07 22:08:15 +07:00 committed by GitHub
parent 0d884fa93d
commit 6e67c57035
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 144 additions and 198 deletions

View File

@ -13,12 +13,12 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher, ComponentType } from 'svelte'
import type { Asset, IntlString } from '@hcengineering/platform'
import { deepEqual } from 'fast-equals'
import { ComponentType, createEventDispatcher } from 'svelte'
import { closePopup, showPopup } from '..'
import { AnySvelteComponent, DropdownIntlItem } from '../types'
import ButtonBase from './ButtonBase.svelte'
import { showPopup, closePopup, eventToHTMLElement } from '..'
import ModernPopup from './ModernPopup.svelte'
export let title: string | undefined = undefined

View File

@ -18,6 +18,7 @@
import plugin from '../plugin'
export let embedded = false
export let selected: string | undefined
interface Category {
id: string
@ -179,7 +180,9 @@
{#if emoji !== undefined}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="element" on:click={() => dispatch('close', emoji)}>{emoji}</div>
<div class="element" class:selected={emoji === selected} on:click={() => dispatch('close', emoji)}>
{emoji}
</div>
{/if}
{/each}
</div>
@ -250,6 +253,7 @@
&.selected {
background-color: var(--theme-popup-header);
border: 1px solid var(--theme-popup-divider);
}
}
</style>

View File

@ -13,31 +13,30 @@
// limitations under the License.
-->
<script lang="ts">
import { Asset, Metadata, getEmbeddedLabel } from '@hcengineering/platform'
import { Attribute, Class, Ref, Status, StatusCategory } from '@hcengineering/core'
import core, { Attribute, Class, Ref, Status, StatusCategory } from '@hcengineering/core'
import { Asset, getEmbeddedLabel } from '@hcengineering/platform'
import presentation, { getClient } from '@hcengineering/presentation'
import { clearSettingsStore } from '@hcengineering/setting-resources'
import { ProjectType, TaskType, calculateStatuses, createState } from '@hcengineering/task'
import {
ModernEditbox,
getColorNumberByText,
Modal,
Label,
ButtonIcon,
ButtonMenu,
EmojiPopup,
IconCopy,
IconDelete,
IconSettings,
IconWithEmoji,
Label,
Modal,
ModernEditbox,
TextArea,
getPlatformColorDef,
themeStore,
EmojiPopup,
ButtonIcon,
IconDelete,
IconCopy
themeStore
} from '@hcengineering/ui'
import { statusStore, ColorsPopup } from '@hcengineering/view-resources'
import { ColorsPopup, statusStore } from '@hcengineering/view-resources'
import view from '@hcengineering/view-resources/src/plugin'
import { clearSettingsStore } from '@hcengineering/setting-resources'
import task from '../plugin'
import { taskTypeStore } from '..'
import IconLink from './icons/Link.svelte'
import task from '../plugin'
export let status: Status | undefined = undefined
export let _class: Ref<Class<Status>> | undefined = status?._class
@ -49,13 +48,19 @@
export let color: number | undefined = undefined
export let icons: Asset[]
export let iconWithEmoji: Asset = view.ids.IconWithEmoji
export let icon: Asset | undefined
const client = getClient()
let description: string | undefined = status?.description
$: allowEditCategory = status === undefined
$: needUpdate =
(status?.name.trim() !== value.trim() || description !== status?.description || color !== status?.color) &&
(status === undefined ||
status.name.trim() !== value.trim() ||
description !== status?.description ||
color !== status.color) &&
value.trim() !== ''
async function save (): Promise<void> {
@ -73,6 +78,13 @@
const lastIndex = states.findLastIndex((p) => p.category === category)
const statuses = [...taskType.statuses.slice(0, lastIndex + 1), _id, ...taskType.statuses.slice(lastIndex + 1)]
type.statuses.push({
_id,
color,
icon,
taskType: taskType._id
})
await client.update(type, {
statuses: calculateStatuses(type, $taskTypeStore, [{ taskTypeId: taskType._id, statuses }])
})
@ -89,35 +101,42 @@
})
const index = taskType.statuses.indexOf(status._id)
const statuses = [...taskType.statuses.slice(0, index), _id, ...taskType.statuses.slice(index + 1)]
for (const status of type.statuses) {
if (status._id === _id) {
status.color = color
status.icon = icon as any // Fix me
}
}
await client.update(type, {
statuses: calculateStatuses(type, $taskTypeStore, [{ taskTypeId: taskType._id, statuses }])
})
await client.update(taskType, {
statuses
})
const projects = await client.findAll(task.class.Project, { type: type._id })
while (true) {
const docs = await client.findAll(
task.class.Task,
{
status: status._id,
space: { $in: projects.map((p) => p._id) }
},
{ limit: 1000 }
)
if (docs.length === 0) {
break
}
if (status._id !== _id) {
const projects = await client.findAll(task.class.Project, { type: type._id })
while (true) {
const docs = await client.findAll(
task.class.Task,
{
status: status._id,
space: { $in: projects.map((p) => p._id) }
},
{ limit: 1000 }
)
if (docs.length === 0) {
break
}
const op = client.apply(_id)
docs.map((p) => op.update(p, { status: _id }))
await op.commit()
const op = client.apply(_id)
docs.map((p) => op.update(p, { status: _id }))
await op.commit()
}
}
}
clearSettingsStore()
}
let selected: number = 0
let selected: number = icon === iconWithEmoji ? 1 : 0
const items = [
{
id: 'color',
@ -128,12 +147,22 @@
label: view.string.EmojiCategory
}
]
$: allCategories = getClient()
.getModel()
.findAllSync(core.class.StatusCategory, { _id: { $in: taskType.statusCategories } })
$: categories = allCategories.map((it) => ({
id: it._id,
label: it.label,
icon: it.icon
}))
</script>
<Modal
label={task.string.StatusPopupTitle}
type={'type-aside'}
okLabel={presentation.string.Save}
okLabel={status === undefined ? presentation.string.Create : presentation.string.Save}
okAction={save}
canSave={needUpdate}
onCancel={() => {
@ -157,28 +186,19 @@
</div>
<div class="hulyModal-content__settingsSet">
<div class="hulyModal-content__settingsSet-line">
<span class="label"><Label label={task.string.Group} /></span>
<!-- <ButtonMenu
{items}
{selected}
icon={IconLink}
label={getEmbeddedLabel('Compeletd')}
<span class="label"><Label label={getEmbeddedLabel('Status Category')} /></span>
<ButtonMenu
items={categories}
selected={category}
disabled={!allowEditCategory}
icon={categories.find((it) => it.id === category)?.icon}
label={categories.find((it) => it.id === category)?.label}
kind={'secondary'}
size={'medium'}
on:selected={() => {}}
/> -->
</div>
<div class="hulyModal-content__settingsSet-line">
<span class="label"><Label label={task.string.Type} /></span>
<!-- <ButtonMenu
{items}
{selected}
icon={IconLink}
label={getEmbeddedLabel('Success')}
kind={'secondary'}
size={'medium'}
on:selected={() => {}}
/> -->
on:selected={(it) => {
category = it.detail
}}
/>
</div>
</div>
<div class="hulyModal-content__settingsSet table">
@ -192,9 +212,17 @@
kind={'secondary'}
size={'small'}
on:selected={(event) => {
if (event.detail) selected = items.findIndex((it) => it.id === event.detail)
if (event.detail) {
selected = items.findIndex((it) => it.id === event.detail)
if (selected === 1) {
icon = undefined
}
}
}}
/>
{#if icon === iconWithEmoji}
<IconWithEmoji icon={color ?? 0} size={'medium'} />
{/if}
</div>
<div class="hulyTableAttr-content" class:mb-2={selected === 1}>
{#if selected === 0}
@ -204,13 +232,16 @@
columns={'auto'}
on:close={(evt) => {
color = evt.detail
icon = undefined
}}
/>
{:else}
<EmojiPopup
embedded
selected={String.fromCodePoint(color ?? 0)}
on:close={(evt) => {
color = evt.detail.codePointAt(0)
icon = iconWithEmoji
}}
/>
{/if}

View File

@ -15,6 +15,7 @@
-->
<script lang="ts">
import core, { IdMap, Ref, Status, StatusCategory } from '@hcengineering/core'
import { Asset } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import task, { Project, ProjectType, TaskType } from '@hcengineering/task'
import {
@ -31,10 +32,10 @@
import { createEventDispatcher, onMount } from 'svelte'
import { typeStore } from '../..'
import IconBacklog from '../icons/IconBacklog.svelte'
import IconUnstarted from '../icons/IconUnstarted.svelte'
import IconCanceled from '../icons/IconCanceled.svelte'
import IconCompleted from '../icons/IconCompleted.svelte'
import IconStarted from '../icons/IconStarted.svelte'
import IconUnstarted from '../icons/IconUnstarted.svelte'
export let value: Status | undefined
export let shouldShowAvatar = true
@ -80,20 +81,10 @@
$: projectState = type?.statuses.find((p) => p._id === value?._id)
const dispatchAccentColor = (color?: ColorDefinition): void => {
dispatch('accent-color', color)
}
$: color = getPlatformColorDef(
projectState?.color ?? category?.color ?? getColorNumberByText(value?.name ?? ''),
$themeStore.dark
)
$: dispatchAccentColor(color)
onMount(() => {
dispatchAccentColor(color)
})
$: void updateCategory(value)
async function updateCategory (value: Status | undefined): Promise<void> {
@ -119,8 +110,21 @@
)
$: index = sameCategory.findIndex((it) => it._id === value?._id) + 1
$: icon = projectState?.icon === view.ids.IconWithEmoji ? IconWithEmoji : projectState?.icon
const dispatchAccentColor = (color?: ColorDefinition, icon?: Asset | typeof IconWithEmoji): void => {
if (icon === undefined) {
dispatch('accent-color', color)
} else {
dispatch('accent-color', null)
}
}
$: dispatchAccentColor(color, icon)
onMount(() => {
dispatchAccentColor(color, icon)
})
</script>
{#if value}

View File

@ -13,36 +13,21 @@
// limitations under the License.
-->
<script lang="ts">
import core, { Attribute, IdMap, Ref, Status, StatusCategory, toIdMap } from '@hcengineering/core'
import core, { IdMap, Ref, Status, StatusCategory, toIdMap } from '@hcengineering/core'
import { Asset } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { ProjectStatus, ProjectType, TaskType, findStatusAttr } from '@hcengineering/task'
import {
CircleButton,
ColorDefinition,
IconOpenedArrow,
IconMoreV2,
IconMoreH,
Label,
defaultBackground,
eventToHTMLElement,
getColorNumberByText,
getPlatformColorDef,
showPopup,
themeStore
} from '@hcengineering/ui'
import { ColorsPopup, IconPicker, ObjectPresenter, statusStore } from '@hcengineering/view-resources'
import { createQuery } from '@hcengineering/presentation'
import { settingsStore } from '@hcengineering/setting-resources'
import { ProjectStatus, ProjectType, TaskType } from '@hcengineering/task'
import { IconMoreV2, IconOpenedArrow, Label } from '@hcengineering/ui'
import { ObjectPresenter } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import task from '../../plugin'
import StatusesPopup from './StatusesPopup.svelte'
import { settingsStore, clearSettingsStore } from '@hcengineering/setting-resources'
export let taskType: TaskType
export let type: ProjectType
export let states: Status[] = []
const dispatch = createEventDispatcher()
const client = getClient()
const elements: HTMLElement[] = []
let selected: number | undefined
@ -74,42 +59,6 @@
})
}
function onColor (state: Status, color: ColorDefinition, el: HTMLElement): void {
showPopup(ColorsPopup, { selected: color.name }, el, async (res) => {
if (res == null) {
return
}
const targetColors = type.statuses.filter((p) => p._id === state._id)
for (const targetColor of targetColors) {
targetColor.color = res
}
await client.update(type, { statuses: type.statuses })
type = type
})
}
function add (ofAttribute: Ref<Attribute<Status>> | undefined, cat: Ref<StatusCategory>): void {
if (ofAttribute === undefined) {
return
}
showPopup(task.component.CreateStatePopup, {
ofAttribute,
_class: taskType.statusClass,
category: cat,
taskType,
type
})
}
function edit (status: Status): void {
showPopup(task.component.CreateStatePopup, {
status,
taskType,
type,
ofAttribute: status.ofAttribute
})
}
let categories: StatusCategory[] = []
let categoriesMap: IdMap<StatusCategory> = new Map()
let groups = new Map<Ref<StatusCategory>, Status[]>()
@ -119,35 +68,10 @@
categoriesMap = toIdMap(res)
})
function click (ev: MouseEvent, state: Status): void {
showPopup(
StatusesPopup,
{
onDelete: () => dispatch('delete', { state }),
showDelete: states.filter((it) => it.category === state.category).length > 1,
onUpdate: () => {
edit(state)
}
},
eventToHTMLElement(ev),
() => {}
)
}
function getProjectStatus (
type: ProjectType,
state: Status,
categoriesMap: IdMap<StatusCategory>
): ProjectStatus | undefined {
function getProjectStatus (type: ProjectType, state: Status): ProjectStatus | undefined {
return type.statuses.find((p) => p._id === state._id)
}
function getColor (type: ProjectType, state: Status, categoriesMap: IdMap<StatusCategory>): ColorDefinition {
const category = state.category !== undefined ? categoriesMap.get(state.category) : undefined
const targetColor = getProjectStatus(type, state, categoriesMap)?.color ?? state.color ?? category?.color
return getPlatformColorDef(targetColor ?? getColorNumberByText(state.name), $themeStore.dark)
}
function group (categories: StatusCategory[], states: Status[]): Map<Ref<StatusCategory>, Status[]> {
const map = new Map<Ref<StatusCategory>, Status[]>(categories.map((p) => [p._id, []]))
for (const state of states) {
@ -171,23 +95,6 @@
}
return index
}
function selectIcon (el: HTMLElement, state: Status): void {
const icons: Asset[] = []
const projectStatus = getProjectStatus(type, state, categoriesMap)
showPopup(IconPicker, { icon: projectStatus?.icon, color: projectStatus?.color, icons }, el, async (result) => {
if (result !== undefined && result !== null) {
const targetColors = type.statuses.filter((p) => p._id === state._id)
for (const targetColor of targetColors) {
targetColor.color = result.color
targetColor.icon = result.icon
}
if (targetColors.length > 0) {
await client.update(type, { statuses: type.statuses })
type = type
}
}
})
}
settingsStore.subscribe((value) => {
if ((value.id === undefined && opened !== undefined) || (value.id !== undefined && value.id !== opened)) {
opened = undefined
@ -197,7 +104,9 @@
if (opened === undefined || opened !== _status._id) {
opened = _status._id
const icons: Asset[] = []
const projectStatus = getProjectStatus(type, _status, categoriesMap)
const category = _status.category !== undefined ? categoriesMap.get(_status.category) : undefined
const projectStatus = getProjectStatus(type, _status)
const color = getProjectStatus(type, _status)?.color ?? _status.color ?? category?.color
$settingsStore = {
id: opened,
component: task.component.CreateStatePopup,
@ -207,13 +116,10 @@
type,
ofAttribute: _status.ofAttribute,
icon: projectStatus?.icon,
color: projectStatus?.color,
color,
icons
}
}
} else if (opened === _status._id) {
clearSettingsStore()
opened = undefined
}
}
</script>
@ -224,27 +130,15 @@
<div class="hulyTableAttr-content class withTitle">
<div class="hulyTableAttr-content__title">
<Label label={cat.label} />
<!-- <CircleButton
icon={IconAdd}
size={'medium'}
on:click={() => {
add(findStatusAttr(getClient().getHierarchy(), taskType.ofClass)?._id, cat._id)
}}
/> -->
</div>
<div class="hulyTableAttr-content__wrapper">
{#each states as state, i}
{@const color = getColor(type, state, categoriesMap)}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- style:background={color.background ?? defaultBackground($themeStore.dark)} -->
<button
bind:this={elements[prevIndex + i]}
class="hulyTableAttr-content__row"
class:selected={state._id === opened}
draggable={true}
on:contextmenu|preventDefault={(e) => {
click(e, state)
}}
on:click={() => {
handleSelect(state)
}}
@ -270,13 +164,6 @@
objectId={state._id}
value={state}
props={{ projectType: type._id, taskType: taskType._id, kind: 'table-attrs' }}
on:click={(ev) => {
if (state.category !== undefined) {
selectIcon(elements[i + prevIndex], state)
} else {
onColor(state, color, elements[i + prevIndex])
}
}}
/>
<div class="hulyTableAttr-content__row-arrow">
<IconOpenedArrow size={'small'} />

View File

@ -15,12 +15,12 @@
-->
<script lang="ts">
import { AttributeEditor, getClient } from '@hcengineering/presentation'
import task, { ProjectType, TaskType, calculateStatuses } from '@hcengineering/task'
import task, { ProjectType, TaskType, calculateStatuses, findStatusAttr } from '@hcengineering/task'
import { Ref, Status } from '@hcengineering/core'
import { Asset, getEmbeddedLabel } from '@hcengineering/platform'
import { Label, showPopup, ButtonIcon, ModernButton, IconSquareExpand, IconAdd, Icon } from '@hcengineering/ui'
import { IconPicker, statusStore } from '@hcengineering/view-resources'
import { ClassAttributes } from '@hcengineering/setting-resources'
import { ClassAttributes, settingsStore } from '@hcengineering/setting-resources'
import { taskTypeStore } from '../..'
import StatesProjectEditor from '../state/StatesProjectEditor.svelte'
import TaskTypeKindEditor from '../taskTypes/TaskTypeKindEditor.svelte'
@ -52,6 +52,25 @@
}
)
}
function handleAddStatus (el: MouseEvent): void {
const icons: Asset[] = []
const attr = findStatusAttr(getClient().getHierarchy(), taskType.ofClass)
$settingsStore = {
id: '#',
component: task.component.CreateStatePopup,
props: {
status: undefined,
taskType,
_class: taskType.statusClass,
category: task.statusCategory.Active,
type: projectType,
ofAttribute: attr,
icon: undefined,
color: 0,
icons
}
}
}
</script>
<div class="hulyComponent-content__column-group mt-4">
@ -105,7 +124,7 @@
<div class="hulyTableAttr-header font-medium-12">
<Icon icon={task.icon.ManageTemplates} size={'small'} />
<span><Label label={plugin.string.ProcessStates} /></span>
<ButtonIcon kind={'primary'} icon={IconAdd} size={'small'} on:click={(ev) => {}} />
<ButtonIcon kind={'primary'} icon={IconAdd} size={'small'} on:click={handleAddStatus} />
</div>
<StatesProjectEditor
{taskType}

View File

@ -75,6 +75,7 @@
{:else}
<EmojiPopup
embedded
selected={String.fromCodePoint(color ?? 0)}
on:close={(evt) => {
dispatch('close', { icon: iconWithEmoji, color: evt.detail.codePointAt(0) })
}}