Realtime updates for projects & cards
This commit is contained in:
parent
e01b93f48b
commit
c271af9d93
|
@ -34,9 +34,27 @@ func CreateCard(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
||||
if err := c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
||||
"id": id,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
card.ID = id;
|
||||
|
||||
source := c.Get("X-Request-Source");
|
||||
if source == "" {
|
||||
return nil;
|
||||
}
|
||||
|
||||
publish(fiber.Map{
|
||||
"object": "card",
|
||||
"action": "create",
|
||||
"data": card,
|
||||
"X-Request-Source": source,
|
||||
})
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
func GetCard(c *fiber.Ctx) error {
|
||||
|
@ -77,7 +95,23 @@ func DeleteCard(c *fiber.Ctx) error {
|
|||
return c.SendStatus(fiber.StatusNotFound)
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
if err := c.SendStatus(fiber.StatusNoContent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source := c.Get("X-Request-Source");
|
||||
if source == "" {
|
||||
return nil;
|
||||
}
|
||||
|
||||
publish(fiber.Map{
|
||||
"object": "card",
|
||||
"action": "delete",
|
||||
"id": id,
|
||||
"X-Request-Source": source,
|
||||
});
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
func UpdateCard(c *fiber.Ctx) error {
|
||||
|
@ -103,5 +137,22 @@ func UpdateCard(c *fiber.Ctx) error {
|
|||
return c.SendStatus(fiber.StatusNotFound)
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
if err := c.SendStatus(fiber.StatusNoContent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source := c.Get("X-Request-Source");
|
||||
if source == "" {
|
||||
return nil;
|
||||
}
|
||||
|
||||
publish(fiber.Map{
|
||||
"object": "card",
|
||||
"action": "update",
|
||||
"id": id,
|
||||
"changes": card,
|
||||
"X-Request-Source": source,
|
||||
})
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
|
|
@ -91,11 +91,16 @@ func CreateProject(c *fiber.Ctx) error {
|
|||
|
||||
p.ID = id;
|
||||
|
||||
source := c.Get("X-Request-Source");
|
||||
if source == "" {
|
||||
return nil;
|
||||
}
|
||||
|
||||
publish(fiber.Map{
|
||||
"object": "project",
|
||||
"action": "create",
|
||||
"id": id,
|
||||
"value": p,
|
||||
"data": p,
|
||||
"X-Request-Source": source,
|
||||
})
|
||||
|
||||
return nil;
|
||||
|
@ -130,11 +135,17 @@ func UpdateProject(c *fiber.Ctx) error {
|
|||
return err;
|
||||
}
|
||||
|
||||
source := c.Get("X-Request-Source");
|
||||
if source == "" {
|
||||
return nil;
|
||||
}
|
||||
|
||||
publish(fiber.Map{
|
||||
"object": "project",
|
||||
"action": "update",
|
||||
"id": id,
|
||||
"value": p,
|
||||
"changes": p,
|
||||
"X-Request-Source": source,
|
||||
})
|
||||
|
||||
return nil;
|
||||
|
@ -164,10 +175,16 @@ func DeleteProject(c *fiber.Ctx) error {
|
|||
return err;
|
||||
}
|
||||
|
||||
source := c.Get("X-Request-Source");
|
||||
if source == "" {
|
||||
return nil;
|
||||
}
|
||||
|
||||
publish(fiber.Map{
|
||||
"object": "project",
|
||||
"action": "delete",
|
||||
"id": id,
|
||||
"X-Request-Source": source,
|
||||
})
|
||||
|
||||
return nil;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@tauri-apps/cli": "^1.5.9",
|
||||
"@types/eslint": "8.56.0",
|
||||
"@types/node": "^20.11.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@zerodevx/svelte-toast": "^0.9.5",
|
||||
|
@ -1125,6 +1126,15 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz",
|
||||
"integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz",
|
||||
"integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/pug": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
|
||||
|
@ -3807,6 +3817,12 @@
|
|||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@tauri-apps/cli": "^1.5.9",
|
||||
"@types/eslint": "8.56.0",
|
||||
"@types/node": "^20.11.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@zerodevx/svelte-toast": "^0.9.5",
|
||||
|
|
|
@ -3,7 +3,6 @@ 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';
|
||||
|
||||
async function getAll(): Promise<Project[]> {
|
||||
|
|
|
@ -152,6 +152,14 @@ export default class Card {
|
|||
return true;
|
||||
}
|
||||
|
||||
updateFromDict(dict: any) {
|
||||
if (dict.project_id && dict.project_id !== this._project.id) {
|
||||
this._project = Project.fromId(dict.project_id) as Project;
|
||||
}
|
||||
if (dict.title) this._title = dict.title;
|
||||
if (dict.content) this._content = dict.content;
|
||||
}
|
||||
|
||||
static parse(json: any): Card | null;
|
||||
static parse(json: any, project: Project | null | undefined): Card | null;
|
||||
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import projectsApi from '$lib/api/projectsApi';
|
||||
import { get, writable } from 'svelte/store';
|
||||
|
||||
export const projects = writable([] as Project[]);
|
||||
const { subscribe, set, update } = writable([] as Project[]);
|
||||
|
||||
export const projects = {
|
||||
subscribe,
|
||||
set,
|
||||
update,
|
||||
reload: () => update((projects) => projects)
|
||||
};
|
||||
|
||||
export default class Project {
|
||||
private _id: number;
|
||||
|
@ -35,7 +42,7 @@ export default class Project {
|
|||
|
||||
if (!id) return null;
|
||||
|
||||
const project = new Project(id, 'untitled');
|
||||
const project = new Project(id, 'New project');
|
||||
|
||||
projects.update((projects) => [...projects, project]);
|
||||
|
||||
|
@ -58,6 +65,10 @@ export default class Project {
|
|||
return true;
|
||||
}
|
||||
|
||||
updateFromDict(dict: any) {
|
||||
if (dict.title) this._title = dict.title;
|
||||
}
|
||||
|
||||
static parse(json: any): Project | null {
|
||||
if (!json) return null;
|
||||
|
||||
|
|
|
@ -7,6 +7,9 @@ import { toastAlert } from './toasts';
|
|||
let backendUrl = 'http://localhost:3000';
|
||||
let backendWsUrl = 'ws://localhost:3000';
|
||||
|
||||
export let randomID =
|
||||
Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
|
||||
export function getBackendWsUrl() {
|
||||
return backendWsUrl;
|
||||
}
|
||||
|
@ -48,9 +51,10 @@ const cachedInstance = setupCache(axiosInstance, {
|
|||
modifiedSince: true
|
||||
});
|
||||
|
||||
axiosInstance.interceptors.request.use(
|
||||
cachedInstance.interceptors.request.use(
|
||||
(config) => {
|
||||
pendingRequests++;
|
||||
config.headers['X-Request-Source'] = randomID;
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
|
@ -59,7 +63,7 @@ axiosInstance.interceptors.request.use(
|
|||
}
|
||||
);
|
||||
|
||||
axiosInstance.interceptors.response.use(
|
||||
cachedInstance.interceptors.response.use(
|
||||
(response) => {
|
||||
pendingRequests--;
|
||||
return response;
|
||||
|
@ -70,7 +74,7 @@ axiosInstance.interceptors.response.use(
|
|||
}
|
||||
);
|
||||
|
||||
export default axiosInstance;
|
||||
export default cachedInstance;
|
||||
|
||||
export function processError(response: AxiosResponse<any, any>, message: string = '') {
|
||||
let title = `${response.status} ${response.statusText}`;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import Card, { cards } from '$lib/types/Card';
|
||||
import Project, { projects } from '$lib/types/Project';
|
||||
import { getBackendWsUrl, hasPendingRequests } from '$lib/utils/api';
|
||||
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';
|
||||
|
||||
|
@ -61,6 +64,11 @@ export default class WebSocketManager {
|
|||
|
||||
this._socket.onmessage = async (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
const source = data['X-Request-Source'];
|
||||
// console.log(source);
|
||||
if (!source || source === randomID) {
|
||||
return;
|
||||
}
|
||||
while (hasPendingRequests()) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
@ -74,6 +82,15 @@ function applyMessage(data: any) {
|
|||
case 'project':
|
||||
applyProject(data);
|
||||
break;
|
||||
case 'card':
|
||||
applyCard(data);
|
||||
break;
|
||||
case 'view':
|
||||
applyView(data);
|
||||
break;
|
||||
case 'projectTag':
|
||||
applyProjectTag(data);
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown object:', data);
|
||||
}
|
||||
|
@ -82,12 +99,56 @@ function applyMessage(data: any) {
|
|||
function applyProject(data: any) {
|
||||
switch (data.action) {
|
||||
case 'create':
|
||||
if (get(projects).find((p) => p.id === data.id)) break;
|
||||
Project.parse(data.data);
|
||||
case 'update':
|
||||
Project.parse(data.value);
|
||||
get(projects)
|
||||
.find((p) => p.id === data.id)
|
||||
?.updateFromDict(data.changes);
|
||||
projects.reload();
|
||||
break;
|
||||
case 'delete':
|
||||
projects.set(get(projects).filter((p) => p.id !== data.id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function applyCard(data: any) {
|
||||
switch (data.action) {
|
||||
case 'create':
|
||||
Card.parse(data.data);
|
||||
break;
|
||||
case 'update':
|
||||
get(cards)
|
||||
.find((c) => c.id === data.id)
|
||||
?.updateFromDict(data.changes);
|
||||
cards.reload();
|
||||
break;
|
||||
case 'delete':
|
||||
cards.set(get(cards).filter((c) => c.id !== data.id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function applyView(data: any) {
|
||||
switch (data.action) {
|
||||
case 'create':
|
||||
case 'update':
|
||||
View.parse(data.value);
|
||||
break;
|
||||
case 'delete':
|
||||
views.set(get(views).filter((v) => v.id !== data.id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function applyProjectTag(data: any) {
|
||||
switch (data.action) {
|
||||
case 'create':
|
||||
case 'update':
|
||||
ProjectTag.parse(data.value);
|
||||
break;
|
||||
case 'delete':
|
||||
projectTags.set(get(projectTags).filter((t) => t.id !== data.id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
import type Project from '$lib/types/Project';
|
||||
import { views } from '$lib/types/View';
|
||||
import { checkTauriUrl } from '$lib/utils/api';
|
||||
import WebSocketManager from '$lib/utils/webSocketManager';
|
||||
import { SvelteToast } from '@zerodevx/svelte-toast';
|
||||
import { onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
let project: Project;
|
||||
const wsManager = new WebSocketManager();
|
||||
|
||||
onMount(async () => {
|
||||
await checkTauriUrl(window);
|
||||
|
@ -29,6 +31,7 @@
|
|||
if (get(views).length > 0) {
|
||||
currentView.set(get(views)[0]);
|
||||
}
|
||||
wsManager.connect();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
Loading…
Reference in New Issue