mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
parent
42d1687ded
commit
243b1343d5
@ -91,6 +91,9 @@
|
||||
"DeleteCard": "All actions will be removed from the activity feed and you won’t 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..."
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +91,9 @@
|
||||
"DeleteCard": "Все действия будут удалены из ленты, и вы не сможете повторно открыть карточку. Отмена невозможна.",
|
||||
"SearchMembers": "Поиск участников",
|
||||
"Menu": "Меню",
|
||||
"ToArchive": "Архивировать"
|
||||
"ToArchive": "Архивировать",
|
||||
"CopyCard": "Копировать карточку",
|
||||
"AlsoCopy": "Также копировать...",
|
||||
"CopyTo": "Копировать в..."
|
||||
}
|
||||
}
|
||||
|
144
plugins/board-resources/src/components/popups/CopyCard.svelte
Normal file
144
plugins/board-resources/src/components/popups/CopyCard.svelte
Normal 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>
|
@ -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 } }
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user