Add action (#1493)

Signed-off-by: Sergey Semenov <lvfx@ya.ru>
This commit is contained in:
Sergey Semenov 2022-04-25 23:01:55 +07:00 committed by GitHub
parent 42d1687ded
commit 243b1343d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 179 additions and 12 deletions

View File

@ -91,6 +91,9 @@
"DeleteCard": "All actions will be removed from the activity feed and you wont be able to re-open the card. There is no undo.",
"SearchMembers": "Search members",
"Menu": "Menu",
"ToArchive": "Archive"
"ToArchive": "Archive",
"CopyCard": "Copy card",
"AlsoCopy": "Keep...",
"CopyTo": "Copy to..."
}
}

View File

@ -91,6 +91,9 @@
"DeleteCard": "Все действия будут удалены из ленты, и вы не сможете повторно открыть карточку. Отмена невозможна.",
"SearchMembers": "Поиск участников",
"Menu": "Меню",
"ToArchive": "Архивировать"
"ToArchive": "Архивировать",
"CopyCard": "Копировать карточку",
"AlsoCopy": "Также копировать...",
"CopyTo": "Копировать в..."
}
}

View File

@ -0,0 +1,144 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte'
import { Label, Button, Status as StatusControl, TextArea } from '@anticrm/ui'
import { Class, Client, Doc, Ref } from '@anticrm/core'
import { getResource, OK, Resource, Status } from '@anticrm/platform'
import { Card } from '@anticrm/board'
import view from '@anticrm/view'
import board from '../../plugin'
import SpaceSelect from '../selectors/SpaceSelect.svelte'
import StateSelect from '../selectors/StateSelect.svelte'
import RankSelect from '../selectors/RankSelect.svelte'
import type { TxOperations } from '@anticrm/core'
import { generateId, AttachedData } from '@anticrm/core'
import task from '@anticrm/task'
export let object: Card
export let client: TxOperations
const hierarchy = client.getHierarchy()
const dispatch = createEventDispatcher()
let inputRef: TextArea
let title = object.title
let status: Status = OK
const selected = {
space: object.space,
state: object.state,
rank: object.rank
}
async function copyCard(): Promise<void> {
const newCardId = generateId() as Ref<Card>
const sequence = await client.findOne(task.class.Sequence, { attachedTo: board.class.Card })
if (sequence === undefined) {
throw new Error('sequence object not found')
}
const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true)
const value: AttachedData<Card> = {
state: selected.state,
doneState: null,
number: (incResult as any).object.sequence,
title: title,
rank: selected.rank,
assignee: null,
description: '',
members: [],
location: ''
}
await client.addCollection(
board.class.Card,
selected.space,
selected.space,
board.class.Board,
'cards',
value,
newCardId
)
dispatch('close')
}
async function invokeValidate(
action: Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status>>
): Promise<Status> {
const impl = await getResource(action)
return await impl(object, client)
}
async function validate(doc: Doc, _class: Ref<Class<Doc>>): Promise<void> {
const clazz = hierarchy.getClass(_class)
const validatorMixin = hierarchy.as(clazz, view.mixin.ObjectValidator)
if (validatorMixin?.validator != null) {
status = await invokeValidate(validatorMixin.validator)
} else if (clazz.extends != null) {
await validate(doc, clazz.extends)
} else {
status = OK
}
}
$: validate({ ...object, ...selected }, object._class)
onMount(() => inputRef.focus())
</script>
<div class="antiPopup antiPopup-withHeader antiPopup-withTitle antiPopup-withCategory w-85">
<div class="ap-space" />
<div class="fs-title ap-header flex-row-center">
<Label label={board.string.CopyCard} />
</div>
<div class="ap-space bottom-divider" />
<StatusControl {status} />
<div class="ap-title">
<Label label={board.string.Title} />
</div>
<div class="mr-4 ml-4 mt-2">
<TextArea bind:this={inputRef} bind:value={title} />
</div>
<div class="ap-title">
<Label label={board.string.CopyTo} />
</div>
<div class="ap-category">
<div class="categoryItem w-full border-radius-2 p-2 background-button-bg-enabled">
<SpaceSelect label={board.string.Board} {object} bind:selected={selected.space} />
</div>
</div>
<div class="ap-category flex-gap-3">
<div class="categoryItem w-full border-radius-2 p-2 background-button-bg-enabled">
{#key selected.space}
<StateSelect label={board.string.List} {object} space={selected.space} bind:selected={selected.state} />
{/key}
</div>
<div class="categoryItem w-full border-radius-2 p-2 background-button-bg-enabled">
{#key selected.state}
<RankSelect
label={board.string.Position}
{object}
state={selected.state}
bind:selected={selected.rank}
isCopying={true}
/>
{/key}
</div>
</div>
<div class="ap-footer">
<Button
size={'small'}
label={board.string.Cancel}
on:click={() => {
dispatch('close')
}}
/>
<Button
label={board.string.CreateCard}
size={'small'}
disabled={status !== OK}
kind={'primary'}
on:click={copyCard}
/>
</div>
</div>

View File

@ -12,6 +12,7 @@
export let label: IntlString
export let state: Ref<State>
export let selected: string
export let isCopying: boolean = false
let ranks: DropdownTextItem[] = []
const tasksQuery = createQuery()
@ -19,22 +20,29 @@
board.class.Card,
{ state },
async (result) => {
;[ranks] = [...result.filter((t) => t._id !== object._id), undefined].reduce<
[DropdownTextItem[], Card | undefined]
>(
const filteredResult = isCopying ? result : result.filter((t) => t._id !== object._id)
;[ranks] = [...filteredResult, undefined].reduce<[DropdownTextItem[], Card | undefined]>(
([arr, prev], next) => [[...arr, { id: calcRank(prev, next), label: `${arr.length + 1}` }], next],
[[], undefined]
)
;[{ id: selected = object.rank }] = ranks.slice(-1)
let selectedRank = ranks.slice(-1)[0].id
if (object.state === state) {
const index = result.findIndex((t) => t._id === object._id)
ranks[index] = {
id: object.rank,
label: await translate(board.string.Current, { label: ranks[index].label })
if (index !== -1) {
selectedRank = isCopying ? ranks[index].id : object.rank
ranks[index] = {
id: selectedRank,
label: await translate(board.string.Current, { label: ranks[index].label })
}
}
selected = object.rank
}
selected = selectedRank
},
{ sort: { rank: SortingOrder.Ascending } }
)

View File

@ -31,6 +31,7 @@ import AttachmentPicker from './components/popups/AttachmentPicker.svelte'
import CardLabelsPopup from './components/popups/CardLabelsPopup.svelte'
import MoveCard from './components/popups/MoveCard.svelte'
import DeleteCard from './components/popups/RemoveCard.svelte'
import CopyCard from './components/popups/CopyCard.svelte'
import DateRangePicker from './components/popups/DateRangePicker.svelte'
import CardDatePresenter from './components/presenters/DatePresenter.svelte'
import CardLabelPresenter from './components/presenters/LabelPresenter.svelte'
@ -56,6 +57,10 @@ async function showDeleteCardPopup (object: Card, client: Client, e?: Event): Pr
showPopup(DeleteCard, { object }, getPopupAlignment(e))
}
async function showCopyCardPopup (object: Card, client: Client, e?: Event): Promise<void> {
showPopup(CopyCard, { object, client }, getPopupAlignment(e))
}
async function showDatePickerPopup (object: Card, client: Client, e?: Event): Promise<void> {
showPopup(DateRangePicker, { object }, getPopupAlignment(e))
}
@ -110,7 +115,8 @@ export default async (): Promise<Resources> => ({
Archive: archiveCard,
SendToBoard: unarchiveCard,
Delete: showDeleteCardPopup,
Members: showEditMembersPopup
Members: showEditMembersPopup,
Copy: showCopyCardPopup
},
cardActionSupportedHandler: {
Join: canAddCurrentUser,

View File

@ -112,7 +112,10 @@ export default mergeIds(boardId, board, {
SearchMembers: '' as IntlString,
DeleteCard: '' as IntlString,
Menu: '' as IntlString,
ToArchive: '' as IntlString
ToArchive: '' as IntlString,
CopyCard: '' as IntlString,
AlsoCopy: '' as IntlString,
CopyTo: '' as IntlString
},
component: {
Boards: '' as AnyComponent,