mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 11:42:30 +03:00
Board: Initial checklist support (#1672)
Signed-off-by: Anna No <anna.no@xored.com>
This commit is contained in:
parent
1c98ca3c3a
commit
fe109a6b30
@ -194,6 +194,7 @@ input.search {
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.justify-center { justify-content: center; }
|
||||
.items-baseline { align-items: baseline; }
|
||||
.items-center { align-items: center; }
|
||||
|
||||
.flex-gap-3 { gap: .75rem; }
|
||||
.flex-gap-2 { gap: .5rem; }
|
||||
@ -456,6 +457,7 @@ input.search {
|
||||
.min-w-80 { min-width: 20rem; }
|
||||
.min-w-min { min-width: min-content; }
|
||||
.min-h-0 { min-height: 0; }
|
||||
.min-h-7 { min-height: 1.75rem; }
|
||||
.max-h-125 { max-height: 31.25rem; }
|
||||
.max-h-60 { max-height: 15rem; }
|
||||
.max-w-60 { max-width: 15rem; }
|
||||
@ -540,6 +542,7 @@ a.no-line {
|
||||
.text-lg { font-size: 1.125rem; }
|
||||
.font-normal { font-weight: 400; }
|
||||
.font-medium { font-weight: 500; }
|
||||
.font-semi-bold { font-weight: 600; }
|
||||
.fs-bold { font-weight: 500; }
|
||||
.uppercase { text-transform: uppercase; }
|
||||
.text-left { text-align: left; }
|
||||
@ -549,6 +552,8 @@ a.no-line {
|
||||
&:hover { text-decoration: underline; }
|
||||
}
|
||||
|
||||
.text-line-through { text-decoration: line-through; }
|
||||
|
||||
.hidden-text {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
@ -632,6 +637,7 @@ a.no-line {
|
||||
.background-highlight-red { background-color: var(--highlight-red); }
|
||||
.background-button-bg-color { background-color: var(--button-bg-color); }
|
||||
.background-button-bg-enabled { background-color: var(--theme-button-bg-enabled); }
|
||||
.background-button-noborder-bg-hover { background-color: var(--noborder-bg-hover); }
|
||||
.background-menu-divider { background-color: var(--theme-menu-divider); }
|
||||
.background-card-divider { background-color: var(--theme-card-divider); }
|
||||
.background-primary-color { background-color: var(--primary-button-enabled); }
|
||||
|
@ -19,7 +19,7 @@
|
||||
import Label from './Label.svelte'
|
||||
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let label: IntlString
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let placeholder: IntlString
|
||||
export let items: ListItem[] = []
|
||||
export let selected: ListItem | undefined = undefined
|
||||
|
@ -68,23 +68,28 @@
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<button
|
||||
bind:this={btns[i]}
|
||||
class="menu-item flex-between"
|
||||
class="flex-between menu-item"
|
||||
disabled={item.isSelectable === false}
|
||||
on:mouseover={(ev) => ev.currentTarget.focus()}
|
||||
on:keydown={(ev) => keyDown(ev, i)}
|
||||
on:click={() => {
|
||||
dispatch('close', item)
|
||||
if (item.isSelectable ?? true) {
|
||||
dispatch('close', item)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex-center img" class:image={item.image}>
|
||||
{#if item.image}
|
||||
<img src={item.image} alt={item.label} />
|
||||
{:else if typeof icon === 'string'}
|
||||
<Icon {icon} size={'small'} />
|
||||
{:else}
|
||||
<svelte:component this={icon} size={'small'} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-grow caption-color">{item.label}</div>
|
||||
{#if item.image || icon}
|
||||
<div class="flex-center img" class:image={item.image}>
|
||||
{#if item.image}
|
||||
<img src={item.image} alt={item.label} />
|
||||
{:else if typeof icon === 'string'}
|
||||
<Icon {icon} size={'small'} />
|
||||
{:else}
|
||||
<svelte:component this={icon} size={'small'} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-grow caption-color font-{item.fontWeight} pl-{item.paddingLeft}">{item.label}</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
@ -23,8 +23,8 @@
|
||||
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let maxWidth: string | undefined
|
||||
export let value: string | number | undefined
|
||||
export let maxWidth: string | undefined = undefined
|
||||
export let value: string | number | undefined = undefined
|
||||
export let placeholder: IntlString = plugin.string.EditBoxPlaceholder
|
||||
export let placeholderParam: any | undefined = undefined
|
||||
export let format: 'text' | 'password' | 'number' = 'text'
|
||||
|
@ -37,7 +37,9 @@
|
||||
<div class="container" on:click={click} class:cursor-pointer={editable}>
|
||||
<div
|
||||
class="bar"
|
||||
style="background-color: {getPlatformColor(color)}; width: calc(100% * {Math.round((value - min) / proc)} / 100);"
|
||||
style="background-color: {getPlatformColor(color)}; width: calc(100% * {proc !== 0
|
||||
? Math.round((value - min) / proc)
|
||||
: 0} / 100);"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
export let width: string | undefined = undefined
|
||||
export let height: string | undefined = undefined
|
||||
export let submitLabel: IntlString = ui.string.Save
|
||||
export let placeholder: IntlString | undefined
|
||||
export let placeholder: IntlString | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let isEditing = false
|
||||
|
@ -25,6 +25,7 @@ export type {
|
||||
AnySvelteComponent,
|
||||
Action,
|
||||
LabelAndProps,
|
||||
ListItem,
|
||||
TooltipAlignment,
|
||||
AnySvelteComponentWithProps,
|
||||
Location,
|
||||
|
@ -93,6 +93,9 @@ export interface ListItem {
|
||||
_id: string
|
||||
label: string
|
||||
image?: string
|
||||
isSelectable?: boolean
|
||||
fontWeight?: 'normal' | 'medium' | 'semi-bold'
|
||||
paddingLeft?: number
|
||||
}
|
||||
|
||||
export interface DropdownTextItem {
|
||||
|
@ -38,6 +38,10 @@
|
||||
"NoColor": "No color.",
|
||||
"NoColorInfo": "This won't show up on the front of cards.",
|
||||
"Checklist": "Checklist",
|
||||
"AddChecklistItem": "Add an item",
|
||||
"ChecklistDropdownNone": "(none)",
|
||||
"ShowDoneChecklistItems": "Show checked items ({done})",
|
||||
"HideDoneChecklistItems": "Hide checked items",
|
||||
"Dates": "Dates",
|
||||
"Attachments": "Attachments",
|
||||
"AddAttachment": "Add an attachment",
|
||||
|
@ -38,6 +38,10 @@
|
||||
"NoColor": "Без цвета.",
|
||||
"NoColorInfo": "Не будет показываться на доске.",
|
||||
"Checklist": "Списки",
|
||||
"AddChecklistItem": "Добавить",
|
||||
"ChecklistDropdownNone": "(не выбрано)",
|
||||
"ShowDoneChecklistItems": "Показать отмеченные ({done})",
|
||||
"HideDoneChecklistItems": "Скрыть отмеченные",
|
||||
"Dates": "Дата",
|
||||
"Attachments": "Прикрепленное",
|
||||
"AddAttachment": "Прикрепить",
|
||||
|
@ -19,7 +19,7 @@
|
||||
import { Panel } from '@anticrm/panel'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import type { State } from '@anticrm/task'
|
||||
import type { State, TodoItem } from '@anticrm/task'
|
||||
import task from '@anticrm/task'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import { Button, EditBox, Icon, Label } from '@anticrm/ui'
|
||||
@ -30,6 +30,7 @@
|
||||
import { updateCard } from '../utils/CardUtils'
|
||||
import CardActions from './editor/CardActions.svelte'
|
||||
import CardAttachments from './editor/CardAttachments.svelte'
|
||||
import CardChecklist from './editor/CardChecklist.svelte'
|
||||
import CardDetails from './editor/CardDetails.svelte'
|
||||
|
||||
export let _id: Ref<Card>
|
||||
@ -38,20 +39,33 @@
|
||||
const client = getClient()
|
||||
const cardQuery = createQuery()
|
||||
const stateQuery = createQuery()
|
||||
const checklistsQuery = createQuery()
|
||||
|
||||
let object: Card | undefined
|
||||
let state: State | undefined
|
||||
let handleMove: (e: Event) => void
|
||||
let checklists: TodoItem[] = []
|
||||
|
||||
$: cardQuery.query(_class, { _id }, async (result) => {
|
||||
function change (field: string, value: any) {
|
||||
if (object) {
|
||||
updateCard(client, object, field, value)
|
||||
}
|
||||
}
|
||||
|
||||
$: cardQuery.query(_class, { _id }, (result) => {
|
||||
object = result[0]
|
||||
})
|
||||
|
||||
$: object?.state &&
|
||||
stateQuery.query(task.class.State, { _id: object.state }, async (result) => {
|
||||
stateQuery.query(task.class.State, { _id: object.state }, (result) => {
|
||||
state = result[0]
|
||||
})
|
||||
|
||||
$: object &&
|
||||
checklistsQuery.query(task.class.TodoItem, { space: object.space, attachedTo: object._id }, (result) => {
|
||||
checklists = result
|
||||
})
|
||||
|
||||
getCardActions(client, { _id: board.cardAction.Move }).then(async (result) => {
|
||||
if (result[0]?.handler) {
|
||||
const handler = await getResource(result[0].handler)
|
||||
@ -63,12 +77,6 @@
|
||||
}
|
||||
})
|
||||
|
||||
function change (field: string, value: any) {
|
||||
if (object) {
|
||||
updateCard(client, object, field, value)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
dispatch('open', { ignoreKeys: ['comments', 'number', 'title'] })
|
||||
})
|
||||
@ -130,8 +138,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<CardAttachments value={object} />
|
||||
<!-- TODO checklists -->
|
||||
<!-- <CardActivity bind:value={object} /> -->
|
||||
{#each checklists as checklist}
|
||||
<CardChecklist value={checklist} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,57 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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 activity from '@anticrm/activity'
|
||||
import type { Card } from '@anticrm/board'
|
||||
import chunter from '@anticrm/chunter'
|
||||
import { Button, Component, Icon, IconActivity, Label } from '@anticrm/ui'
|
||||
import board from '../../plugin'
|
||||
|
||||
export let value: Card | undefined
|
||||
let isActivityShown: boolean = true
|
||||
</script>
|
||||
|
||||
{#if value !== undefined}
|
||||
<div class="flex-col-stretch h-full w-full">
|
||||
<div class="flex-row-stretch mt-4 mb-2">
|
||||
<div class="w-9">
|
||||
<Icon icon={IconActivity} size="large" />
|
||||
</div>
|
||||
<div class="flex-grow fs-title">
|
||||
<Label label={activity.string.Activity} />
|
||||
</div>
|
||||
<Button
|
||||
kind="no-border"
|
||||
label={isActivityShown ? board.string.HideDetails : board.string.ShowDetails}
|
||||
width="100px"
|
||||
on:click={() => {
|
||||
isActivityShown = !isActivityShown
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-row-stretch">
|
||||
<div class="w-9" />
|
||||
<div class="w-full">
|
||||
<Component is={chunter.component.CommentInput} props={{ object: value }} />
|
||||
</div>
|
||||
</div>
|
||||
{#if isActivityShown === true}
|
||||
<Component is={activity.component.Activity} props={{ object: value, showCommenInput: false, transparent: true }}>
|
||||
<slot />
|
||||
</Component>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
@ -0,0 +1,222 @@
|
||||
<!--
|
||||
// Copyright © 2022 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 { Ref } from '@anticrm/core'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import type { TodoItem } from '@anticrm/task'
|
||||
import task from '@anticrm/task'
|
||||
import { Button, CheckBox, TextAreaEditor, Icon, IconMoreH, Progress, showPopup } from '@anticrm/ui'
|
||||
import { ContextMenu, HTMLPresenter } from '@anticrm/view-resources'
|
||||
|
||||
import board from '../../plugin'
|
||||
import { getPopupAlignment } from '../../utils/PopupUtils'
|
||||
|
||||
export let value: TodoItem
|
||||
const client = getClient()
|
||||
const checklistItemsQuery = createQuery()
|
||||
let checklistItems: TodoItem[] = []
|
||||
let done = 0
|
||||
let isEditingName: boolean = false
|
||||
let isAddingItem: boolean = false
|
||||
let hideDoneItems: boolean = false
|
||||
let newItemName = ''
|
||||
let editingItemId: Ref<TodoItem> | undefined = undefined
|
||||
let hovered: Ref<TodoItem> | undefined
|
||||
|
||||
function deleteChecklist () {
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
client.removeCollection(
|
||||
value._class,
|
||||
value.space,
|
||||
value._id,
|
||||
value.attachedTo,
|
||||
value.attachedToClass,
|
||||
value.collection
|
||||
)
|
||||
}
|
||||
|
||||
function startAddingItem () {
|
||||
isAddingItem = true
|
||||
}
|
||||
|
||||
async function addItem (event: CustomEvent<string>) {
|
||||
newItemName = ''
|
||||
const item = {
|
||||
name: event.detail ?? '',
|
||||
assignee: null,
|
||||
dueTo: null,
|
||||
done: false
|
||||
}
|
||||
if (item.name.length <= 0) {
|
||||
return
|
||||
}
|
||||
await client.addCollection(task.class.TodoItem, value.space, value._id, value._class, 'items', item)
|
||||
}
|
||||
|
||||
function updateName (event: CustomEvent<string>) {
|
||||
isEditingName = false
|
||||
const name = event.detail
|
||||
if (name !== undefined && name.length > 0 && name !== value.name) {
|
||||
value.name = name
|
||||
client.update(value, { name: value.name })
|
||||
}
|
||||
}
|
||||
|
||||
function updateItemName (item: TodoItem, name: string) {
|
||||
if (name !== undefined && name.length > 0 && name !== value.name) {
|
||||
item.name = name
|
||||
client.update(item, { name: item.name })
|
||||
}
|
||||
}
|
||||
|
||||
async function setDoneToChecklistItem (item: TodoItem, event: CustomEvent<boolean>) {
|
||||
const isDone = event.detail
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
await client.update(item, { done: isDone })
|
||||
}
|
||||
|
||||
function showItemMenu (item: TodoItem, e?: Event) {
|
||||
showPopup(ContextMenu, { object: item }, getPopupAlignment(e))
|
||||
}
|
||||
|
||||
$: checklistItemsQuery.query(task.class.TodoItem, { space: value.space, attachedTo: value._id }, (result) => {
|
||||
checklistItems = result
|
||||
done = checklistItems.reduce((result: number, current: TodoItem) => {
|
||||
return current.done ? result + 1 : result
|
||||
}, 0)
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if value !== undefined}
|
||||
<div class="flex-col w-full">
|
||||
<div class="flex-row-stretch mt-4 mb-2">
|
||||
<div class="w-9">
|
||||
<Icon icon={board.icon.Card} size="large" />
|
||||
</div>
|
||||
{#if isEditingName}
|
||||
<div class="flex-grow">
|
||||
<TextAreaEditor
|
||||
value={value.name}
|
||||
on:submit={updateName}
|
||||
on:cancel={() => {
|
||||
isEditingName = false
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="flex-grow fs-title"
|
||||
on:click={() => {
|
||||
isEditingName = true
|
||||
}}
|
||||
>
|
||||
{value.name}
|
||||
</div>
|
||||
{#if done > 0}
|
||||
<div class="mr-1">
|
||||
<Button
|
||||
label={hideDoneItems ? board.string.ShowDoneChecklistItems : board.string.HideDoneChecklistItems}
|
||||
labelParams={{ done }}
|
||||
kind="no-border"
|
||||
size="small"
|
||||
on:click={() => {
|
||||
hideDoneItems = !hideDoneItems
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<Button label={board.string.Delete} kind="no-border" size="small" on:click={deleteChecklist} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-row-stretch mb-2 mt-1">
|
||||
<div class="w-9 text-sm pl-1 pr-1">
|
||||
{checklistItems.length > 0 ? Math.round((done / checklistItems.length) * 100) : 0}%
|
||||
</div>
|
||||
<div class="flex-center flex-grow w-full">
|
||||
<Progress min={0} max={checklistItems?.length ?? 0} value={done} />
|
||||
</div>
|
||||
</div>
|
||||
{#each checklistItems.filter((item) => !hideDoneItems || !item.done) as item}
|
||||
<div
|
||||
class="flex-row-stretch mb-1 mt-1 pl-1 min-h-7 border-radius-1"
|
||||
class:background-button-noborder-bg-hover={hovered === item._id && editingItemId !== item._id}
|
||||
on:mouseover={() => {
|
||||
hovered = item._id
|
||||
}}
|
||||
on:focus={() => {
|
||||
hovered = item._id
|
||||
}}
|
||||
on:mouseout={() => {
|
||||
hovered = undefined
|
||||
}}
|
||||
on:blur={() => {
|
||||
hovered = undefined
|
||||
}}
|
||||
>
|
||||
<div class="w-9 flex items-center">
|
||||
<CheckBox bind:checked={item.done} on:value={(event) => setDoneToChecklistItem(item, event)} />
|
||||
</div>
|
||||
{#if editingItemId === item._id}
|
||||
<div class="flex-grow">
|
||||
<TextAreaEditor
|
||||
value={item.name}
|
||||
on:submit={(event) => {
|
||||
editingItemId = undefined
|
||||
updateItemName(item, event.detail)
|
||||
}}
|
||||
on:cancel={() => {
|
||||
editingItemId = undefined
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="flex-col justify-center flex-gap-1 w-full"
|
||||
class:text-line-through={item.done}
|
||||
on:click={() => {
|
||||
editingItemId = item._id
|
||||
}}
|
||||
>
|
||||
<HTMLPresenter bind:value={item.name} />
|
||||
</div>
|
||||
<div class="flex-center">
|
||||
<Button icon={IconMoreH} kind="transparent" size="small" on:click={(e) => showItemMenu(item, e)} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<div class="flex-row-stretch mt-2 mb-2">
|
||||
<div class="w-9" />
|
||||
{#if isAddingItem}
|
||||
<div class="w-full p-1">
|
||||
<TextAreaEditor
|
||||
bind:value={newItemName}
|
||||
on:submit={addItem}
|
||||
on:cancel={() => {
|
||||
newItemName = ''
|
||||
isAddingItem = false
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<Button label={board.string.AddChecklistItem} kind="no-border" size="small" on:click={startAddingItem} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
@ -0,0 +1,152 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { Card } from '@anticrm/board'
|
||||
import { WithLookup } from '@anticrm/core'
|
||||
import task, { TodoItem } from '@anticrm/task'
|
||||
import { translate } from '@anticrm/platform'
|
||||
import presentation, { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { Label, Button, Dropdown, EditBox, IconClose } from '@anticrm/ui'
|
||||
import type { ListItem } from '@anticrm/ui'
|
||||
|
||||
import board from '../../plugin'
|
||||
|
||||
export let object: Card
|
||||
|
||||
const noneListItem: ListItem = {
|
||||
_id: 'none',
|
||||
label: ''
|
||||
}
|
||||
|
||||
let name: string | undefined
|
||||
let selectedTemplate: ListItem | undefined = undefined
|
||||
let templateListItems: ListItem[] = [noneListItem]
|
||||
let templatesMap: Map<string, TodoItem> = new Map()
|
||||
const client = getClient()
|
||||
const templatesQuery = createQuery()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
translate(board.string.ChecklistDropdownNone, {}).then((result) => {
|
||||
noneListItem.label = result
|
||||
})
|
||||
|
||||
function close () {
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
async function addChecklist () {
|
||||
if (!name || name.trim().length <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const template = selectedTemplate ? templatesMap.get(selectedTemplate._id) : undefined
|
||||
const items: TodoItem[] = template ? await client.findAll(task.class.TodoItem, { attachedTo: template._id }) : []
|
||||
const checklistRef = await client.addCollection(
|
||||
task.class.TodoItem,
|
||||
object.space,
|
||||
object._id,
|
||||
object._class,
|
||||
'todoItems',
|
||||
{
|
||||
name,
|
||||
done: false,
|
||||
dueTo: null,
|
||||
assignee: null
|
||||
}
|
||||
)
|
||||
if (items.length > 0) {
|
||||
await Promise.all(
|
||||
items.map((item) =>
|
||||
client.addCollection(task.class.TodoItem, object.space, checklistRef, task.class.TodoItem, 'items', {
|
||||
name: item.name,
|
||||
dueTo: item.dueTo,
|
||||
done: item.done,
|
||||
assignee: item.assignee
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
$: templatesQuery.query(
|
||||
board.class.Card,
|
||||
{ todoItems: { $gt: 0 } },
|
||||
(result: WithLookup<Card>[]) => {
|
||||
templateListItems = [noneListItem]
|
||||
templatesMap = new Map()
|
||||
|
||||
for (const card of result) {
|
||||
const todoItems = card.$lookup?.todoItems as TodoItem[]
|
||||
if (!todoItems) {
|
||||
continue
|
||||
}
|
||||
|
||||
templateListItems.push({
|
||||
_id: card._id,
|
||||
label: card.title,
|
||||
fontWeight: 'semi-bold',
|
||||
isSelectable: false
|
||||
})
|
||||
|
||||
for (const todoItem of todoItems) {
|
||||
templateListItems.push({
|
||||
_id: todoItem._id,
|
||||
label: todoItem.name,
|
||||
paddingLeft: 4
|
||||
})
|
||||
templatesMap.set(todoItem._id, todoItem)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ lookup: { _id: { todoItems: task.class.TodoItem } } }
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="antiPopup w-85">
|
||||
<div class="relative flex-row-center w-full ">
|
||||
<div class="flex-center flex-grow fs-title mt-1 mb-1">
|
||||
<Label label={board.string.Checklist} />
|
||||
</div>
|
||||
|
||||
<div class="absolute mr-1 mt-1 mb-1" style:top="0" style:right="0">
|
||||
<Button icon={IconClose} kind="transparent" size="small" on:click={close} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ap-space bottom-divider" />
|
||||
<div class="flex-col ml-4 mt-4 mr-4 flex-gap-1">
|
||||
<div class="text-md font-medium">
|
||||
<Label label={board.string.Title} />
|
||||
</div>
|
||||
|
||||
<div class="p-2 mt-1 mb-1 border-bg-accent border-radius-1">
|
||||
<EditBox bind:value={name} maxWidth="100%" focus={true} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-col ml-4 mt-4 mr-4 flex-gap-1">
|
||||
<div class="text-md font-medium">
|
||||
<Label label={board.string.Title} />
|
||||
</div>
|
||||
|
||||
<div class="mt-1 mb-1 w-full">
|
||||
<Dropdown
|
||||
bind:selected={selectedTemplate}
|
||||
items={templateListItems}
|
||||
justify="left"
|
||||
width="100%"
|
||||
placeholder={board.string.ChecklistDropdownNone}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ap-footer">
|
||||
<Button
|
||||
label={presentation.string.Add}
|
||||
size="small"
|
||||
kind="primary"
|
||||
disabled={(name?.length ?? 0) <= 0}
|
||||
on:click={addChecklist}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
@ -27,6 +27,7 @@ import CreateCard from './components/CreateCard.svelte'
|
||||
import EditCard from './components/EditCard.svelte'
|
||||
import KanbanCard from './components/KanbanCard.svelte'
|
||||
import KanbanView from './components/KanbanView.svelte'
|
||||
import AddChecklist from './components/popups/AddChecklist.svelte'
|
||||
import AttachmentPicker from './components/popups/AttachmentPicker.svelte'
|
||||
import CardLabelsPopup from './components/popups/CardLabelsPopup.svelte'
|
||||
import MoveCard from './components/popups/MoveCard.svelte'
|
||||
@ -73,6 +74,10 @@ async function showCardLabelsPopup (object: Card, client: Client, e?: Event): Pr
|
||||
showPopup(CardLabelsPopup, { object }, getPopupAlignment(e))
|
||||
}
|
||||
|
||||
async function showChecklistsPopup (object: Card, client: Client, e?: Event): Promise<void> {
|
||||
showPopup(AddChecklist, { object }, getPopupAlignment(e))
|
||||
}
|
||||
|
||||
async function showEditMembersPopup (object: Card, client: Client, e?: Event): Promise<void> {
|
||||
showPopup(
|
||||
UsersPopup,
|
||||
@ -126,6 +131,7 @@ export default async (): Promise<Resources> => ({
|
||||
SendToBoard: unarchiveCard,
|
||||
Delete: showDeleteCardPopup,
|
||||
Members: showEditMembersPopup,
|
||||
Checklist: showChecklistsPopup,
|
||||
Copy: showCopyCardPopup,
|
||||
Cover: showCoverPopup
|
||||
},
|
||||
|
@ -59,6 +59,10 @@ export default mergeIds(boardId, board, {
|
||||
NoColor: '' as IntlString,
|
||||
NoColorInfo: '' as IntlString,
|
||||
Checklist: '' as IntlString,
|
||||
AddChecklistItem: '' as IntlString,
|
||||
ChecklistDropdownNone: '' as IntlString,
|
||||
ShowDoneChecklistItems: '' as IntlString,
|
||||
HideDoneChecklistItems: '' as IntlString,
|
||||
Dates: '' as IntlString,
|
||||
Attachments: '' as IntlString,
|
||||
AddAttachment: '' as IntlString,
|
||||
|
@ -55,7 +55,7 @@ export { default as LinkPresenter } from './components/LinkPresenter.svelte'
|
||||
export * from './context'
|
||||
export * from './selection'
|
||||
export { buildModel, getCollectionCounter, getObjectPresenter, LoadingProps } from './utils'
|
||||
export { Table, TableView, DocAttributeBar, EditDoc, ColorsPopup, Menu, SpacePresenter, UpDownNavigator }
|
||||
export { HTMLPresenter, Table, TableView, DocAttributeBar, EditDoc, ColorsPopup, Menu, SpacePresenter, UpDownNavigator }
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
actionImpl: actionImpl,
|
||||
|
Loading…
Reference in New Issue
Block a user