Drag & Drop
This commit is contained in:
parent
16996de532
commit
cc9ddc21a0
|
@ -26,10 +26,27 @@ export async function newCardApi(projectId: number): Promise<Card> {
|
|||
}
|
||||
|
||||
export async function deleteCardApi(cardID: number): Promise<void> {
|
||||
const response = await api.delete(`/v2/cards/${cardID}`);
|
||||
const response = await api.delete(`/v1/cards/${cardID}`);
|
||||
|
||||
if (response.status !== status.NoContent) {
|
||||
processError(response, 'Failed to delete card');
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateCardTagApi(
|
||||
cardID: number,
|
||||
tagID: number,
|
||||
option_id: number,
|
||||
value: string
|
||||
): Promise<void> {
|
||||
const response = await api.put(`/v1/cards/${cardID}/tags/${tagID}`, {
|
||||
option_id: option_id,
|
||||
value: value
|
||||
});
|
||||
|
||||
if (response.status !== status.NoContent) {
|
||||
processError(response, 'Failed to update card tag');
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
<script lang="ts">
|
||||
import type { Card } from '../stores/interfaces';
|
||||
import projectTags from '../stores/projectTags';
|
||||
import { currentModalCard } from '../stores/smallStore';
|
||||
import { currentDraggedCard, currentModalCard } from '../stores/smallStore';
|
||||
import ModalCard from './modal_card.svelte';
|
||||
|
||||
export let card: Card;
|
||||
</script>
|
||||
|
||||
<!-- on:dragend={() => currentDraggedCard.set(null)} -->
|
||||
<div
|
||||
class="card"
|
||||
tabindex="0"
|
||||
draggable={true}
|
||||
on:dragstart={() => currentDraggedCard.set(card)}
|
||||
on:click={() => ($currentModalCard = card.id)}
|
||||
role="button"
|
||||
on:keydown={(e) => {
|
||||
|
|
|
@ -1,15 +1,76 @@
|
|||
<script lang="ts">
|
||||
import { updateCardTagApi } from '../api/cards';
|
||||
import type { Card, TagOption } from '../stores/interfaces';
|
||||
import { cards, currentDraggedCard } from '../stores/smallStore';
|
||||
import api, { processError } from '../utils/api';
|
||||
import status from '../utils/status';
|
||||
import CardC from './card.svelte';
|
||||
|
||||
export let title: string;
|
||||
export let cards: Card[] = [];
|
||||
export let option: TagOption;
|
||||
export let columnCards: Card[] = [];
|
||||
|
||||
async function onDrop(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
if ($currentDraggedCard && $currentDraggedCard.tags) {
|
||||
for (let tag of $currentDraggedCard.tags) {
|
||||
if (tag.tag_id == option.tag_id) {
|
||||
try {
|
||||
if (tag.option_id == option.id) return;
|
||||
// DELETE
|
||||
if (tag.option_id !== -1 && option.id === -1) {
|
||||
const response = await api.delete(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`);
|
||||
|
||||
if (response.status !== status.NoContent) {
|
||||
processError(response, 'Failed to delete tag');
|
||||
return;
|
||||
}
|
||||
}
|
||||
// CREATE
|
||||
else if (tag.option_id == -1 && option.id !== -1) {
|
||||
const response = await api.post(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`, {
|
||||
value: tag.value,
|
||||
option_id: option.id
|
||||
});
|
||||
if (response.status !== status.Created) {
|
||||
processError(response, 'Failed to create tag');
|
||||
return;
|
||||
}
|
||||
}
|
||||
// UPDATE
|
||||
else {
|
||||
const response = await api.put(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`, {
|
||||
value: tag.value,
|
||||
option_id: option.id
|
||||
});
|
||||
if (response.status !== status.NoContent) {
|
||||
processError(response, 'Failed to update tag');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
tag.option_id = option.id;
|
||||
cards.reload();
|
||||
} catch (e) {}
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentDraggedCard.set(null);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="column">
|
||||
<h3>{title}</h3>
|
||||
<div
|
||||
class="column"
|
||||
role="listbox"
|
||||
tabindex="-1"
|
||||
on:drop={onDrop}
|
||||
on:dragover={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<h3>{option.value}</h3>
|
||||
<ul>
|
||||
{#each cards as card}
|
||||
{#each columnCards as card}
|
||||
<CardC {card} />
|
||||
{/each}
|
||||
</ul>
|
||||
|
@ -21,7 +82,8 @@
|
|||
}
|
||||
|
||||
.column {
|
||||
width: 200px;
|
||||
margin: 0 10px;
|
||||
width: 200px;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { type Project, type Card, parseCards, type View } from '../stores/interfaces';
|
||||
import { type Project, type Card, type TagOption, type View } from '../stores/interfaces';
|
||||
import projectTags from '../stores/projectTags';
|
||||
import { deleteCardApi, newCardApi } from '../api/cards';
|
||||
import { getProjectAPI, getProjectCardsAPI } from '../api/projects';
|
||||
import { getProjectAPI } from '../api/projects';
|
||||
import Column from './column.svelte';
|
||||
import { cards, currentModalCard, currentView } from '../stores/smallStore';
|
||||
import { cards, currentView } from '../stores/smallStore';
|
||||
|
||||
export let projectId: number;
|
||||
|
||||
|
@ -69,7 +68,7 @@
|
|||
</svelte:head>
|
||||
|
||||
{#if project}
|
||||
<div id="project">
|
||||
<section>
|
||||
<header>
|
||||
<h2>{project.title}</h2>
|
||||
<button on:click={newCard}>New card</button>
|
||||
|
@ -78,22 +77,32 @@
|
|||
<div class="grid">
|
||||
{#each $projectTags[view.primary_tag_id].options as option}
|
||||
<Column
|
||||
title={option.value}
|
||||
cards={$cards.filter((c) => c.tags.map((t) => t.option_id).includes(option.id))}
|
||||
{option}
|
||||
columnCards={$cards.filter((c) => c.tags.map((t) => t.option_id).includes(option.id))}
|
||||
/>
|
||||
{/each}
|
||||
<Column
|
||||
title={`No ${$projectTags[view.primary_tag_id]?.title || 'tag'}`}
|
||||
cards={$cards.filter((c) => c.tags.find((t) => t.tag_id)?.option_id == -1 || false)}
|
||||
option={{
|
||||
id: -1,
|
||||
tag_id: view.primary_tag_id,
|
||||
value: `No ${$projectTags[view.primary_tag_id].title}`
|
||||
}}
|
||||
columnCards={$cards.filter((c) => c.tags.find((t) => t.tag_id)?.option_id == -1 || false)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { toastAlert } from '../utils/toasts';
|
||||
|
||||
export interface Project {
|
||||
id: number;
|
||||
title: string;
|
||||
|
|
|
@ -7,6 +7,8 @@ export const currentView = writable(null as View | null);
|
|||
|
||||
export const currentModalCard = writable(-1);
|
||||
|
||||
export const currentDraggedCard = writable(null as Card | null);
|
||||
|
||||
export const cards = (() => {
|
||||
const { subscribe, set, update } = writable([] as Card[]);
|
||||
|
||||
|
@ -28,6 +30,9 @@ export const cards = (() => {
|
|||
update((cards) => cards.filter((c) => c.id !== card.id));
|
||||
currentModalCard.set(-1);
|
||||
});
|
||||
},
|
||||
reload: () => {
|
||||
update((cards) => cards);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
|
Loading…
Reference in New Issue