join_admin/src/components/Kanban/index.vue

282 lines
5.5 KiB
Vue

<template>
<div class="drag-container">
<ul class="drag-list">
<li
v-for="stage in stages"
:key="stage.text"
class="drag-column"
:class="{ ['drag-column-' + stage.text]: true }"
>
<span class="drag-column-header">
<slot :name="stage.text">
<h2>{{ stage.text }}</h2>
</slot>
</span>
<img v-if="stage.image" class="personImage" :src="stage.image">
<div class="drag-options" />
<ul ref="list" class="drag-inner-list" :data-status="stage.text">
<li
v-for="block in getBlocks(stage.text)"
:key="block.id"
class="drag-item"
:data-block-id="block.id"
>
<slot :name="block.id">
<strong>{{ block.title }}</strong>
</slot>
</li>
</ul>
<div class="drag-column-footer">
<slot :name="`footer-${stage.text}`" />
</div>
</li>
</ul>
</div>
</template>
<script>
import dragula from 'dragula'
import { Machine } from 'xstate'
export default {
name: 'KanbanBoard',
props: {
stages: {
type: Array,
required: true
},
blocks: {
type: Array,
required: true
},
config: {
type: Object,
default: () => ({})
},
stateMachineConfig: {
type: Object,
default: null
}
},
data() {
return {
machine: null
}
},
computed: {
localBlocks() {
return this.blocks
}
},
updated() {
this.drake.containers = this.$refs.list
},
mounted() {
this.config.accepts = this.config.accepts || this.accepts
this.drake = dragula(this.$refs.list, this.config)
.on('drag', el => {
el.classList.add('is-moving')
})
.on('drop', (block, list, source) => {
let index = 0
for (index = 0; index < list.children.length; index += 1) {
if (list.children[index].classList.contains('is-moving')) break
}
let newState = list.dataset.status
if (this.machine) {
const transition = this.findTransition(list, source)
if (!transition) return
newState = this.machine.transition(source.dataset.status, transition)
.value
}
this.$emit('update-block', block.dataset.blockId, newState, index)
})
.on('dragend', el => {
el.classList.remove('is-moving')
window.setTimeout(() => {
el.classList.add('is-moved')
window.setTimeout(() => {
el.classList.remove('is-moved')
}, 600)
}, 100)
})
},
created() {
if (!this.stateMachineConfig) return
this.machine = Machine(this.stateMachineConfig)
},
methods: {
getBlocks(status) {
return this.localBlocks.filter(block => block.status === status)
},
findPossibleTransitions(sourceState) {
return this.machine.config.states[sourceState].on || {}
},
findTransition(target, source) {
const targetState = target.dataset.status
const sourceState = source.dataset.status
const possibleTransitions = this.findPossibleTransitions(sourceState)
return Object.keys(possibleTransitions).find(
transition => possibleTransitions[transition] === targetState
)
},
accepts(block, target, source) {
if (!this.machine) return true
const targetState = target.dataset.status
const sourceState = source.dataset.status
return Object.values(this.findPossibleTransitions(sourceState)).includes(
targetState
)
}
}
}
</script>
<style lang="scss">
.personImage {
height: 100px;
}
$ease-out: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
ul.drag-list,
ul.drag-inner-list {
list-style-type: none;
margin: 0;
padding: 0;
}
.drag-container {
max-width: 1000px;
margin: 20px auto;
}
.drag-list {
display: flex;
align-items: flex-start;
@media (max-width: 690px) {
display: block;
}
}
.drag-column {
flex: 1;
margin: 0 10px;
position: relative;
background: rgba(black, 0.2);
overflow: hidden;
@media (max-width: 690px) {
margin-bottom: 30px;
}
h2 {
font-size: 0.8rem;
margin: 0;
text-transform: uppercase;
font-weight: 600;
}
}
.drag-column-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
}
.drag-inner-list {
min-height: 50px;
color: white;
}
.drag-item {
padding: 10px;
margin: 10px;
//height: 100px;
background: rgba(black, 0.4);
transition: $ease-out;
&.is-moving {
transform: scale(1.5);
background: rgba(black, 0.8);
}
}
.drag-header-more {
cursor: pointer;
}
.drag-options {
position: absolute;
top: 44px;
left: 0;
width: 100%;
height: 100%;
padding: 10px;
transform: translateX(100%);
opacity: 0;
transition: $ease-out;
&.active {
transform: translateX(0);
opacity: 1;
}
&-label {
display: block;
margin: 0 0 5px 0;
input {
opacity: 0.6;
}
span {
display: inline-block;
font-size: 0.9rem;
font-weight: 400;
margin-left: 5px;
}
}
}
/* Dragula CSS */
.gu-mirror {
position: fixed !important;
margin: 0 !important;
z-index: 9999 !important;
opacity: 0.8;
list-style-type: none;
}
.gu-hide {
display: none !important;
}
.gu-unselectable {
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
}
.gu-transit {
opacity: 0.2;
}
</style>