Merge pull request #1444 from hcengineering/ano/update-date-presenter

Board: Update Date Presenter to reuse as presenter
This commit is contained in:
Anna No 2022-04-19 21:49:53 +07:00 committed by GitHub
commit 582fb4dad0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 253 additions and 227 deletions

View File

@ -44,10 +44,11 @@
let handleMove: () => void
$: cardQuery.query(_class, { _id }, async (result) => {
object = result[0]
})
object = result[0]
})
$: object?.state && stateQuery.query(task.class.State, { _id: object.state }, async (result) => {
$: object?.state &&
stateQuery.query(task.class.State, { _id: object.state }, async (result) => {
state = result[0]
})
@ -62,7 +63,7 @@
}
})
function change(field: string, value: any) {
function change (field: string, value: any) {
if (object) {
updateCard(client, object, field, value)
}

View File

@ -38,7 +38,7 @@
}
function canDropAttachment (e: DragEvent): boolean {
return !!e.dataTransfer?.items && e.dataTransfer?.items.length > 0;
return !!e.dataTransfer?.items && e.dataTransfer?.items.length > 0
}
</script>

View File

@ -1,67 +1,68 @@
<script lang="ts">
import { ActionIcon, Button, EditBox, IconAdd, IconClose } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import board from '../plugin'
import { ActionIcon, Button, EditBox, IconAdd, IconClose } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import board from '../plugin'
const dispatch = createEventDispatcher()
const dispatch = createEventDispatcher()
let isAdding = false
let newPanelTitle = ''
let isAdding = false
let newPanelTitle = ''
async function onAdd () {
if (!newPanelTitle) return
dispatch('add', newPanelTitle)
newPanelTitle = ''
isAdding = false
}
async function onAdd () {
if (!newPanelTitle) return
dispatch('add', newPanelTitle)
newPanelTitle = ''
isAdding = false
}
</script>
<div class="panel-container step-lr75">
{#if isAdding}
<EditBox
bind:value={newPanelTitle}
maxWidth={'19rem'}
placeholder={board.string.NewListPlaceholder}
focus={true}
/>
<div class="list-add-controls">
<Button
icon={IconAdd}
label={board.string.AddList}
justify={'left'}
on:click={() => { onAdd() }}
/>
<ActionIcon
icon={IconClose}
size={'large'}
action={() => { isAdding = false }}
/>
</div>
{:else}
<Button
<div class="panel-container step-lr75">
{#if isAdding}
<EditBox bind:value={newPanelTitle} maxWidth={'19rem'} placeholder={board.string.NewListPlaceholder} focus={true} />
<div class="list-add-controls">
<Button
icon={IconAdd}
label={board.string.NewList}
label={board.string.AddList}
justify={'left'}
on:click={() => { isAdding = true }}
/>
{/if}
on:click={() => {
onAdd()
}}
/>
<ActionIcon
icon={IconClose}
size={'large'}
action={() => {
isAdding = false
}}
/>
</div>
{:else}
<Button
icon={IconAdd}
label={board.string.NewList}
justify={'left'}
on:click={() => {
isAdding = true
}}
/>
{/if}
</div>
<style lang="scss">
.panel-container {
display: flex;
flex-direction: column;
align-items: stretch;
width: 20rem;
height: fit-content;
background-color: transparent;
border: .125rem solid transparent;
border-radius: .25rem;
}
.list-add-controls {
display: flex;
flex-direction: row;
align-items: center;
margin: 0.5rem 0 0 0;
}
.panel-container {
display: flex;
flex-direction: column;
align-items: stretch;
width: 20rem;
height: fit-content;
background-color: transparent;
border: 0.125rem solid transparent;
border-radius: 0.25rem;
}
.list-add-controls {
display: flex;
flex-direction: row;
align-items: center;
margin: 0.5rem 0 0 0;
}
</style>

View File

@ -23,7 +23,7 @@
import KanbanCard from './KanbanCard.svelte'
import KanbanPanelEmpty from './KanbanPanelEmpty.svelte'
import AddCard from './add-card/AddCard.svelte'
export let _class: Ref<Class<Card>>
export let space: Ref<SpaceWithStates>
export let search: string
@ -59,11 +59,7 @@
const client = getClient()
async function addItem (title: any) {
const lastOne = await client.findOne(
task.class.State,
{ space },
{ sort: { rank: SortingOrder.Descending } }
)
const lastOne = await client.findOne(task.class.State, { space }, { sort: { rank: SortingOrder.Descending } })
await client.createDoc(task.class.State, space, {
title,
color: 9,
@ -73,17 +69,29 @@
/* eslint-disable no-undef */
</script>
<KanbanUI {_class} {space} {search} {options} query={{ doneState: null }} states={states}
fieldName={'state'} rankFieldName={'rank'}>
<svelte:fragment slot='card' let:object let:dragged>
<KanbanUI
{_class}
{space}
{search}
{options}
query={{ doneState: null }}
{states}
fieldName={'state'}
rankFieldName={'rank'}
>
<svelte:fragment slot="card" let:object let:dragged>
<KanbanCard object={castObject(object)} {dragged} />
</svelte:fragment>
<svelte:fragment slot='additionalPanel'>
<KanbanPanelEmpty on:add={(e) => { addItem(e.detail) }} />
<svelte:fragment slot="additionalPanel">
<KanbanPanelEmpty
on:add={(e) => {
addItem(e.detail)
}}
/>
</svelte:fragment>
<svelte:fragment slot="afterCard" let:space={targetSpace} let:state={targetState}>
<AddCard space={targetSpace} state={targetState}/>
<AddCard space={targetSpace} state={targetState} />
</svelte:fragment>
</KanbanUI>

View File

@ -28,7 +28,7 @@
let anchorRef: HTMLDivElement
const client = getClient()
async function addCard(title: string) {
async function addCard (title: string) {
const newCardId = generateId() as Ref<BoardCard>
const sequence = await client.findOne(task.class.Sequence, { attachedTo: board.class.Card })
@ -58,7 +58,7 @@
return client.addCollection(board.class.Card, space, space, board.class.Board, 'cards', value, newCardId)
}
async function addCards(title: string, checkNewLine: boolean = false) {
async function addCards (title: string, checkNewLine: boolean = false) {
if (!checkNewLine) {
return addCard(title.replace('\n', ' '))
}

View File

@ -1,4 +1,4 @@
<script lang='ts'>
<script lang="ts">
import { Button, TextArea, ActionIcon, IconClose } from '@anticrm/ui'
import board from '../../plugin'
@ -10,7 +10,7 @@
let inputRef: TextArea
let openedContainerRef: HTMLDivElement
async function addCard() {
async function addCard () {
if (!title) {
inputRef.focus()
return
@ -21,7 +21,7 @@
title = ''
}
async function onClickOutside(e: any) {
async function onClickOutside (e: any) {
if (openedContainerRef && !openedContainerRef.contains(e.target) && !e.defaultPrevented && isEditing) {
if (title) {
await onAdd(title)
@ -33,12 +33,12 @@
const onKeydown = (e: any) => {
if (e.detail.key !== 'Enter') {
return;
return
}
e.detail.preventDefault()
addCard()
};
}
$: if (inputRef && !title) {
isEditing = true
@ -46,23 +46,23 @@
}
</script>
<svelte:window on:click={onClickOutside}/>
<svelte:window on:click={onClickOutside} />
<div bind:this={openedContainerRef}>
<div class="card-container">
<div class="card-container">
<TextArea
placeholder={board.string.CardTitlePlaceholder}
bind:this={inputRef}
bind:value={title}
on:keydown={onKeydown}
noFocusBorder={true}
placeholder={board.string.CardTitlePlaceholder}
bind:this={inputRef}
bind:value={title}
on:keydown={onKeydown}
noFocusBorder={true}
/>
</div>
<div class="flex-row-center mt-3">
</div>
<div class="flex-row-center mt-3">
<Button label={board.string.AddCard} kind="no-border" on:click={addCard} />
<div class="ml-2" on:click={onClose}>
<ActionIcon icon={IconClose} size={'large'} action={onClose} />
</div>
<ActionIcon icon={IconClose} size={'large'} action={onClose} />
</div>
</div>
</div>
<style lang="scss">
@ -75,4 +75,4 @@
border-radius: 0.25rem;
user-select: none;
}
</style>
</style>

View File

@ -41,12 +41,12 @@
.close-icon {
position: absolute;
top: .75rem;
right: .75rem;
top: 0.75rem;
right: 0.75rem;
}
.header {
padding-bottom: .5rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--divider-color);
}
</style>

View File

@ -28,12 +28,11 @@
let inputFile: HTMLInputElement
async function fetch() {
async function fetch () {
attachments = await client.findAll(attachment.class.Attachment, { space: value.space, attachedTo: value._id })
}
$: value?.attachments && value.attachments > 0 && fetch()
</script>
{#if value !== undefined && value.attachments !== undefined && value.attachments > 0}
@ -55,7 +54,7 @@
<div class="mt-2">
<AddAttachment bind:inputFile objectClass={value._class} objectId={value._id} space={value.space}>
<svelte:fragment slot="control" let:click>
<Button label={board.string.AddAttachment} kind="no-border" on:click={click}/>
<Button label={board.string.AddAttachment} kind="no-border" on:click={click} />
</svelte:fragment>
</AddAttachment>
</div>

View File

@ -14,7 +14,7 @@
// limitations under the License.
-->
<script lang="ts">
import type { Card, CardLabel } from '@anticrm/board'
import type { Card, CardDate, CardLabel } from '@anticrm/board'
import contact, { Employee } from '@anticrm/contact'
import { getResource } from '@anticrm/platform'
@ -71,6 +71,10 @@
labels = []
}
function updateDate (e: CustomEvent<CardDate>) {
client.update(value, { date: e.detail })
}
getCardActions(client, {
_id: { $in: [board.cardAction.Dates, board.cardAction.Labels, board.cardAction.Members] }
}).then(async (result) => {
@ -87,7 +91,6 @@
}
}
})
</script>
{#if value}
@ -123,7 +126,7 @@
<Label label={board.string.Dates} />
</div>
{#key value.date}
<DatePresenter {value} on:click={dateHandler} />
<DatePresenter value={value.date} on:click={dateHandler} on:update={updateDate} />
{/key}
</div>
{/if}

View File

@ -17,7 +17,7 @@
let filteredLabels: CardLabel[] = []
let hovered: Ref<CardLabel> | undefined = undefined
function applySearch() {
function applySearch () {
if (!search || search.trim().length <= 0) {
filteredLabels = boardCardLabels
return
@ -27,14 +27,14 @@
filteredLabels = boardCardLabels.filter((l) => l.title?.toUpperCase().includes(text) ?? false)
}
async function fetchBoardLabels() {
async function fetchBoardLabels () {
if (object.space) {
boardCardLabels = await getBoardLabels(client, object.space)
applySearch()
}
}
function toggle(label: CardLabel) {
function toggle (label: CardLabel) {
if (!object) {
return
}

View File

@ -4,7 +4,7 @@
import { Card, CardDate } from '@anticrm/board'
import calendar from '@anticrm/calendar'
import board from '../../plugin'
import { getClient } from '@anticrm/presentation';
import { getClient } from '@anticrm/presentation'
export let object: Card
@ -24,36 +24,46 @@
const date: CardDate = {}
if (startDate !== undefined) date.startDate = startDate
if (dueDate !== undefined) date.dueDate = dueDate
client.update(object, {date})
client.update(object, { date })
}
</script>
<div class="antiPopup antiPopup-withHeader antiPopup-withTitle antiPopup-withCategory w-85">
<div class="ap-space"/>
<div class="ap-space" />
<div class="fs-title ap-header flex-row-center">
<Label label={board.string.Dates}/>
<Label label={board.string.Dates} />
</div>
<div class="ap-space bottom-divider"/>
<div class="ap-category">
<div class="categoryItem flex-center whitespace-nowrap">
<Label label={board.string.StartDate}/>
</div>
<div class="ap-space bottom-divider" />
<div class="ap-category">
<div class="categoryItem flex-center whitespace-nowrap">
<Label label={board.string.StartDate} />
</div>
<div class="categoryItem p-2 flex-center">
<CheckBox bind:checked={startDateEnabled} on:value={() => {startDate = startDateEnabled ? savedStartDate : undefined}}/>
<CheckBox
bind:checked={startDateEnabled}
on:value={() => {
startDate = startDateEnabled ? savedStartDate : undefined
}}
/>
</div>
<div class="categoryItem w-full p-2">
<DateRangePresenter bind:value={startDate} editable={startDateEnabled} labelNull={board.string.NullDate}/>
<DateRangePresenter bind:value={startDate} editable={startDateEnabled} labelNull={board.string.NullDate} />
</div>
</div>
<div class="ap-category">
<div class="categoryItem flex-center whitespace-nowrap">
<Label label={board.string.DueDate}/>
<Label label={board.string.DueDate} />
</div>
<div class="categoryItem p-2 flex-center">
<CheckBox bind:checked={dueDateEnabled} on:value={() => {dueDate = dueDateEnabled ? savedDueDate : undefined}}/>
<CheckBox
bind:checked={dueDateEnabled}
on:value={() => {
dueDate = dueDateEnabled ? savedDueDate : undefined
}}
/>
</div>
<div class="categoryItem w-full p-2">
<DateRangePresenter bind:value={dueDate} editable={dueDateEnabled} labelNull={board.string.NullDate}/>
<DateRangePresenter bind:value={dueDate} editable={dueDateEnabled} labelNull={board.string.NullDate} />
</div>
</div>
<div class="ap-footer">
@ -68,7 +78,7 @@
label={board.string.Remove}
size={'small'}
on:click={() => {
client.update(object, {date: {}})
client.update(object, { date: {} })
dispatch('close')
}}
/>

View File

@ -4,7 +4,7 @@
import { getClient } from '@anticrm/presentation'
import { Class, Client, Doc, DocumentUpdate, Ref } from '@anticrm/core'
import { getResource, OK, Resource, Status } from '@anticrm/platform'
import { Card } from '@anticrm/board';
import { Card } from '@anticrm/board'
import view from '@anticrm/view'
import board from '../../plugin'
import SpaceSelect from '../selectors/SpaceSelect.svelte'
@ -51,15 +51,15 @@
}
}
$: validate({...object, ...selected}, object._class)
$: validate({ ...object, ...selected }, object._class)
</script>
<div class="antiPopup antiPopup-withHeader antiPopup-withTitle antiPopup-withCategory w-85">
<div class="ap-space"/>
<div class="ap-space" />
<div class="fs-title ap-header flex-row-center">
<Label label={board.string.MoveCard}/>
<Label label={board.string.MoveCard} />
</div>
<div class="ap-space bottom-divider"/>
<div class="ap-space bottom-divider" />
<StatusControl {status} />
<div class="ap-title">
<Label label={board.string.SelectDestination} />
@ -92,7 +92,7 @@
<Button
label={board.string.Move}
size={'small'}
disabled={status !== OK || object.state === selected.state && object.rank === selected.rank}
disabled={status !== OK || (object.state === selected.state && object.rank === selected.rank)}
kind={'primary'}
on:click={move}
/>

View File

@ -18,7 +18,6 @@
export let value: Attachment
// TODO: implement
</script>
<div>

View File

@ -1,31 +1,31 @@
<script lang="ts">
import type { Card } from '@anticrm/board'
import { getClient } from '@anticrm/presentation'
import type { CardDate } from '@anticrm/board'
import { CheckBox, DatePresenter } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
export let value: Card
export let value: CardDate
export let isInline: boolean = false
const client = getClient()
const {date} = value
let isChecked = date?.isChecked
let isChecked = value?.isChecked
const dispatch = createEventDispatcher()
function update(){
if (isChecked === undefined) return
client.update(value, {date: {...date, isChecked}})
function check () {
if (isInline || isChecked === undefined) return
dispatch('update', { ...value, isChecked })
}
</script>
{#if date}
{#if value}
<div class="flex-presenter flex-gap-1 h-full">
<CheckBox bind:checked={isChecked} on:value={update}/>
<CheckBox bind:checked={isChecked} on:value={check} />
<div class="flex-center h-full" on:click>
<div class="flex-row-center background-button-bg-color border-radius-1 w-full">
{#if date.startDate}
<DatePresenter bind:value={date.startDate} />
{#if value.startDate}
<DatePresenter bind:value={value.startDate} />
{/if}
{#if date.startDate && date.dueDate}-{/if}
{#if date.dueDate}
<DatePresenter bind:value={date.dueDate} withTime={true} showIcon={false} />
{#if value.startDate && value.dueDate}-{/if}
{#if value.dueDate}
<DatePresenter bind:value={value.dueDate} withTime={true} showIcon={false} />
{/if}
</div>
</div>

View File

@ -1,41 +1,43 @@
<script lang="ts">
import { IntlString, translate } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation';
import { DropdownLabels } from '@anticrm/ui'
import { Ref, SortingOrder } from '@anticrm/core'
import { DropdownTextItem } from '@anticrm/ui/src/types';
import { calcRank, State } from '@anticrm/task';
import { Card } from '@anticrm/board'
import board from '../../plugin'
import { IntlString, translate } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation'
import { DropdownLabels } from '@anticrm/ui'
import { Ref, SortingOrder } from '@anticrm/core'
import { DropdownTextItem } from '@anticrm/ui/src/types'
import { calcRank, State } from '@anticrm/task'
import { Card } from '@anticrm/board'
import board from '../../plugin'
export let object: Card
export let label: IntlString
export let state: Ref<State>
export let selected: string
export let object: Card
export let label: IntlString
export let state: Ref<State>
export let selected: string
let ranks: DropdownTextItem[] = []
const tasksQuery = createQuery()
tasksQuery.query(
board.class.Card, { state },
async result => {
[ranks] = [...result.filter(t => t._id !== object._id), 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 ranks: DropdownTextItem[] = []
const tasksQuery = createQuery()
tasksQuery.query(
board.class.Card,
{ state },
async (result) => {
;[ranks] = [...result.filter((t) => t._id !== object._id), 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)
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})
}
selected = object.rank
}
},
{ sort: { rank: SortingOrder.Ascending } }
)
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 })
}
selected = object.rank
}
},
{ sort: { rank: SortingOrder.Ascending } }
)
</script>
<DropdownLabels items={ranks} label={label} bind:selected={selected}/>
<DropdownLabels items={ranks} {label} bind:selected />

View File

@ -1,26 +1,23 @@
<script lang="ts">
import { IntlString, translate } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation';
import { DropdownLabels } from '@anticrm/ui'
import { Ref } from '@anticrm/core'
import { DropdownTextItem } from '@anticrm/ui/src/types';
import { Board, Card } from '@anticrm/board'
import board from '../../plugin'
import { IntlString, translate } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation'
import { DropdownLabels } from '@anticrm/ui'
import { Ref } from '@anticrm/core'
import { DropdownTextItem } from '@anticrm/ui/src/types'
import { Board, Card } from '@anticrm/board'
import board from '../../plugin'
export let object: Card
export let label: IntlString
export let selected: Ref<Board>
export let object: Card
export let label: IntlString
export let selected: Ref<Board>
let spaces: DropdownTextItem[] = []
const spacesQuery = createQuery()
spacesQuery.query(
board.class.Board, {},
async result => {
spaces = result.map(({_id, name}) => ({id: _id, label: name}))
const index = spaces.findIndex(({id}) => id === object.space)
spaces[index].label = await translate(board.string.Current, {label: spaces[index].label})
}
)
let spaces: DropdownTextItem[] = []
const spacesQuery = createQuery()
spacesQuery.query(board.class.Board, {}, async (result) => {
spaces = result.map(({ _id, name }) => ({ id: _id, label: name }))
const index = spaces.findIndex(({ id }) => id === object.space)
spaces[index].label = await translate(board.string.Current, { label: spaces[index].label })
})
</script>
<DropdownLabels items={spaces} label={label} bind:selected/>
<DropdownLabels items={spaces} {label} bind:selected />

View File

@ -1,34 +1,35 @@
<script lang="ts">
import { IntlString, translate } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation';
import { DropdownLabels } from '@anticrm/ui'
import { Ref, SortingOrder, Space } from '@anticrm/core'
import { DropdownTextItem } from '@anticrm/ui/src/types';
import task, { State } from '@anticrm/task';
import { Card } from '@anticrm/board'
import board from '../../plugin'
import { IntlString, translate } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation'
import { DropdownLabels } from '@anticrm/ui'
import { Ref, SortingOrder, Space } from '@anticrm/core'
import { DropdownTextItem } from '@anticrm/ui/src/types'
import task, { State } from '@anticrm/task'
import { Card } from '@anticrm/board'
import board from '../../plugin'
export let object: Card
export let label: IntlString
export let selected: Ref<State>
export let space: Ref<Space>
export let object: Card
export let label: IntlString
export let selected: Ref<State>
export let space: Ref<Space>
let states: DropdownTextItem[] = []
const statesQuery = createQuery()
statesQuery.query(
task.class.State, { space },
async result => {
if(!result) return
states = result.map(({_id, title }) => ({id: _id, label: title}));
[{ _id: selected }] = result
if (object.space === space) {
const index = states.findIndex(({id}) => id === object.state)
states[index].label = await translate(board.string.Current, {label: states[index].label})
selected = object.state
}
},
{ sort: { rank: SortingOrder.Ascending } }
)
let states: DropdownTextItem[] = []
const statesQuery = createQuery()
statesQuery.query(
task.class.State,
{ space },
async (result) => {
if (!result) return
states = result.map(({ _id, title }) => ({ id: _id, label: title }))
;[{ _id: selected }] = result
if (object.space === space) {
const index = states.findIndex(({ id }) => id === object.state)
states[index].label = await translate(board.string.Current, { label: states[index].label })
selected = object.state
}
},
{ sort: { rank: SortingOrder.Ascending } }
)
</script>
<DropdownLabels items={states} label={label} bind:selected/>
<DropdownLabels items={states} {label} bind:selected />

View File

@ -36,7 +36,12 @@ export async function createBoardLabels (client: TxOperations, boardRef: Ref<Boa
])
}
export async function createCardLabel (client: TxOperations, boardRef: Ref<Board>, color: string, title?: string): Promise<void> {
export async function createCardLabel (
client: TxOperations,
boardRef: Ref<Board>,
color: string,
title?: string
): Promise<void> {
await client.createDoc(board.class.CardLabel, core.space.Model, {
attachedTo: boardRef,
attachedToClass: board.class.Board,