Create & edit views
This commit is contained in:
parent
5bc43aaba2
commit
bc566280a6
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 576 512">
|
||||||
|
<path
|
||||||
|
d="M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 632 B |
|
@ -53,8 +53,8 @@
|
||||||
<div>
|
<div>
|
||||||
<button class:disabled={true}>Sub-group</button>
|
<button class:disabled={true}>Sub-group</button>
|
||||||
</div>
|
</div>
|
||||||
<button>Filter</button>
|
<button class:disabled={true}>Filter</button>
|
||||||
<button>Sort</button>
|
<button class:disabled={true}>Sort</button>
|
||||||
<button id="newButton" on:click={async () => cards.add(project.id, getEmptyTags())}>New</button>
|
<button id="newButton" on:click={async () => cards.add(project.id, getEmptyTags())}>New</button>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import Column from './column.svelte';
|
import Column from './column.svelte';
|
||||||
import type { Project, View } from '../../stores/interfaces';
|
import type { Project, View } from '../../stores/interfaces';
|
||||||
import projectTags from '../../stores/projectTags';
|
import projectTags from '../../stores/projectTags';
|
||||||
import { cards, currentView } from '../../stores/smallStore';
|
import { cards, currentView, views } from '../../stores/smallStore';
|
||||||
import Header from './header.svelte';
|
import Header from './header.svelte';
|
||||||
|
|
||||||
export let project: Project;
|
export let project: Project;
|
||||||
|
|
|
@ -2,24 +2,48 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import api, { processError } from '../utils/api';
|
import api, { processError } from '../utils/api';
|
||||||
import type { Project, View } from '../stores/interfaces';
|
import type { Project, View } from '../stores/interfaces';
|
||||||
import { currentView } from '../stores/smallStore';
|
import { currentView, views } from '../stores/smallStore';
|
||||||
import ViewIcon from './icons/viewIcon.svelte';
|
import ViewIcon from './icons/viewIcon.svelte';
|
||||||
|
import projectTags from '../stores/projectTags';
|
||||||
|
import EditIcon from './icons/editIcon.svelte';
|
||||||
|
|
||||||
export let project: Project;
|
export let project: Project;
|
||||||
let views: View[];
|
|
||||||
|
let viewEditId: number;
|
||||||
|
let viewEditValue: string;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const response = await api.get(`/v1/projects/${project.id}/views`);
|
console.log('aaa');
|
||||||
|
await views.init(project.id);
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if ($views.length > 0) currentView.set($views[0]);
|
||||||
processError(response, 'Failed to fetch views');
|
});
|
||||||
return;
|
|
||||||
|
async function newView() {
|
||||||
|
if (!$views) return;
|
||||||
|
|
||||||
|
const primaryTagId =
|
||||||
|
$currentView?.primary_tag_id || Object.values($projectTags).find((t) => true)?.id || -1;
|
||||||
|
|
||||||
|
const newView = await views.add(project.id, 'New view', primaryTagId);
|
||||||
|
|
||||||
|
if (!newView) return;
|
||||||
|
|
||||||
|
currentView.set(newView);
|
||||||
|
viewEditId = newView.id;
|
||||||
|
viewEditValue = newView.title;
|
||||||
|
document.getElementById(`viewTitle-${newView.id}`)?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
views = response.data;
|
async function saveView(view: View) {
|
||||||
|
if (!view || !$views.includes(view)) return;
|
||||||
|
if (viewEditId === view.id && viewEditValue !== view.title) {
|
||||||
|
if (!(await views.update(view))) return;
|
||||||
|
}
|
||||||
|
|
||||||
if (views.length > 0) currentView.set(views[0]);
|
viewEditId = -1;
|
||||||
});
|
viewEditValue = '';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
|
@ -32,15 +56,46 @@
|
||||||
{#if views}
|
{#if views}
|
||||||
<h2>{project.title}</h2>
|
<h2>{project.title}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{#each views as view}
|
{#each $views as view}
|
||||||
<li>
|
<!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
|
||||||
<ViewIcon />
|
<li
|
||||||
<!-- on:click={() => {
|
on:click={() => currentView.set(view)}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
currentView.set(view);
|
currentView.set(view);
|
||||||
}} -->
|
}
|
||||||
<span>
|
}}
|
||||||
{view.title}
|
class:active={$currentView === view}
|
||||||
</span>
|
>
|
||||||
|
<ViewIcon />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly={viewEditId !== view.id}
|
||||||
|
bind:value={view.title}
|
||||||
|
class:inEdit={viewEditId === view.id}
|
||||||
|
on:blur={() => saveView(view)}
|
||||||
|
id="viewTitle-{view.id}"
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
saveView(view);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
if (viewEditId === view.id) {
|
||||||
|
saveView(view);
|
||||||
|
} else {
|
||||||
|
viewEditId = view.id;
|
||||||
|
viewEditValue = view.title;
|
||||||
|
document.getElementById(`viewTitle-${view.id}`)?.focus();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -49,7 +104,19 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
<div id="newView" on:click={() => {}}>+ New view</div>
|
<div
|
||||||
|
id="newView"
|
||||||
|
on:click={newView}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
newView();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+ New view
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
@ -93,9 +160,46 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 17px;
|
||||||
|
width: 60%;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.inEdit {
|
||||||
|
background-color: #fff5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 17px;
|
||||||
|
padding: 5px 0;
|
||||||
|
float: right;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: #fff1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
|
|
|
@ -57,3 +57,85 @@ export const cards = (() => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
export const views = (() => {
|
||||||
|
const { subscribe, set, update } = writable([] as View[]);
|
||||||
|
|
||||||
|
const init = async (projectId: number): Promise<boolean> => {
|
||||||
|
const response = await api.get(`/v1/projects/${projectId}/views`);
|
||||||
|
|
||||||
|
if (response.status !== status.OK) {
|
||||||
|
processError(response, 'Failed to get views');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(response.data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const add = async (projectId: number, title: string, primaryTagId: number): Promise<View> => {
|
||||||
|
const response = await api.post(`/v1/views`, {
|
||||||
|
title,
|
||||||
|
project_id: projectId,
|
||||||
|
primary_tag_id: primaryTagId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status !== status.Created) {
|
||||||
|
processError(response, 'Failed to add view');
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
const view: View = {
|
||||||
|
id: response.data.id,
|
||||||
|
title: title,
|
||||||
|
project_id: projectId,
|
||||||
|
primary_tag_id: primaryTagId,
|
||||||
|
secondary_tag_id: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
update((views) => [...views, view]);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
};
|
||||||
|
const remove = async (view: View): Promise<boolean> => {
|
||||||
|
const response = await api.delete(`/v1/views/${view.id}`);
|
||||||
|
|
||||||
|
if (response.status !== status.NoContent) {
|
||||||
|
processError(response, 'Failed to delete view');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
update((views) => views.filter((v) => v.id !== view.id));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const edit = async (view: View): Promise<boolean> => {
|
||||||
|
if (view.title === '') {
|
||||||
|
if (confirm('Are you sure you want to delete this view?')) {
|
||||||
|
return remove(view);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api.put(`/v1/views/${view.id}`, view);
|
||||||
|
|
||||||
|
if (response.status !== status.NoContent) {
|
||||||
|
processError(response, 'Failed to update view');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
update((views) => views.map((v) => (v.id === view.id ? view : v)));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
init,
|
||||||
|
add,
|
||||||
|
remove,
|
||||||
|
update: edit
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
Loading…
Reference in New Issue