mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-22 00:10:37 +00:00
234 lines
7.0 KiB
Svelte
234 lines
7.0 KiB
Svelte
<!--
|
|
// 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 core, {
|
|
AttachedDoc,
|
|
Class,
|
|
Doc,
|
|
DocumentQuery,
|
|
DocumentUpdate,
|
|
FindOptions, Ref, Space
|
|
} from '@anticrm/core'
|
|
import { getResource } from '@anticrm/platform'
|
|
import { createQuery, getClient } from '@anticrm/presentation'
|
|
import task, { calcRank, DocWithRank } from '@anticrm/task'
|
|
import { AnySvelteComponent, getPlatformColor, Loading, ScrollBox } from '@anticrm/ui'
|
|
import { slide } from 'svelte/transition'
|
|
import KanbanPanel from './KanbanPanel.svelte'
|
|
|
|
type StateType = any
|
|
type Item = DocWithRank & { state: StateType; doneState: StateType | null }
|
|
|
|
export let _class: Ref<Class<Item>>
|
|
export let space: Ref<Space>
|
|
// export let open: AnyComponent
|
|
export let search: string
|
|
export let options: FindOptions<Item> | undefined
|
|
// export let config: string[]
|
|
|
|
type TypeState = { _id: StateType; title: string; color: number }
|
|
|
|
export let states: TypeState[] = []
|
|
|
|
export let stateQuery: DocumentQuery<Item> = {
|
|
/// doneState: null
|
|
}
|
|
|
|
let objects: Item[] = []
|
|
|
|
const objsQ = createQuery()
|
|
$: objsQ.query(
|
|
_class,
|
|
{
|
|
space,
|
|
...stateQuery,
|
|
...(search !== '' ? { $search: search } : {})
|
|
},
|
|
(result) => {
|
|
objects = result
|
|
},
|
|
{
|
|
...options
|
|
}
|
|
)
|
|
|
|
function getStateObjects (objects: Item[], state: TypeState, dragItem?: Item): {it:Item, prev?:Item, next?: Item}[] {
|
|
const stateCards = objects.filter(it => it.state === state._id)
|
|
stateCards.sort((a, b) => a.rank.localeCompare(b.rank))
|
|
return stateCards.map((it, idx, arr) => ({ it, prev: arr[idx - 1], next: arr[idx + 1] }))
|
|
}
|
|
|
|
async function updateItem (item: Item, update: DocumentUpdate<Item>) {
|
|
if (client.getHierarchy().isDerived(_class, core.class.AttachedDoc)) {
|
|
const adoc: AttachedDoc = item as Doc as AttachedDoc
|
|
await client.updateCollection(
|
|
_class,
|
|
space,
|
|
adoc._id as Ref<Doc> as Ref<AttachedDoc>,
|
|
adoc.attachedTo,
|
|
adoc.attachedToClass,
|
|
adoc.collection,
|
|
update
|
|
)
|
|
} else {
|
|
await client.updateDoc(item._class, item.space, item._id, update)
|
|
}
|
|
}
|
|
|
|
async function move (state: StateType) {
|
|
if (dragCard === undefined) {
|
|
return
|
|
}
|
|
let updates: DocumentUpdate<Item> = {}
|
|
|
|
if (dragCardInitialState !== state) {
|
|
updates = {
|
|
...updates,
|
|
state
|
|
}
|
|
}
|
|
|
|
if (dragCardInitialRank !== dragCard.rank) {
|
|
updates = {
|
|
...updates,
|
|
rank: dragCard.rank
|
|
}
|
|
}
|
|
|
|
if (Object.keys(updates).length > 0) {
|
|
await updateItem(dragCard, updates)
|
|
}
|
|
dragCard = undefined
|
|
}
|
|
|
|
const client = getClient()
|
|
|
|
let dragCard: Item | undefined
|
|
let dragCardInitialRank: string
|
|
let dragCardInitialState: StateType
|
|
|
|
async function cardPresenter (_class: Ref<Class<Doc>>): Promise<AnySvelteComponent> {
|
|
const clazz = client.getHierarchy().getClass(_class)
|
|
const presenterMixin = client.getHierarchy().as(clazz, task.mixin.KanbanCard)
|
|
return await getResource(presenterMixin.card)
|
|
}
|
|
|
|
let isDragging = false
|
|
|
|
async function updateDone (query: DocumentUpdate<Item>): Promise<void> {
|
|
isDragging = false
|
|
if (dragCard === undefined) {
|
|
return
|
|
}
|
|
await updateItem(dragCard, query)
|
|
}
|
|
function doCalcRank (object: {prev?: Item, it: Item, next?: Item}, event: DragEvent & { currentTarget: EventTarget & HTMLDivElement}): string {
|
|
const rect = event.currentTarget.getBoundingClientRect()
|
|
|
|
if (event.clientY < rect.top + rect.height * 2 / 3) {
|
|
return calcRank(object.prev, object.it)
|
|
} else {
|
|
return calcRank(object.it, object.next)
|
|
}
|
|
}
|
|
const slideD = (node: any, args:any) => args.isDragging ? slide(node, args) : {}
|
|
</script>
|
|
|
|
{#await cardPresenter(_class)}
|
|
<Loading />
|
|
{:then presenter}
|
|
<div class="flex-col kanban-container">
|
|
<div class="scrollable">
|
|
<ScrollBox>
|
|
<div class="kanban-content">
|
|
{#each states as state}
|
|
<KanbanPanel
|
|
label={state.title}
|
|
color={getPlatformColor(state.color)}
|
|
on:dragover={(event) => {
|
|
event.preventDefault()
|
|
if (dragCard !== undefined && dragCard.state !== state._id) {
|
|
dragCard.state = state._id
|
|
const objs = getStateObjects(objects, state)
|
|
dragCard.rank = calcRank(objs[objs.length - 1]?.it, undefined)
|
|
}
|
|
}}
|
|
on:drop={() => {
|
|
move(state._id)
|
|
isDragging = false
|
|
}}
|
|
>
|
|
<!-- <KanbanCardEmpty label={'Create new application'} /> -->
|
|
{#each getStateObjects(objects, state, dragCard) as object}
|
|
<div transition:slideD|local={{ isDragging }}
|
|
class="step-tb75"
|
|
on:dragover|preventDefault={(evt) => {
|
|
if (dragCard !== undefined) {
|
|
dragCard.rank = doCalcRank(object, evt)
|
|
}
|
|
}}
|
|
on:drop|preventDefault={(evt) => {
|
|
if (dragCard !== undefined) {
|
|
dragCard.rank = doCalcRank(object, evt)
|
|
}
|
|
isDragging = false
|
|
}}
|
|
>
|
|
<svelte:component
|
|
this={presenter}
|
|
object={object.it}
|
|
dragged={isDragging && object.it._id === dragCard?._id}
|
|
draggable={true}
|
|
on:dragstart={() => {
|
|
dragCardInitialState = state._id
|
|
dragCardInitialRank = object.it.rank
|
|
dragCard = object.it
|
|
isDragging = true
|
|
}}
|
|
on:dragend={() => {
|
|
isDragging = false
|
|
}}
|
|
/>
|
|
</div>
|
|
{/each}
|
|
</KanbanPanel>
|
|
{/each}
|
|
<!-- <KanbanPanelEmpty label={'Add new column'} /> -->
|
|
</div>
|
|
</ScrollBox>
|
|
</div>
|
|
{#if isDragging}
|
|
<slot name="doneBar" onDone={updateDone} />
|
|
{/if}
|
|
</div>
|
|
{/await}
|
|
|
|
<style lang="scss">
|
|
.kanban-container {
|
|
position: relative;
|
|
height: 100%;
|
|
background: var(--board-bg-color);
|
|
}
|
|
.kanban-content {
|
|
display: flex;
|
|
margin: 1.5rem 2rem;
|
|
height: 100%;
|
|
}
|
|
|
|
.scrollable {
|
|
height: 100%;
|
|
}
|
|
</style>
|