Sidebar fixed

This commit is contained in:
Brieuc Dubois 2024-01-07 01:47:02 +01:00
parent 720fb35326
commit 5e272a9bcc
8 changed files with 174 additions and 83 deletions

View File

@ -1,6 +1,7 @@
import type Card from '$lib/types/Card';
import Project from '$lib/types/Project';
import ProjectTag from '$lib/types/ProjectTag';
import View from '$lib/types/View';
import api, { processError } from '$lib/utils/api';
import { parseCards } from '$lib/utils/parser';
import status from '$lib/utils/status';
@ -29,7 +30,7 @@ async function create(title: string): Promise<number | null> {
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}`);
if (response.status !== status.OK) {
@ -37,7 +38,7 @@ async function get(projectId: number): Promise<number | null> {
return null;
}
return response.data;
return Project.parse(response.data);
}
async function update(projectId: number, title: string): Promise<boolean> {
@ -83,9 +84,20 @@ async function getTags(project: Project): Promise<ProjectTag[]> {
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 {
@ -95,5 +107,6 @@ export default {
delete: delete_,
getAll,
getCards,
getTags
getTags,
getViews
};

View File

@ -4,7 +4,7 @@ import api, { processError } from '$lib/utils/api';
import status from '$lib/utils/status';
async function create(project: Project): Promise<number | null> {
const response = await api.post('/views', {
const response = await api.post('/v1/views', {
project: project.id
});
@ -16,17 +16,25 @@ async function create(project: Project): Promise<number | null> {
return response.data.id;
}
async function update(view: View): Promise<boolean> {
const response = await api.put(`/views/${view.id}`, {
project: view.project.id,
primary_tag_id: view.primaryTag?.id,
secondary_tag_id: view.secondaryTag?.id,
title: view.title,
sort_tag_id: view.sortTag?.id,
sort_direction: view.sortDirection
async function update(
id: number,
projectId: number,
primaryTagId: number | null,
secondaryTagId: number | null,
title: string,
sortTagId: number | null,
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');
return false;
}
@ -35,7 +43,7 @@ async function update(view: View): 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) {
processError(response, 'Failed to delete view');

View File

@ -1,52 +1,44 @@
<script lang="ts">
import currentView from '$lib/stores/currentView';
import views from '$lib/stores/views';
import type Project from '$lib/types/Project';
import type View from '$lib/types/View';
import Project from '$lib/types/Project';
import View, { views } from '$lib/types/View';
import { onMount } from 'svelte';
import { get } from 'svelte/store';
import projectTags from '../../stores/projectTags';
import EditIcon from '../icons/EditIcon.svelte';
import MenuOpener from '../icons/MenuOpener.svelte';
import ViewIcon from '../icons/ViewIcon.svelte';
export let project: Project;
let viewEditId: number;
let viewEditValue: string;
let viewEdit: View | null;
let newTitle: string;
let isVisible = false;
onMount(async () => {
await views.init(project.id);
if ($views && $views.length > 0) currentView.set($views[0]);
onMount(() => {
if (views && $views.length > 0) currentView.set($views[0]);
});
async function createView() {
if (!$views) return;
const primaryTagId =
$currentView?.primary_tag_id || Object.values($projectTags).find((t) => true)?.id || null;
const newView = await views.add(project.id, 'New view', primaryTagId);
const newView = await View.create(project);
if (!newView) return;
currentView.set(newView);
viewEditId = newView.id;
viewEditValue = newView.title;
viewEdit = newView;
document.getElementById(`viewTitle-${newView.id}`)?.focus();
}
async function saveView(view: View) {
if (!view || !$views.includes(view)) return;
if (viewEditId === view.id && viewEditValue !== view.title) {
if (!(await views.edit(view))) return;
async function saveView() {
if (!viewEdit) return;
if (!newTitle) return;
if (newTitle != viewEdit.title) {
await viewEdit.setTitle(newTitle);
}
viewEditId = -1;
viewEditValue = '';
viewEdit = null;
}
</script>
@ -60,7 +52,7 @@
<h2>{project.title}</h2>
{#if views}
<ul>
{#each get(views) as view}
{#each $views as view}
<!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
<li
on:click={() => currentView.set(view)}
@ -74,26 +66,29 @@
class:active={$currentView === view}
>
<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') {
e.currentTarget.blur();
}
}}
/>
{#if viewEdit && viewEdit === view}
<input
type="text"
bind:value={newTitle}
on:blur={saveView}
id="viewTitle-{view.id}"
class="inEdit"
on:keydown={(e) => {
if (e.key === 'Enter') {
e.currentTarget.blur();
}
}}
/>
{:else}
<span class="title">{view.title}</span>
{/if}
<button
on:click={() => {
if (viewEditId === view.id) {
saveView(view);
if (viewEdit && viewEdit.id === view.id) {
saveView();
} else {
viewEditId = view.id;
viewEditValue = view.title;
viewEdit = view;
newTitle = view.title;
document.getElementById(`viewTitle-${view.id}`)?.focus();
}
}}
@ -213,9 +208,16 @@
li {
cursor: pointer;
padding: 0 10px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 5px;
}
span.title,
input {
display: inline-block;
cursor: pointer;
padding: 10px;
border-radius: 5px;
@ -239,8 +241,6 @@
border: none;
color: inherit;
font-size: 17px;
padding: 5px 0;
float: right;
cursor: pointer;
}

View File

@ -90,7 +90,13 @@ export default class 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;
}

View File

@ -63,7 +63,11 @@ export default class Project {
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;
}

View File

@ -4,7 +4,7 @@ import { get, writable } from 'svelte/store';
import TagOption from './TagOption';
import Project from './Project';
const projectTags = writable([] as ProjectTag[]);
export const projectTags = writable([] as ProjectTag[]);
export default class ProjectTag {
private _id: number;
@ -47,11 +47,8 @@ export default class ProjectTag {
return this._options;
}
static getAll(): ProjectTag[] {
return get(projectTags);
}
static fromId(id: number): ProjectTag | null {
static fromId(id: number | null | undefined): ProjectTag | null {
if (!id) return null;
for (const projectTag of get(projectTags)) {
if (projectTag.id === id) {
return projectTag;
@ -76,7 +73,13 @@ export default class ProjectTag {
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;
}

View File

@ -2,8 +2,9 @@ import { writable } from 'svelte/store';
import Project from './Project';
import ProjectTag from './ProjectTag';
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 {
private _id: number;
@ -80,23 +81,43 @@ export default class View {
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, 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) return null;
if (!project) {
toastAlert('Failed to find project');
return null;
}
const primaryTag = ProjectTag.fromId(json.primary_tag_id);
if (!primaryTag) return null;
const secondaryTag = ProjectTag.fromId(json.secondary_tag_id);
if (!secondaryTag) return null;
const sortTag = ProjectTag.fromId(json.sort_tag_id);
if (!sortTag) return null;
const view = new View(
json.id,
@ -108,8 +129,40 @@ export default class View {
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;
}
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;
}
}

View File

@ -1,7 +1,6 @@
<script lang="ts">
import { page } from '$app/stores';
import { getProjectAPI } from '$lib/api/projects';
import ProjectComponent from '$lib/components/project/Project.svelte';
import projectsApi from '$lib/api/projectsApi';
import Sidebar from '$lib/components/project/Sidebar.svelte';
import type Project from '$lib/types/Project';
import { SvelteToast } from '@zerodevx/svelte-toast';
@ -11,17 +10,22 @@
let project: Project;
onMount(() => {
getProjectAPI(projectId).then((p) => {
project = p;
});
onMount(async () => {
const res = await projectsApi.get(projectId);
if (!res) return;
project = res;
await projectsApi.getTags(project);
await projectsApi.getViews(project);
});
</script>
{#if project}
<div>
<Sidebar {project} />
<ProjectComponent {project} />
<!-- <ProjectComponent {project} /> -->
</div>
<SvelteToast />
{/if}