From 77a0c33c18abc67c4de4e09246cf9f42fce64b58 Mon Sep 17 00:00:00 2001 From: Brieuc Dubois Date: Tue, 23 Jan 2024 15:31:06 +0100 Subject: [PATCH] Real-time sync for filters, projectTagOption, projectTag --- frontend/src/lib/types/Filter.ts | 16 +++++ frontend/src/lib/types/ProjectTag.ts | 39 +++++++++++- frontend/src/lib/types/TagOption.ts | 4 ++ frontend/src/lib/types/View.ts | 28 +++++++-- frontend/src/lib/utils/webSocketManager.ts | 70 ++++++++++++++++++++-- 5 files changed, 146 insertions(+), 11 deletions(-) diff --git a/frontend/src/lib/types/Filter.ts b/frontend/src/lib/types/Filter.ts index 27fafaf..01a5fd6 100644 --- a/frontend/src/lib/types/Filter.ts +++ b/frontend/src/lib/types/Filter.ts @@ -150,4 +150,20 @@ export default class Filter { return new Filter(json.id, view, projectTag, json.filter_type, tagOption || null); } + + parseUpdate(changes: any) { + if (changes.tag_id) { + const projectTag = ProjectTag.fromId(changes.tag_id); + if (projectTag) this._projectTag = projectTag; + } + + if (changes.filter_type) { + this._filterType = changes.filter_type; + } + + if (changes.option_id) { + this._tagOption = + this.projectTag.options.find((option) => option.id === changes.option_id) || null; + } + } } diff --git a/frontend/src/lib/types/ProjectTag.ts b/frontend/src/lib/types/ProjectTag.ts index bf39e1e..729ee4a 100644 --- a/frontend/src/lib/types/ProjectTag.ts +++ b/frontend/src/lib/types/ProjectTag.ts @@ -1,9 +1,9 @@ // import type TagOption from './TagOption'; -import { get, writable } from 'svelte/store'; -import TagOption from './TagOption'; -import Project from './Project'; import projectTagsApi from '$lib/api/projectTagsApi'; +import { get, writable } from 'svelte/store'; +import Project from './Project'; +import TagOption from './TagOption'; const { subscribe, update, set } = writable([] as ProjectTag[]); @@ -149,4 +149,37 @@ export default class ProjectTag { return projectTags; } + + static parseDelete(id: number) { + projectTags.update((projectTags) => projectTags.filter((projectTag) => projectTag.id !== id)); + } + + parseUpdate(json: any) { + if (!json) return; + + if (json.title) this._title = json.title; + if (json.type) this._type = json.type; + } + + parseOption(json: any) { + if (!json) return; + + const option = TagOption.parse(json, this); + if (!option) return; + + this._options = [...this._options, option]; + } + + parseOptionDelete(id: number) { + this._options = this._options.filter((option) => option.id !== id); + } + + parseOptionUpdate(json: any) { + if (!json) return; + + const option = this._options.find((option) => option.id === json.id); + if (!option) return; + + option.parseUpdate(json); + } } diff --git a/frontend/src/lib/types/TagOption.ts b/frontend/src/lib/types/TagOption.ts index 0a0f3ff..b97ff86 100644 --- a/frontend/src/lib/types/TagOption.ts +++ b/frontend/src/lib/types/TagOption.ts @@ -73,4 +73,8 @@ export default class TagOption { return options; } + + parseUpdate(dict: any) { + if (dict.value) this._value = dict.value; + } } diff --git a/frontend/src/lib/types/View.ts b/frontend/src/lib/types/View.ts index 1ef5e5c..5f93bd6 100644 --- a/frontend/src/lib/types/View.ts +++ b/frontend/src/lib/types/View.ts @@ -1,11 +1,11 @@ +import viewsApi from '$lib/api/viewsApi'; +import currentView from '$lib/stores/currentView'; +import { toastAlert } from '$lib/utils/toasts'; import { get, writable } from 'svelte/store'; +import Filter from './Filter'; import Project from './Project'; import ProjectTag from './ProjectTag'; -import viewsApi from '$lib/api/viewsApi'; -import { toastAlert } from '$lib/utils/toasts'; -import Filter from './Filter'; import type TagOption from './TagOption'; -import currentView from '$lib/stores/currentView'; const { subscribe, set, update } = writable([] as View[]); @@ -284,4 +284,24 @@ export default class View { return views; } + + parseFilter(json: any) { + const filter = Filter.parse(json, this); + + if (!filter) return; + + this._filters = [...this._filters, filter]; + } + + parseFilterUpdate(json: any) { + const filter = this._filters.find((f) => f.id === json.id); + + if (!filter) return; + + filter.parseUpdate(json); + } + + parseFilterDelete(id: number) { + this._filters = this._filters.filter((f) => f.id !== id); + } } diff --git a/frontend/src/lib/utils/webSocketManager.ts b/frontend/src/lib/utils/webSocketManager.ts index fc9a118..4ae721c 100644 --- a/frontend/src/lib/utils/webSocketManager.ts +++ b/frontend/src/lib/utils/webSocketManager.ts @@ -1,11 +1,9 @@ import Card, { cards } from '$lib/types/Card'; -import CardTag from '$lib/types/CardTag'; import Project, { projects } from '$lib/types/Project'; import ProjectTag, { projectTags } from '$lib/types/ProjectTag'; import View, { views } from '$lib/types/View'; import { getBackendWsUrl, hasPendingRequests, randomID } from '$lib/utils/api'; import { toastAlert, toastWarning } from '$lib/utils/toasts'; -import { get } from 'svelte/store'; export default class WebSocketManager { _socket: WebSocket | null; @@ -95,6 +93,7 @@ function applyMessage(data: any) { else if (data.object === 'card') applyCard(data); else if (data.object === 'view') applyView(data); else if (data.object === 'projectTag') applyProjectTag(data); + else if (data.object === 'tagOption') applyProjectTagOption(data); else if (data.object === 'filter') applyFilter(data); else toastWarning('Failed to parse WebSocket message: unknown object'); } @@ -195,6 +194,69 @@ function applyView(data: any) { views.reload(view); } -function applyProjectTag(data: any) {} +function applyProjectTag(data: any) { + if (data.action === 'create') { + ProjectTag.parse(data.data); + return; + } + if (data.action === 'delete') { + ProjectTag.parseDelete(data.id); + return; + } -function applyFilter(data: any) {} + if (data.action !== 'update') { + toastWarning('Failed to parse project tag update: unknown action'); + return; + } + + const projectTag = ProjectTag.fromId(data.id); + if (!projectTag) { + toastWarning('Failed to parse project tag update: project tag not found'); + return; + } + + projectTag.parseUpdate(data.changes); + projectTags.reload(); +} + +function applyProjectTagOption(data: any) { + const projectTag = ProjectTag.fromId(data.project_tag_id); + if (!projectTag) { + toastWarning('Failed to parse project tag option update: project tag not found'); + return; + } + + if (data.action === 'create') { + projectTag.parseOption(data.data); + } else if (data.action === 'update') { + projectTag.parseOptionUpdate(data.changes); + } else if (data.action === 'delete') { + projectTag.parseOptionDelete(data.id); + } else { + toastWarning('Failed to parse project tag option update: unknown action'); + return; + } + + projectTags.reload(); +} + +function applyFilter(data: any) { + const view = View.fromId(data.view_id); + if (!view) { + toastWarning('Failed to parse filter update: view not found'); + return; + } + + if (data.action === 'create') { + view.parseFilter(data.data); + } else if (data.action === 'update') { + view.parseFilterUpdate(data.changes); + } else if (data.action === 'delete') { + view.parseFilterDelete(data.id); + } else { + toastWarning('Failed to parse filter update: unknown action'); + return; + } + + views.reload(view); +}