mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-20 15:20:18 +00: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-end { justify-content: flex-end; }
|
||||||
.justify-center { justify-content: center; }
|
.justify-center { justify-content: center; }
|
||||||
.items-baseline { align-items: baseline; }
|
.items-baseline { align-items: baseline; }
|
||||||
|
.items-center { align-items: center; }
|
||||||
|
|
||||||
.flex-gap-3 { gap: .75rem; }
|
.flex-gap-3 { gap: .75rem; }
|
||||||
.flex-gap-2 { gap: .5rem; }
|
.flex-gap-2 { gap: .5rem; }
|
||||||
@ -456,6 +457,7 @@ input.search {
|
|||||||
.min-w-80 { min-width: 20rem; }
|
.min-w-80 { min-width: 20rem; }
|
||||||
.min-w-min { min-width: min-content; }
|
.min-w-min { min-width: min-content; }
|
||||||
.min-h-0 { min-height: 0; }
|
.min-h-0 { min-height: 0; }
|
||||||
|
.min-h-7 { min-height: 1.75rem; }
|
||||||
.max-h-125 { max-height: 31.25rem; }
|
.max-h-125 { max-height: 31.25rem; }
|
||||||
.max-h-60 { max-height: 15rem; }
|
.max-h-60 { max-height: 15rem; }
|
||||||
.max-w-60 { max-width: 15rem; }
|
.max-w-60 { max-width: 15rem; }
|
||||||
@ -540,6 +542,7 @@ a.no-line {
|
|||||||
.text-lg { font-size: 1.125rem; }
|
.text-lg { font-size: 1.125rem; }
|
||||||
.font-normal { font-weight: 400; }
|
.font-normal { font-weight: 400; }
|
||||||
.font-medium { font-weight: 500; }
|
.font-medium { font-weight: 500; }
|
||||||
|
.font-semi-bold { font-weight: 600; }
|
||||||
.fs-bold { font-weight: 500; }
|
.fs-bold { font-weight: 500; }
|
||||||
.uppercase { text-transform: uppercase; }
|
.uppercase { text-transform: uppercase; }
|
||||||
.text-left { text-align: left; }
|
.text-left { text-align: left; }
|
||||||
@ -549,6 +552,8 @@ a.no-line {
|
|||||||
&:hover { text-decoration: underline; }
|
&:hover { text-decoration: underline; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-line-through { text-decoration: line-through; }
|
||||||
|
|
||||||
.hidden-text {
|
.hidden-text {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@ -632,6 +637,7 @@ a.no-line {
|
|||||||
.background-highlight-red { background-color: var(--highlight-red); }
|
.background-highlight-red { background-color: var(--highlight-red); }
|
||||||
.background-button-bg-color { background-color: var(--button-bg-color); }
|
.background-button-bg-color { background-color: var(--button-bg-color); }
|
||||||
.background-button-bg-enabled { background-color: var(--theme-button-bg-enabled); }
|
.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-menu-divider { background-color: var(--theme-menu-divider); }
|
||||||
.background-card-divider { background-color: var(--theme-card-divider); }
|
.background-card-divider { background-color: var(--theme-card-divider); }
|
||||||
.background-primary-color { background-color: var(--primary-button-enabled); }
|
.background-primary-color { background-color: var(--primary-button-enabled); }
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
import Label from './Label.svelte'
|
import Label from './Label.svelte'
|
||||||
|
|
||||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||||
export let label: IntlString
|
export let label: IntlString | undefined = undefined
|
||||||
export let placeholder: IntlString
|
export let placeholder: IntlString
|
||||||
export let items: ListItem[] = []
|
export let items: ListItem[] = []
|
||||||
export let selected: ListItem | undefined = undefined
|
export let selected: ListItem | undefined = undefined
|
||||||
|
@ -68,13 +68,17 @@
|
|||||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||||
<button
|
<button
|
||||||
bind:this={btns[i]}
|
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:mouseover={(ev) => ev.currentTarget.focus()}
|
||||||
on:keydown={(ev) => keyDown(ev, i)}
|
on:keydown={(ev) => keyDown(ev, i)}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
if (item.isSelectable ?? true) {
|
||||||
dispatch('close', item)
|
dispatch('close', item)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{#if item.image || icon}
|
||||||
<div class="flex-center img" class:image={item.image}>
|
<div class="flex-center img" class:image={item.image}>
|
||||||
{#if item.image}
|
{#if item.image}
|
||||||
<img src={item.image} alt={item.label} />
|
<img src={item.image} alt={item.label} />
|
||||||
@ -84,7 +88,8 @@
|
|||||||
<svelte:component this={icon} size={'small'} />
|
<svelte:component this={icon} size={'small'} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow caption-color">{item.label}</div>
|
{/if}
|
||||||
|
<div class="flex-grow caption-color font-{item.fontWeight} pl-{item.paddingLeft}">{item.label}</div>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,8 +23,8 @@
|
|||||||
|
|
||||||
export let label: IntlString | undefined = undefined
|
export let label: IntlString | undefined = undefined
|
||||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||||
export let maxWidth: string | undefined
|
export let maxWidth: string | undefined = undefined
|
||||||
export let value: string | number | undefined
|
export let value: string | number | undefined = undefined
|
||||||
export let placeholder: IntlString = plugin.string.EditBoxPlaceholder
|
export let placeholder: IntlString = plugin.string.EditBoxPlaceholder
|
||||||
export let placeholderParam: any | undefined = undefined
|
export let placeholderParam: any | undefined = undefined
|
||||||
export let format: 'text' | 'password' | 'number' = 'text'
|
export let format: 'text' | 'password' | 'number' = 'text'
|
||||||
|
@ -37,7 +37,9 @@
|
|||||||
<div class="container" on:click={click} class:cursor-pointer={editable}>
|
<div class="container" on:click={click} class:cursor-pointer={editable}>
|
||||||
<div
|
<div
|
||||||
class="bar"
|
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>
|
</div>
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
export let width: string | undefined = undefined
|
export let width: string | undefined = undefined
|
||||||
export let height: string | undefined = undefined
|
export let height: string | undefined = undefined
|
||||||
export let submitLabel: IntlString = ui.string.Save
|
export let submitLabel: IntlString = ui.string.Save
|
||||||
export let placeholder: IntlString | undefined
|
export let placeholder: IntlString | undefined = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let isEditing = false
|
let isEditing = false
|
||||||
|
@ -25,6 +25,7 @@ export type {
|
|||||||
AnySvelteComponent,
|
AnySvelteComponent,
|
||||||
Action,
|
Action,
|
||||||
LabelAndProps,
|
LabelAndProps,
|
||||||
|
ListItem,
|
||||||
TooltipAlignment,
|
TooltipAlignment,
|
||||||
AnySvelteComponentWithProps,
|
AnySvelteComponentWithProps,
|
||||||
Location,
|
Location,
|
||||||
|
@ -93,6 +93,9 @@ export interface ListItem {
|
|||||||
_id: string
|
_id: string
|
||||||
label: string
|
label: string
|
||||||
image?: string
|
image?: string
|
||||||
|
isSelectable?: boolean
|
||||||
|
fontWeight?: 'normal' | 'medium' | 'semi-bold'
|
||||||
|
paddingLeft?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DropdownTextItem {
|
export interface DropdownTextItem {
|
||||||
|
@ -38,6 +38,10 @@
|
|||||||
"NoColor": "No color.",
|
"NoColor": "No color.",
|
||||||
"NoColorInfo": "This won't show up on the front of cards.",
|
"NoColorInfo": "This won't show up on the front of cards.",
|
||||||
"Checklist": "Checklist",
|
"Checklist": "Checklist",
|
||||||
|
"AddChecklistItem": "Add an item",
|
||||||
|
"ChecklistDropdownNone": "(none)",
|
||||||
|
"ShowDoneChecklistItems": "Show checked items ({done})",
|
||||||
|
"HideDoneChecklistItems": "Hide checked items",
|
||||||
"Dates": "Dates",
|
"Dates": "Dates",
|
||||||
"Attachments": "Attachments",
|
"Attachments": "Attachments",
|
||||||
"AddAttachment": "Add an attachment",
|
"AddAttachment": "Add an attachment",
|
||||||
|
@ -38,6 +38,10 @@
|
|||||||
"NoColor": "Без цвета.",
|
"NoColor": "Без цвета.",
|
||||||
"NoColorInfo": "Не будет показываться на доске.",
|
"NoColorInfo": "Не будет показываться на доске.",
|
||||||
"Checklist": "Списки",
|
"Checklist": "Списки",
|
||||||
|
"AddChecklistItem": "Добавить",
|
||||||
|
"ChecklistDropdownNone": "(не выбрано)",
|
||||||
|
"ShowDoneChecklistItems": "Показать отмеченные ({done})",
|
||||||
|
"HideDoneChecklistItems": "Скрыть отмеченные",
|
||||||
"Dates": "Дата",
|
"Dates": "Дата",
|
||||||
"Attachments": "Прикрепленное",
|
"Attachments": "Прикрепленное",
|
||||||
"AddAttachment": "Прикрепить",
|
"AddAttachment": "Прикрепить",
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
import { Panel } from '@anticrm/panel'
|
import { Panel } from '@anticrm/panel'
|
||||||
import { getResource } from '@anticrm/platform'
|
import { getResource } from '@anticrm/platform'
|
||||||
import { createQuery, getClient } from '@anticrm/presentation'
|
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 task from '@anticrm/task'
|
||||||
import { StyledTextBox } from '@anticrm/text-editor'
|
import { StyledTextBox } from '@anticrm/text-editor'
|
||||||
import { Button, EditBox, Icon, Label } from '@anticrm/ui'
|
import { Button, EditBox, Icon, Label } from '@anticrm/ui'
|
||||||
@ -30,6 +30,7 @@
|
|||||||
import { updateCard } from '../utils/CardUtils'
|
import { updateCard } from '../utils/CardUtils'
|
||||||
import CardActions from './editor/CardActions.svelte'
|
import CardActions from './editor/CardActions.svelte'
|
||||||
import CardAttachments from './editor/CardAttachments.svelte'
|
import CardAttachments from './editor/CardAttachments.svelte'
|
||||||
|
import CardChecklist from './editor/CardChecklist.svelte'
|
||||||
import CardDetails from './editor/CardDetails.svelte'
|
import CardDetails from './editor/CardDetails.svelte'
|
||||||
|
|
||||||
export let _id: Ref<Card>
|
export let _id: Ref<Card>
|
||||||
@ -38,20 +39,33 @@
|
|||||||
const client = getClient()
|
const client = getClient()
|
||||||
const cardQuery = createQuery()
|
const cardQuery = createQuery()
|
||||||
const stateQuery = createQuery()
|
const stateQuery = createQuery()
|
||||||
|
const checklistsQuery = createQuery()
|
||||||
|
|
||||||
let object: Card | undefined
|
let object: Card | undefined
|
||||||
let state: State | undefined
|
let state: State | undefined
|
||||||
let handleMove: (e: Event) => void
|
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 = result[0]
|
||||||
})
|
})
|
||||||
|
|
||||||
$: object?.state &&
|
$: object?.state &&
|
||||||
stateQuery.query(task.class.State, { _id: object.state }, async (result) => {
|
stateQuery.query(task.class.State, { _id: object.state }, (result) => {
|
||||||
state = result[0]
|
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) => {
|
getCardActions(client, { _id: board.cardAction.Move }).then(async (result) => {
|
||||||
if (result[0]?.handler) {
|
if (result[0]?.handler) {
|
||||||
const handler = await getResource(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(() => {
|
onMount(() => {
|
||||||
dispatch('open', { ignoreKeys: ['comments', 'number', 'title'] })
|
dispatch('open', { ignoreKeys: ['comments', 'number', 'title'] })
|
||||||
})
|
})
|
||||||
@ -130,8 +138,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CardAttachments value={object} />
|
<CardAttachments value={object} />
|
||||||
<!-- TODO checklists -->
|
{#each checklists as checklist}
|
||||||
<!-- <CardActivity bind:value={object} /> -->
|
<CardChecklist value={checklist} />
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</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 EditCard from './components/EditCard.svelte'
|
||||||
import KanbanCard from './components/KanbanCard.svelte'
|
import KanbanCard from './components/KanbanCard.svelte'
|
||||||
import KanbanView from './components/KanbanView.svelte'
|
import KanbanView from './components/KanbanView.svelte'
|
||||||
|
import AddChecklist from './components/popups/AddChecklist.svelte'
|
||||||
import AttachmentPicker from './components/popups/AttachmentPicker.svelte'
|
import AttachmentPicker from './components/popups/AttachmentPicker.svelte'
|
||||||
import CardLabelsPopup from './components/popups/CardLabelsPopup.svelte'
|
import CardLabelsPopup from './components/popups/CardLabelsPopup.svelte'
|
||||||
import MoveCard from './components/popups/MoveCard.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))
|
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> {
|
async function showEditMembersPopup (object: Card, client: Client, e?: Event): Promise<void> {
|
||||||
showPopup(
|
showPopup(
|
||||||
UsersPopup,
|
UsersPopup,
|
||||||
@ -126,6 +131,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
SendToBoard: unarchiveCard,
|
SendToBoard: unarchiveCard,
|
||||||
Delete: showDeleteCardPopup,
|
Delete: showDeleteCardPopup,
|
||||||
Members: showEditMembersPopup,
|
Members: showEditMembersPopup,
|
||||||
|
Checklist: showChecklistsPopup,
|
||||||
Copy: showCopyCardPopup,
|
Copy: showCopyCardPopup,
|
||||||
Cover: showCoverPopup
|
Cover: showCoverPopup
|
||||||
},
|
},
|
||||||
|
@ -59,6 +59,10 @@ export default mergeIds(boardId, board, {
|
|||||||
NoColor: '' as IntlString,
|
NoColor: '' as IntlString,
|
||||||
NoColorInfo: '' as IntlString,
|
NoColorInfo: '' as IntlString,
|
||||||
Checklist: '' as IntlString,
|
Checklist: '' as IntlString,
|
||||||
|
AddChecklistItem: '' as IntlString,
|
||||||
|
ChecklistDropdownNone: '' as IntlString,
|
||||||
|
ShowDoneChecklistItems: '' as IntlString,
|
||||||
|
HideDoneChecklistItems: '' as IntlString,
|
||||||
Dates: '' as IntlString,
|
Dates: '' as IntlString,
|
||||||
Attachments: '' as IntlString,
|
Attachments: '' as IntlString,
|
||||||
AddAttachment: '' as IntlString,
|
AddAttachment: '' as IntlString,
|
||||||
|
@ -55,7 +55,7 @@ export { default as LinkPresenter } from './components/LinkPresenter.svelte'
|
|||||||
export * from './context'
|
export * from './context'
|
||||||
export * from './selection'
|
export * from './selection'
|
||||||
export { buildModel, getCollectionCounter, getObjectPresenter, LoadingProps } from './utils'
|
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> => ({
|
export default async (): Promise<Resources> => ({
|
||||||
actionImpl: actionImpl,
|
actionImpl: actionImpl,
|
||||||
|
Loading…
Reference in New Issue
Block a user