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