mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-10 09:22:23 +00:00
231 lines
6.9 KiB
Svelte
231 lines
6.9 KiB
Svelte
<!--
|
|
// 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 type { Ref, SpaceWithStates, State, Class, Obj } from '@anticrm/core'
|
|
import { CircleButton, IconAdd, Label, IconMoreH, ActionIcon, showPopup, ScrollBox } from '@anticrm/ui'
|
|
import { createQuery, getClient, AttributeEditor } from '@anticrm/presentation'
|
|
import type { Kanban } from '@anticrm/view'
|
|
import { createEventDispatcher } from 'svelte'
|
|
import Close from './icons/Close.svelte'
|
|
import Circles from './icons/Circles.svelte'
|
|
import Status from './icons/Status.svelte'
|
|
import ColorsPopup from './ColorsPopup.svelte'
|
|
import StatusesPopup from './StatusesPopup.svelte'
|
|
|
|
import core from '@anticrm/core'
|
|
import view from '@anticrm/view'
|
|
|
|
export let _id: Ref<SpaceWithStates>
|
|
export let spaceClass: Ref<Class<Obj>>
|
|
|
|
let kanban: Kanban | undefined
|
|
|
|
let states: State[] = []
|
|
let elements: HTMLElement[] = []
|
|
|
|
const client = getClient()
|
|
|
|
function sort(states: State[]): State[] {
|
|
if (kanban === undefined || states.length === 0) { return [] }
|
|
console.log(states)
|
|
const map = states.reduce((map, state) => { map.set(state._id, state); return map }, new Map<Ref<State>, State>())
|
|
console.log(kanban.states)
|
|
const x = kanban.states.map(id => map.get(id) as State )
|
|
return x
|
|
}
|
|
|
|
const kanbanQuery = createQuery()
|
|
$: kanbanQuery.query(view.class.Kanban, { attachedTo: _id }, result => { kanban = result[0] })
|
|
|
|
const query = createQuery()
|
|
$: query.query(core.class.State, { _id: { $in: kanban?.states ?? [] } }, result => { states = sort(result) })
|
|
|
|
let selected: number | undefined
|
|
let dragState: Ref<State>
|
|
let dragStateInitialPosition: number
|
|
|
|
function dragswap(ev: MouseEvent, i: number): boolean {
|
|
let s = selected as number
|
|
if (i < s) {
|
|
return ev.offsetY < elements[i].offsetHeight / 2
|
|
} else if (i > s) {
|
|
return ev.offsetY > elements[i].offsetHeight / 2
|
|
}
|
|
return false
|
|
}
|
|
|
|
function dragover(ev: MouseEvent, i: number) {
|
|
let s = selected as number
|
|
if (dragswap(ev, i)) {
|
|
const dragover = states[i]
|
|
const dragging = states[s]
|
|
states[i] = dragging
|
|
states[s] = dragover
|
|
selected = i
|
|
}
|
|
}
|
|
|
|
async function move(to: number) {
|
|
client.updateDoc(view.class.Kanban, _id, (kanban as Kanban)._id, {
|
|
$move: {
|
|
states: {
|
|
$value: dragState,
|
|
$position: to
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
async function addStatus () {
|
|
const state = await client.createDoc(core.class.State, _id, {
|
|
title: 'New State',
|
|
color: '#7C6FCD'
|
|
})
|
|
await client.updateDoc(view.class.Kanban, _id, (kanban as Kanban)._id, {
|
|
$push: {
|
|
states: state
|
|
}
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<div class="flex-col floatdialog-container">
|
|
<div class="flex-between header">
|
|
<div class="flex-grow flex-col">
|
|
<div class="flex-row-center">
|
|
<div class="icon"><Status size={'small'} /></div>
|
|
<span class="overflow-label title">Manage application statuses within vacancy</span>
|
|
</div>
|
|
<div class="overflow-label subtitle">Vacancy name</div>
|
|
</div>
|
|
<div class="tool" on:click={() => dispatch('close')}><Close size={'small'} /></div>
|
|
</div>
|
|
<div class="flex-grow flex-col content">
|
|
<div class="flex-no-shrink flex-between states-header">
|
|
<Label label={'ACTIVE STATUSES'} />
|
|
<div on:click={addStatus}><CircleButton icon={IconAdd} size={'medium'} /></div>
|
|
</div>
|
|
<div class="vScroll"><div class="h-full">
|
|
{#each states as state, i}
|
|
{#if state}
|
|
<div bind:this={elements[i]} class="flex-between states" draggable={true}
|
|
on:dragover|preventDefault={(ev) => {
|
|
dragover(ev, i)
|
|
}}
|
|
on:drop|preventDefault={() => {
|
|
move(i)
|
|
}}
|
|
on:dragstart={() => {
|
|
dragStateInitialPosition = selected = i
|
|
dragState = states[i]._id
|
|
}}
|
|
on:dragend={() => {
|
|
selected = undefined
|
|
}}
|
|
>
|
|
<div class="bar"><Circles /></div>
|
|
<div class="color" style="background-color: {state.color}"
|
|
on:click={() => {
|
|
showPopup(ColorsPopup, {}, elements[i], (result) => { if (result) state.color = result })
|
|
}}
|
|
/>
|
|
<div class="flex-grow caption-color"><AttributeEditor maxWidth="20rem" _class={core.class.State} object={state} key="title"/></div>
|
|
<div class="tool hover-trans"
|
|
on:click={(ev) => {
|
|
showPopup(StatusesPopup, { kanban, state, spaceClass }, ev.target, (result) => { if (result) console.log('StatusesPopup:', result) })
|
|
}}
|
|
>
|
|
<IconMoreH size={'medium'} />
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
{/each}
|
|
</div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<style lang="scss">
|
|
.floatdialog-container {
|
|
margin: 2rem 1rem 1.25rem 0;
|
|
height: calc(100% - 3.25rem);
|
|
background: var(--theme-dialog-bg-spec);
|
|
border-radius: 1.25rem;
|
|
box-shadow: var(--theme-dialog-shadow);
|
|
backdrop-filter: blur(15px);
|
|
|
|
.header {
|
|
padding: 0 2rem 0 2.5rem;
|
|
height: 4.5rem;
|
|
min-height: 4.5rem;
|
|
|
|
.icon {
|
|
margin-right: .5rem;
|
|
opacity: .6;
|
|
}
|
|
.title {
|
|
font-weight: 500;
|
|
font-size: 1rem;
|
|
color: var(--theme-caption-color);
|
|
}
|
|
.subtitle {
|
|
font-size: .75rem;
|
|
color: var(--theme-content-dark-color);
|
|
}
|
|
.tool {
|
|
margin-left: 2.5rem;
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
.content { margin: 1rem 2.5rem 1rem 2.5rem; }
|
|
}
|
|
|
|
.states {
|
|
padding: .625rem 1rem;
|
|
color: #fff;
|
|
background-color: rgba(67, 67, 72, .3);
|
|
border: 1px solid var(--theme-bg-accent-color);
|
|
border-radius: .75rem;
|
|
user-select: none;
|
|
|
|
&-header {
|
|
margin-bottom: 1rem;
|
|
font-weight: 600;
|
|
font-size: .75rem;
|
|
color: var(--theme-content-trans-color);
|
|
}
|
|
|
|
.bar {
|
|
margin-right: .375rem;
|
|
width: .375rem;
|
|
height: 1rem;
|
|
opacity: .4;
|
|
cursor: grabbing;
|
|
}
|
|
.color {
|
|
margin-right: .75rem;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
border-radius: .25rem;
|
|
cursor: pointer;
|
|
}
|
|
.tool { margin-left: 1rem; }
|
|
}
|
|
.states + .states { margin-top: .5rem; }
|
|
</style>
|