mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-07 18:27:44 +02:00
UBER-619: StatusPopup for creating/renaming (#3536)
Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
<!--
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { EditBox, Label } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import task from '../plugin'
|
||||
import presentation, { Card, getClient } from '@hcengineering/presentation'
|
||||
import { calcRank, DoneState, Kanban, KanbanTemplate, KanbanTemplateSpace, State } from '@hcengineering/task'
|
||||
import { Class, Data, generateId, Ref, SortingOrder } from '@hcengineering/core'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
export let status: State | undefined = undefined
|
||||
export let _class: Ref<Class<State | DoneState>> | undefined = status?._class
|
||||
export let template: KanbanTemplate | undefined = undefined
|
||||
export let value = status?.name ?? ''
|
||||
const isTemplate = template !== undefined
|
||||
export let space: Kanban | KanbanTemplateSpace | undefined
|
||||
|
||||
let canSave = true
|
||||
async function save () {
|
||||
if (space === undefined && template === undefined && status?.space === undefined) return
|
||||
const attachedTo = isTemplate && template?._id ? { attachedTo: template._id } : {}
|
||||
const kanban = space as Kanban
|
||||
if (_class !== undefined && status === undefined) {
|
||||
const query = isTemplate ? { ...attachedTo } : kanban?.attachedTo ? { space: kanban.attachedTo } : {}
|
||||
const lastOne = await client.findOne(_class, query, { sort: { rank: SortingOrder.Descending } })
|
||||
let newDoc: Data<State> = {
|
||||
ofAttribute: task.attribute.State,
|
||||
name: value.trim(),
|
||||
rank: calcRank(lastOne, undefined),
|
||||
...attachedTo
|
||||
}
|
||||
if (!hierarchy.isDerived(_class, task.class.DoneState)) {
|
||||
newDoc = {
|
||||
ofAttribute: task.attribute.State,
|
||||
name: value.trim(),
|
||||
color: 9,
|
||||
rank: calcRank(lastOne, undefined),
|
||||
...attachedTo
|
||||
}
|
||||
}
|
||||
const ops = client.apply(template?.space ?? kanban?.attachedTo ?? generateId()).notMatch(_class, {
|
||||
space: isTemplate && template ? template.space : kanban?.attachedTo,
|
||||
name: value.trim(),
|
||||
...attachedTo
|
||||
})
|
||||
await ops.createDoc(_class, isTemplate && template ? template.space : kanban?.attachedTo, newDoc)
|
||||
canSave = await ops.commit()
|
||||
}
|
||||
if (status !== undefined && _class !== undefined) {
|
||||
const ops = client.apply(status._id).notMatch(_class, { space: status.space, name: value.trim(), ...attachedTo })
|
||||
await ops.update(status, { name: value.trim() })
|
||||
canSave = await ops.commit()
|
||||
}
|
||||
if (canSave) dispatch('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={task.string.StatusPopupTitle}
|
||||
okAction={save}
|
||||
canSave
|
||||
okLabel={presentation.string.Save}
|
||||
on:changeContent
|
||||
onCancel={() => dispatch('close')}
|
||||
>
|
||||
<EditBox focusIndex={1} bind:value placeholder={task.string.StatusName} kind={'large-style'} autoFocus fullSize />
|
||||
<svelte:fragment slot="error">
|
||||
{#if !canSave}
|
||||
<Label label={task.string.NameAlreadyExists} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Card>
|
||||
@@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Class, Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import type { DoneState, Kanban, State } from '@hcengineering/task'
|
||||
import task, { calcRank } from '@hcengineering/task'
|
||||
@@ -31,7 +31,6 @@
|
||||
$: lostStates = doneStates.filter((x) => x._class === task.class.LostState)
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const statesQ = createQuery()
|
||||
$: statesQ.query(
|
||||
@@ -73,33 +72,6 @@
|
||||
rank: calcRank(prev, next)
|
||||
})
|
||||
}
|
||||
|
||||
async function onAdd (_class: Ref<Class<State | DoneState>>) {
|
||||
const lastOne = await client.findOne(_class, {}, { sort: { rank: SortingOrder.Descending } })
|
||||
if (hierarchy.isDerived(_class, task.class.DoneState)) {
|
||||
await client.createDoc(_class, kanban.space, {
|
||||
ofAttribute: task.attribute.State,
|
||||
name: 'New Done State',
|
||||
rank: calcRank(lastOne, undefined)
|
||||
})
|
||||
} else {
|
||||
await client.createDoc(task.class.State, kanban.space, {
|
||||
ofAttribute: task.attribute.State,
|
||||
name: 'New State',
|
||||
color: 9,
|
||||
rank: calcRank(lastOne, undefined)
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<StatesEditor
|
||||
{states}
|
||||
{wonStates}
|
||||
{lostStates}
|
||||
on:add={(e) => {
|
||||
onAdd(e.detail)
|
||||
}}
|
||||
on:delete
|
||||
on:move={onMove}
|
||||
/>
|
||||
<StatesEditor {states} {wonStates} {lostStates} space={kanban} on:delete on:move={onMove} />
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Ref, Space, SortingOrder, Class } from '@hcengineering/core'
|
||||
import { Ref, Space, SortingOrder } from '@hcengineering/core'
|
||||
import core from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import type {
|
||||
@@ -42,7 +42,6 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const statesQ = createQuery()
|
||||
$: statesQ.query(
|
||||
@@ -95,32 +94,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
async function onAdd (_class: Ref<Class<State | DoneState>>) {
|
||||
const lastOne = await client.findOne(
|
||||
task.class.StateTemplate,
|
||||
{ attachedTo: kanban._id },
|
||||
{ sort: { rank: SortingOrder.Descending } }
|
||||
)
|
||||
|
||||
if (hierarchy.isDerived(_class, task.class.DoneState)) {
|
||||
const targetClass = _class === task.class.WonState ? task.class.WonStateTemplate : task.class.LostStateTemplate
|
||||
await client.createDoc(targetClass, kanban.space, {
|
||||
ofAttribute: task.attribute.DoneState,
|
||||
name: 'New Done State',
|
||||
rank: calcRank(lastOne, undefined),
|
||||
attachedTo: kanban._id
|
||||
})
|
||||
} else {
|
||||
await client.createDoc(task.class.StateTemplate, kanban.space, {
|
||||
name: 'New State',
|
||||
ofAttribute: task.attribute.DoneState,
|
||||
color: 9,
|
||||
rank: calcRank(lastOne, undefined),
|
||||
attachedTo: kanban._id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function onDelete ({ detail: { state } }: { detail: { state: State | DoneState } }) {
|
||||
if (space === undefined) {
|
||||
return
|
||||
@@ -136,9 +109,6 @@
|
||||
{states}
|
||||
{wonStates}
|
||||
{lostStates}
|
||||
on:add={(e) => {
|
||||
onAdd(e.detail)
|
||||
}}
|
||||
on:delete={onDelete}
|
||||
on:move={onMove}
|
||||
/>
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Class, Ref } from '@hcengineering/core'
|
||||
import { AttributeEditor, getClient } from '@hcengineering/presentation'
|
||||
import type { DoneState, KanbanTemplate, KanbanTemplateSpace, State } from '@hcengineering/task'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import type { DoneState, Kanban, KanbanTemplate, KanbanTemplateSpace, State } from '@hcengineering/task'
|
||||
import {
|
||||
CircleButton,
|
||||
Component,
|
||||
@@ -32,7 +32,7 @@
|
||||
showPopup,
|
||||
themeStore
|
||||
} from '@hcengineering/ui'
|
||||
import { ColorsPopup } from '@hcengineering/view-resources'
|
||||
import { ColorsPopup, StringPresenter } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import task from '../../plugin'
|
||||
import Lost from '../icons/Lost.svelte'
|
||||
@@ -40,7 +40,7 @@
|
||||
import StatusesPopup from './StatusesPopup.svelte'
|
||||
|
||||
export let template: KanbanTemplate | undefined = undefined
|
||||
export let space: KanbanTemplateSpace | undefined = undefined
|
||||
export let space: KanbanTemplateSpace | Kanban | undefined = undefined
|
||||
export let states: State[] = []
|
||||
export let wonStates: DoneState[] = []
|
||||
export let lostStates: DoneState[] = []
|
||||
@@ -86,14 +86,11 @@
|
||||
|
||||
await client.updateDoc(state._class, state.space, state._id, { color })
|
||||
}
|
||||
|
||||
async function onAdd (_class: Ref<Class<State | DoneState>>) {
|
||||
dispatch('add', _class)
|
||||
}
|
||||
const spaceEditor = (space as KanbanTemplateSpace)?.editor
|
||||
</script>
|
||||
|
||||
{#if space?.editor}
|
||||
<Component is={space.editor} props={{ template }} />
|
||||
{#if spaceEditor}
|
||||
<Component is={spaceEditor} props={{ template }} />
|
||||
{/if}
|
||||
<div class="flex-no-shrink flex-between trans-title uppercase">
|
||||
<Label label={task.string.ActiveStates} />
|
||||
@@ -101,7 +98,15 @@
|
||||
icon={IconAdd}
|
||||
size={'medium'}
|
||||
on:click={() => {
|
||||
onAdd(task.class.State)
|
||||
showPopup(
|
||||
task.component.CreateStatePopup,
|
||||
{
|
||||
space,
|
||||
template,
|
||||
_class: template !== undefined ? task.class.StateTemplate : task.class.State
|
||||
},
|
||||
undefined
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -138,24 +143,28 @@
|
||||
}}
|
||||
/>
|
||||
<div class="flex-grow caption-color">
|
||||
<AttributeEditor maxWidth={'20rem'} _class={state._class} object={state} key="name" />
|
||||
<StringPresenter value={state.name} oneLine />
|
||||
</div>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="tool hover-trans"
|
||||
on:click={(ev) => {
|
||||
showPopup(
|
||||
StatusesPopup,
|
||||
{
|
||||
onDelete: () => dispatch('delete', { state }),
|
||||
showDelete: states.length > 1,
|
||||
onUpdate: () => {
|
||||
showPopup(task.component.CreateStatePopup, { status: state, template }, undefined)
|
||||
}
|
||||
},
|
||||
eventToHTMLElement(ev),
|
||||
() => {}
|
||||
)
|
||||
}}
|
||||
>
|
||||
<IconMoreH size={'medium'} />
|
||||
</div>
|
||||
{#if states.length > 1}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="tool hover-trans"
|
||||
on:click={(ev) => {
|
||||
showPopup(
|
||||
StatusesPopup,
|
||||
{ onDelete: () => dispatch('delete', { state }) },
|
||||
eventToHTMLElement(ev),
|
||||
() => {}
|
||||
)
|
||||
}}
|
||||
>
|
||||
<IconMoreH size={'medium'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
@@ -167,7 +176,15 @@
|
||||
icon={IconAdd}
|
||||
size={'medium'}
|
||||
on:click={() => {
|
||||
onAdd(task.class.WonState)
|
||||
showPopup(
|
||||
task.component.CreateStatePopup,
|
||||
{
|
||||
space,
|
||||
template,
|
||||
_class: template !== undefined ? task.class.WonStateTemplate : task.class.WonState
|
||||
},
|
||||
undefined
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -185,24 +202,29 @@
|
||||
<Won size={'medium'} />
|
||||
</div>
|
||||
<div class="flex-grow caption-color">
|
||||
<AttributeEditor maxWidth={'13rem'} _class={state._class} object={state} key="name" />
|
||||
<StringPresenter value={state.name} oneLine />
|
||||
<!-- <AttributeEditor maxWidth={'13rem'} _class={state._class} object={state} key="name" />-->
|
||||
</div>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="tool hover-trans"
|
||||
on:click={(ev) => {
|
||||
showPopup(
|
||||
StatusesPopup,
|
||||
{
|
||||
onDelete: () => dispatch('delete', { state }),
|
||||
showDelete: wonStates.length > 1,
|
||||
onUpdate: () => {
|
||||
showPopup(task.component.CreateStatePopup, { status: state, template }, undefined)
|
||||
}
|
||||
},
|
||||
eventToHTMLElement(ev),
|
||||
() => {}
|
||||
)
|
||||
}}
|
||||
>
|
||||
<IconMoreH size={'medium'} />
|
||||
</div>
|
||||
{#if wonStates.length > 1}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="tool hover-trans"
|
||||
on:click={(ev) => {
|
||||
showPopup(
|
||||
StatusesPopup,
|
||||
{ onDelete: () => dispatch('delete', { state }) },
|
||||
eventToHTMLElement(ev),
|
||||
() => {}
|
||||
)
|
||||
}}
|
||||
>
|
||||
<IconMoreH size={'medium'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
@@ -215,7 +237,15 @@
|
||||
icon={IconAdd}
|
||||
size={'medium'}
|
||||
on:click={() => {
|
||||
onAdd(task.class.LostState)
|
||||
showPopup(
|
||||
task.component.CreateStatePopup,
|
||||
{
|
||||
space,
|
||||
template,
|
||||
_class: template !== undefined ? task.class.LostStateTemplate : task.class.LostState
|
||||
},
|
||||
undefined
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -233,24 +263,29 @@
|
||||
<Lost size={'medium'} />
|
||||
</div>
|
||||
<div class="flex-grow caption-color">
|
||||
<AttributeEditor maxWidth={'13rem'} _class={state._class} object={state} key="name" />
|
||||
<StringPresenter value={state.name} oneLine />
|
||||
<!-- <AttributeEditor maxWidth={'13rem'} _class={state._class} object={state} key="name" />-->
|
||||
</div>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="tool hover-trans"
|
||||
on:click={(ev) => {
|
||||
showPopup(
|
||||
StatusesPopup,
|
||||
{
|
||||
onDelete: () => dispatch('delete', { state }),
|
||||
showDelete: lostStates.length > 1,
|
||||
onUpdate: () => {
|
||||
showPopup(task.component.CreateStatePopup, { status: state, template }, undefined)
|
||||
}
|
||||
},
|
||||
eventToHTMLElement(ev),
|
||||
() => {}
|
||||
)
|
||||
}}
|
||||
>
|
||||
<IconMoreH size={'medium'} />
|
||||
</div>
|
||||
{#if lostStates.length > 1}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="tool hover-trans"
|
||||
on:click={(ev) => {
|
||||
showPopup(
|
||||
StatusesPopup,
|
||||
{ onDelete: () => dispatch('delete', { state }) },
|
||||
eventToHTMLElement(ev),
|
||||
() => {}
|
||||
)
|
||||
}}
|
||||
>
|
||||
<IconMoreH size={'medium'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -14,28 +14,45 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Label, IconDelete as Delete } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { Label, IconDelete as Delete, IconEdit } from '@hcengineering/ui'
|
||||
import task from '../../plugin'
|
||||
|
||||
export let onDelete: () => void
|
||||
export let showDelete = true
|
||||
export let onUpdate: () => void
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="antiPopup">
|
||||
<div class="ap-space x2" />
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
{#if showDelete}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="ap-menuItem hoverable flex-row-center redlight"
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
onDelete()
|
||||
}}
|
||||
>
|
||||
<div class="mr-2">
|
||||
<Delete size={'small'} />
|
||||
</div>
|
||||
<Label label={task.string.Delete} />
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
class="ap-menuItem hoverable flex-row-center redlight"
|
||||
class="ap-menuItem hoverable flex-row-center"
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
onDelete()
|
||||
onUpdate()
|
||||
}}
|
||||
>
|
||||
<div class="mr-2">
|
||||
<Delete size={'small'} />
|
||||
<IconEdit size={'small'} />
|
||||
</div>
|
||||
<Label label={task.string.Delete} />
|
||||
<Label label={view.string.Rename} />
|
||||
</div>
|
||||
<div class="ap-space x2" />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user