Sidebar fixed
This commit is contained in:
parent
720fb35326
commit
5e272a9bcc
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
Loading…
Reference in New Issue