Sidebar fixed
This commit is contained in:
parent
720fb35326
commit
5e272a9bcc
|
@ -1,6 +1,7 @@
|
||||||
import type Card from '$lib/types/Card';
|
import type Card from '$lib/types/Card';
|
||||||
import Project from '$lib/types/Project';
|
import Project from '$lib/types/Project';
|
||||||
import ProjectTag from '$lib/types/ProjectTag';
|
import ProjectTag from '$lib/types/ProjectTag';
|
||||||
|
import View from '$lib/types/View';
|
||||||
import api, { processError } from '$lib/utils/api';
|
import api, { processError } from '$lib/utils/api';
|
||||||
import { parseCards } from '$lib/utils/parser';
|
import { parseCards } from '$lib/utils/parser';
|
||||||
import status from '$lib/utils/status';
|
import status from '$lib/utils/status';
|
||||||
|
@ -29,7 +30,7 @@ async function create(title: string): Promise<number | null> {
|
||||||
return response.data.id;
|
return response.data.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get(projectId: number): Promise<number | null> {
|
async function get(projectId: number): Promise<Project | null> {
|
||||||
const response = await api.get(`/v1/projects/${projectId}`);
|
const response = await api.get(`/v1/projects/${projectId}`);
|
||||||
|
|
||||||
if (response.status !== status.OK) {
|
if (response.status !== status.OK) {
|
||||||
|
@ -37,7 +38,7 @@ async function get(projectId: number): Promise<number | null> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data;
|
return Project.parse(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update(projectId: number, title: string): Promise<boolean> {
|
async function update(projectId: number, title: string): Promise<boolean> {
|
||||||
|
@ -83,9 +84,20 @@ async function getTags(project: Project): Promise<ProjectTag[]> {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectTags: ProjectTag[] = ProjectTag.parseAll(response.data, project);
|
return ProjectTag.parseAll(response.data, project);
|
||||||
|
}
|
||||||
|
|
||||||
return projectTags;
|
async function getViews(project: Project): Promise<View[]> {
|
||||||
|
const response = await api.get(`/v1/projects/${project.id}/views`);
|
||||||
|
|
||||||
|
if (response.status !== status.OK) {
|
||||||
|
processError(response, 'Failed to fetch views');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const views: View[] = View.parseAll(response.data, project);
|
||||||
|
|
||||||
|
return views;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -95,5 +107,6 @@ export default {
|
||||||
delete: delete_,
|
delete: delete_,
|
||||||
getAll,
|
getAll,
|
||||||
getCards,
|
getCards,
|
||||||
getTags
|
getTags,
|
||||||
|
getViews
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import api, { processError } from '$lib/utils/api';
|
||||||
import status from '$lib/utils/status';
|
import status from '$lib/utils/status';
|
||||||
|
|
||||||
async function create(project: Project): Promise<number | null> {
|
async function create(project: Project): Promise<number | null> {
|
||||||
const response = await api.post('/views', {
|
const response = await api.post('/v1/views', {
|
||||||
project: project.id
|
project: project.id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,17 +16,25 @@ async function create(project: Project): Promise<number | null> {
|
||||||
return response.data.id;
|
return response.data.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update(view: View): Promise<boolean> {
|
async function update(
|
||||||
const response = await api.put(`/views/${view.id}`, {
|
id: number,
|
||||||
project: view.project.id,
|
projectId: number,
|
||||||
primary_tag_id: view.primaryTag?.id,
|
primaryTagId: number | null,
|
||||||
secondary_tag_id: view.secondaryTag?.id,
|
secondaryTagId: number | null,
|
||||||
title: view.title,
|
title: string,
|
||||||
sort_tag_id: view.sortTag?.id,
|
sortTagId: number | null,
|
||||||
sort_direction: view.sortDirection
|
sortDirection: number | null
|
||||||
|
): Promise<boolean> {
|
||||||
|
const response = await api.put(`/v1/views/${id}`, {
|
||||||
|
project_id: projectId,
|
||||||
|
primary_tag_id: primaryTagId,
|
||||||
|
secondary_tag_id: secondaryTagId,
|
||||||
|
title: title,
|
||||||
|
sort_tag_id: sortTagId,
|
||||||
|
sort_direction: sortDirection
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status !== status.OK) {
|
if (response.status !== status.NoContent) {
|
||||||
processError(response, 'Failed to update view');
|
processError(response, 'Failed to update view');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -35,7 +43,7 @@ async function update(view: View): Promise<boolean> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function delete_(viewId: number): Promise<boolean> {
|
async function delete_(viewId: number): Promise<boolean> {
|
||||||
const response = await api.delete(`/views/${viewId}`);
|
const response = await api.delete(`/v1/views/${viewId}`);
|
||||||
|
|
||||||
if (response.status !== status.OK) {
|
if (response.status !== status.OK) {
|
||||||
processError(response, 'Failed to delete view');
|
processError(response, 'Failed to delete view');
|
||||||
|
|
|
@ -1,52 +1,44 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import currentView from '$lib/stores/currentView';
|
import currentView from '$lib/stores/currentView';
|
||||||
import views from '$lib/stores/views';
|
import Project from '$lib/types/Project';
|
||||||
import type Project from '$lib/types/Project';
|
import View, { views } from '$lib/types/View';
|
||||||
import type View from '$lib/types/View';
|
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import projectTags from '../../stores/projectTags';
|
|
||||||
import EditIcon from '../icons/EditIcon.svelte';
|
import EditIcon from '../icons/EditIcon.svelte';
|
||||||
import MenuOpener from '../icons/MenuOpener.svelte';
|
import MenuOpener from '../icons/MenuOpener.svelte';
|
||||||
import ViewIcon from '../icons/ViewIcon.svelte';
|
import ViewIcon from '../icons/ViewIcon.svelte';
|
||||||
|
|
||||||
export let project: Project;
|
export let project: Project;
|
||||||
|
|
||||||
let viewEditId: number;
|
let viewEdit: View | null;
|
||||||
let viewEditValue: string;
|
let newTitle: string;
|
||||||
|
|
||||||
let isVisible = false;
|
let isVisible = false;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(() => {
|
||||||
await views.init(project.id);
|
if (views && $views.length > 0) currentView.set($views[0]);
|
||||||
|
|
||||||
if ($views && $views.length > 0) currentView.set($views[0]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createView() {
|
async function createView() {
|
||||||
if (!$views) return;
|
if (!$views) return;
|
||||||
|
|
||||||
const primaryTagId =
|
const newView = await View.create(project);
|
||||||
$currentView?.primary_tag_id || Object.values($projectTags).find((t) => true)?.id || null;
|
|
||||||
|
|
||||||
const newView = await views.add(project.id, 'New view', primaryTagId);
|
|
||||||
|
|
||||||
if (!newView) return;
|
if (!newView) return;
|
||||||
|
|
||||||
currentView.set(newView);
|
currentView.set(newView);
|
||||||
viewEditId = newView.id;
|
viewEdit = newView;
|
||||||
viewEditValue = newView.title;
|
|
||||||
document.getElementById(`viewTitle-${newView.id}`)?.focus();
|
document.getElementById(`viewTitle-${newView.id}`)?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveView(view: View) {
|
async function saveView() {
|
||||||
if (!view || !$views.includes(view)) return;
|
if (!viewEdit) return;
|
||||||
if (viewEditId === view.id && viewEditValue !== view.title) {
|
if (!newTitle) return;
|
||||||
if (!(await views.edit(view))) return;
|
if (newTitle != viewEdit.title) {
|
||||||
|
await viewEdit.setTitle(newTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
viewEditId = -1;
|
viewEdit = null;
|
||||||
viewEditValue = '';
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -60,7 +52,7 @@
|
||||||
<h2>{project.title}</h2>
|
<h2>{project.title}</h2>
|
||||||
{#if views}
|
{#if views}
|
||||||
<ul>
|
<ul>
|
||||||
{#each get(views) as view}
|
{#each $views as view}
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
|
<!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
|
||||||
<li
|
<li
|
||||||
on:click={() => currentView.set(view)}
|
on:click={() => currentView.set(view)}
|
||||||
|
@ -74,26 +66,29 @@
|
||||||
class:active={$currentView === view}
|
class:active={$currentView === view}
|
||||||
>
|
>
|
||||||
<ViewIcon />
|
<ViewIcon />
|
||||||
<input
|
{#if viewEdit && viewEdit === view}
|
||||||
type="text"
|
<input
|
||||||
readonly={viewEditId !== view.id}
|
type="text"
|
||||||
bind:value={view.title}
|
bind:value={newTitle}
|
||||||
class:inEdit={viewEditId === view.id}
|
on:blur={saveView}
|
||||||
on:blur={() => saveView(view)}
|
id="viewTitle-{view.id}"
|
||||||
id="viewTitle-{view.id}"
|
class="inEdit"
|
||||||
on:keydown={(e) => {
|
on:keydown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.currentTarget.blur();
|
e.currentTarget.blur();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{:else}
|
||||||
|
<span class="title">{view.title}</span>
|
||||||
|
{/if}
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
if (viewEditId === view.id) {
|
if (viewEdit && viewEdit.id === view.id) {
|
||||||
saveView(view);
|
saveView();
|
||||||
} else {
|
} else {
|
||||||
viewEditId = view.id;
|
viewEdit = view;
|
||||||
viewEditValue = view.title;
|
newTitle = view.title;
|
||||||
document.getElementById(`viewTitle-${view.id}`)?.focus();
|
document.getElementById(`viewTitle-${view.id}`)?.focus();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -213,9 +208,16 @@
|
||||||
li {
|
li {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.title,
|
||||||
input {
|
input {
|
||||||
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
@ -239,8 +241,6 @@
|
||||||
border: none;
|
border: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
padding: 5px 0;
|
|
||||||
float: right;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,13 @@ export default class Card {
|
||||||
|
|
||||||
card._tags = CardTag.parseAll(json.tags, card);
|
card._tags = CardTag.parseAll(json.tags, card);
|
||||||
|
|
||||||
cards.update((cards) => [...cards, card]);
|
cards.update((cards) => {
|
||||||
|
if (!cards.find((c) => c.id === card.id)) {
|
||||||
|
return [...cards, card];
|
||||||
|
}
|
||||||
|
|
||||||
|
return cards.map((c) => (c.id === card.id ? card : c));
|
||||||
|
});
|
||||||
|
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,11 @@ export default class Project {
|
||||||
|
|
||||||
const project = new Project(json.id, json.title);
|
const project = new Project(json.id, json.title);
|
||||||
|
|
||||||
projects.update((projects) => [...projects, project]);
|
projects.update((projects) => {
|
||||||
|
if (!projects.find((p) => p.id === project.id)) return [...projects, project];
|
||||||
|
|
||||||
|
return projects.map((p) => (p.id === project.id ? project : p));
|
||||||
|
});
|
||||||
|
|
||||||
return project;
|
return project;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { get, writable } from 'svelte/store';
|
||||||
import TagOption from './TagOption';
|
import TagOption from './TagOption';
|
||||||
import Project from './Project';
|
import Project from './Project';
|
||||||
|
|
||||||
const projectTags = writable([] as ProjectTag[]);
|
export const projectTags = writable([] as ProjectTag[]);
|
||||||
|
|
||||||
export default class ProjectTag {
|
export default class ProjectTag {
|
||||||
private _id: number;
|
private _id: number;
|
||||||
|
@ -47,11 +47,8 @@ export default class ProjectTag {
|
||||||
return this._options;
|
return this._options;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getAll(): ProjectTag[] {
|
static fromId(id: number | null | undefined): ProjectTag | null {
|
||||||
return get(projectTags);
|
if (!id) return null;
|
||||||
}
|
|
||||||
|
|
||||||
static fromId(id: number): ProjectTag | null {
|
|
||||||
for (const projectTag of get(projectTags)) {
|
for (const projectTag of get(projectTags)) {
|
||||||
if (projectTag.id === id) {
|
if (projectTag.id === id) {
|
||||||
return projectTag;
|
return projectTag;
|
||||||
|
@ -76,7 +73,13 @@ export default class ProjectTag {
|
||||||
|
|
||||||
projectTag._options = options;
|
projectTag._options = options;
|
||||||
|
|
||||||
projectTags.update((projectTags) => [...projectTags, projectTag]);
|
projectTags.update((projectTags) => {
|
||||||
|
if (!projectTags.find((projectTag) => projectTag.id === json.id)) {
|
||||||
|
return [...projectTags, projectTag];
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectTags.map((pt) => (pt.id === json.id ? projectTag : pt));
|
||||||
|
});
|
||||||
|
|
||||||
return projectTag;
|
return projectTag;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,9 @@ import { writable } from 'svelte/store';
|
||||||
import Project from './Project';
|
import Project from './Project';
|
||||||
import ProjectTag from './ProjectTag';
|
import ProjectTag from './ProjectTag';
|
||||||
import viewsApi from '$lib/api/viewsApi';
|
import viewsApi from '$lib/api/viewsApi';
|
||||||
|
import { toastAlert } from '$lib/utils/toasts';
|
||||||
|
|
||||||
const views = writable([] as View[]);
|
export const views = writable([] as View[]);
|
||||||
|
|
||||||
export default class View {
|
export default class View {
|
||||||
private _id: number;
|
private _id: number;
|
||||||
|
@ -80,23 +81,43 @@ export default class View {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setTitle(title: string): Promise<boolean> {
|
||||||
|
if (
|
||||||
|
!(await viewsApi.update(
|
||||||
|
this.id,
|
||||||
|
this.project.id,
|
||||||
|
this.primaryTag?.id || null,
|
||||||
|
this.secondaryTag?.id || null,
|
||||||
|
title,
|
||||||
|
this.sortTag?.id || null,
|
||||||
|
this.sortDirection || null
|
||||||
|
))
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this._title = title;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static parse(json: any): View | null;
|
static parse(json: any): View | null;
|
||||||
static parse(json: any, project: Project | null | undefined): View | null;
|
static parse(json: any, project: Project | null | undefined): View | null;
|
||||||
|
|
||||||
static parse(json: any, project?: Project | null | undefined): View | null {
|
static parse(json: any, project?: Project | null | undefined): View | null {
|
||||||
if (!json) return null;
|
if (!json) {
|
||||||
|
toastAlert('Failed to parse view');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!project) project = Project.fromId(json.project_id);
|
if (!project) project = Project.fromId(json.project_id);
|
||||||
if (!project) return null;
|
if (!project) {
|
||||||
|
toastAlert('Failed to find project');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const primaryTag = ProjectTag.fromId(json.primary_tag_id);
|
const primaryTag = ProjectTag.fromId(json.primary_tag_id);
|
||||||
if (!primaryTag) return null;
|
|
||||||
|
|
||||||
const secondaryTag = ProjectTag.fromId(json.secondary_tag_id);
|
const secondaryTag = ProjectTag.fromId(json.secondary_tag_id);
|
||||||
if (!secondaryTag) return null;
|
|
||||||
|
|
||||||
const sortTag = ProjectTag.fromId(json.sort_tag_id);
|
const sortTag = ProjectTag.fromId(json.sort_tag_id);
|
||||||
if (!sortTag) return null;
|
|
||||||
|
|
||||||
const view = new View(
|
const view = new View(
|
||||||
json.id,
|
json.id,
|
||||||
|
@ -108,8 +129,40 @@ export default class View {
|
||||||
json.sort_direction
|
json.sort_direction
|
||||||
);
|
);
|
||||||
|
|
||||||
views.update((views) => [...views, view]);
|
views.update((views) => {
|
||||||
|
if (!views.find((view) => view.id === json.id)) {
|
||||||
|
return [...views, view];
|
||||||
|
}
|
||||||
|
|
||||||
|
return views.map((v) => (v.id === json.id ? view : v));
|
||||||
|
});
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static parseAll(json: any): View[];
|
||||||
|
static parseAll(json: any, project: Project | null | undefined): View[];
|
||||||
|
|
||||||
|
static parseAll(json: any, project?: Project | null | undefined): View[] {
|
||||||
|
if (!json) {
|
||||||
|
toastAlert('Failed to parse views');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!project) project = Project.fromId(json.project_id);
|
||||||
|
if (!project) {
|
||||||
|
toastAlert('Failed to find project');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const views: View[] = [];
|
||||||
|
|
||||||
|
for (const viewJson of json) {
|
||||||
|
const view = View.parse(viewJson, project);
|
||||||
|
|
||||||
|
if (view) views.push(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
return views;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { getProjectAPI } from '$lib/api/projects';
|
import projectsApi from '$lib/api/projectsApi';
|
||||||
import ProjectComponent from '$lib/components/project/Project.svelte';
|
|
||||||
import Sidebar from '$lib/components/project/Sidebar.svelte';
|
import Sidebar from '$lib/components/project/Sidebar.svelte';
|
||||||
import type Project from '$lib/types/Project';
|
import type Project from '$lib/types/Project';
|
||||||
import { SvelteToast } from '@zerodevx/svelte-toast';
|
import { SvelteToast } from '@zerodevx/svelte-toast';
|
||||||
|
@ -11,17 +10,22 @@
|
||||||
|
|
||||||
let project: Project;
|
let project: Project;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
getProjectAPI(projectId).then((p) => {
|
const res = await projectsApi.get(projectId);
|
||||||
project = p;
|
|
||||||
});
|
if (!res) return;
|
||||||
|
|
||||||
|
project = res;
|
||||||
|
|
||||||
|
await projectsApi.getTags(project);
|
||||||
|
await projectsApi.getViews(project);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if project}
|
{#if project}
|
||||||
<div>
|
<div>
|
||||||
<Sidebar {project} />
|
<Sidebar {project} />
|
||||||
<ProjectComponent {project} />
|
<!-- <ProjectComponent {project} /> -->
|
||||||
</div>
|
</div>
|
||||||
<SvelteToast />
|
<SvelteToast />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
Loading…
Reference in New Issue