From 53d832474329fe456b1f6f2c6f405b01984300b0 Mon Sep 17 00:00:00 2001 From: Kuchuk Andrey Date: Sun, 3 Dec 2023 18:55:26 +0400 Subject: [PATCH 1/4] [#83] create locked events store --- plugins/events.client.ts | 12 ++++++++ src/shared/types/local-storage.ts | 1 + stores/events.ts | 19 +++++++++++-- stores/locked-ids.ts | 47 +++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 stores/locked-ids.ts diff --git a/plugins/events.client.ts b/plugins/events.client.ts index dc46571d..b1dde4eb 100644 --- a/plugins/events.client.ts +++ b/plugins/events.client.ts @@ -3,10 +3,12 @@ import { useApiTransport } from '~/src/shared/lib/use-api-transport' import type { EventId, EventType, ServerEvent } from '~/src/shared/types'; import { useCachedIdsStore } from "~/stores/cached-ids"; import { useEventStore } from "~/stores/events"; +import { useLockedIdsStore } from "~/stores/locked-ids"; export default defineNuxtPlugin(() => { const eventsStore = useEventStore(); const cachedIdsStore = useCachedIdsStore(); + const lockedIdsStore = useLockedIdsStore(); const { deleteEvent, @@ -51,6 +53,7 @@ export default defineNuxtPlugin(() => { if (events.length) { eventsStore.addList(events); cachedIdsStore.syncWithActive(events.map(({ uuid }) => uuid)); + lockedIdsStore.syncWithActive(events.map(({ uuid }) => uuid)); } else { // NOTE: clear cached events hardly eventsStore.removeAll(); @@ -69,6 +72,10 @@ export default defineNuxtPlugin(() => { cachedIds, } = storeToRefs(cachedIdsStore) + const { + lockedIds, + } = storeToRefs(lockedIdsStore) + return { provide: { events: { @@ -88,6 +95,11 @@ export default defineNuxtPlugin(() => { rayExecution: { continue: rayContinueExecution, stop: rayStopExecution, + }, + lockedEvents: { + items: lockedIds, + add: lockedIdsStore.addId, + remove: lockedIdsStore.removeId, } } } diff --git a/src/shared/types/local-storage.ts b/src/shared/types/local-storage.ts index 1e6daa3a..2e0e699c 100644 --- a/src/shared/types/local-storage.ts +++ b/src/shared/types/local-storage.ts @@ -1,6 +1,7 @@ export enum LOCAL_STORAGE_KEYS { CACHED_EVENTS = "cached_events", + LOCKED_EVENTS = "locked_events", THEME = "theme", NAVBAR = "navbar", } diff --git a/stores/events.ts b/stores/events.ts index 556e2f44..79771565 100644 --- a/stores/events.ts +++ b/stores/events.ts @@ -1,5 +1,7 @@ import { defineStore } from "pinia"; import type { EventId, EventType, ServerEvent } from '~/src/shared/types'; +import { useLockedIdsStore } from "~/stores/locked-ids"; + export const useEventStore = defineStore("useEventStore", { state: () => ({ @@ -23,14 +25,25 @@ export const useEventStore = defineStore("useEventStore", { }); }, removeAll() { - this.events.length = 0; + const { lockedIds } = useLockedIdsStore(); + + if (!lockedIds.length) { + this.events.length = 0; + } else { + this.events = this.events.filter(({ uuid }) => lockedIds.includes(uuid)); + } }, removeById(eventUuid: EventId) { this.events = this.events.filter(({ uuid }) => uuid !== eventUuid); }, removeByType(eventType: EventType) { - this.events = this.events.filter(({ type }) => type !== eventType); - }, + const { lockedIds } = useLockedIdsStore(); + if (!lockedIds.length) { + this.events = this.events.filter(({ type }) => type !== eventType); + } else { + this.events = this.events.filter(({ type, uuid }) => type !== eventType && !lockedIds.includes(uuid)); + } + }, }, }); diff --git a/stores/locked-ids.ts b/stores/locked-ids.ts new file mode 100644 index 00000000..ffc496f4 --- /dev/null +++ b/stores/locked-ids.ts @@ -0,0 +1,47 @@ +import { defineStore } from "pinia"; +import { LOCAL_STORAGE_KEYS } from '~/src/shared/types'; +import type { EventId } from '~/src/shared/types'; + +const { localStorage } = window; +const getLockedIds = (): EventId[] => { + const storageValue = localStorage?.getItem(LOCAL_STORAGE_KEYS.LOCKED_EVENTS); + + if (storageValue) { + return JSON.parse(storageValue) as EventId[]; + } + + return []; +}; + +const syncLocalStorage = (lockedIds: EventId[]) => { + localStorage?.setItem(LOCAL_STORAGE_KEYS.LOCKED_EVENTS, JSON.stringify(lockedIds)); +} + +export const useLockedIdsStore = defineStore("useLockedIdsStore", { + state: () => ({ + lockedIds: getLockedIds() || [], + }), + actions: { + removeId(eventUuid: EventId) { + this.lockedIds.filter((id) => id !== eventUuid); + + syncLocalStorage(this.lockedIds); + }, + addId(eventUuid: EventId) { + this.lockedIds.push(eventUuid); + + syncLocalStorage(this.lockedIds); + }, + syncWithActive(activeIds: EventId[]) { + if (!activeIds.length) { + this.lockedIds.length = 0; + + return; + } + + this.lockedIds.filter((lockedId: EventId) => !activeIds.includes(lockedId)) + + syncLocalStorage(this.lockedIds); + } + } +}) From 8f1864a499bd271a10e0ea9d82320b8c63b0b6d1 Mon Sep 17 00:00:00 2001 From: Kuchuk Andrey Date: Sun, 3 Dec 2023 18:57:07 +0400 Subject: [PATCH 2/4] [#83] polish actions in preview-card-header --- src/shared/ui/preview-card/preview-card-header.vue | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/shared/ui/preview-card/preview-card-header.vue b/src/shared/ui/preview-card/preview-card-header.vue index 25025c42..d1cea335 100644 --- a/src/shared/ui/preview-card/preview-card-header.vue +++ b/src/shared/ui/preview-card/preview-card-header.vue @@ -30,15 +30,15 @@ const changeView = () => { emit("toggleView", true); }; -const onDeleteButtonClick = () => { +const deleteEvent = () => { emit("delete", true); }; -const onCopyButtonRightClick = () => { +const copyEvent = () => { emit("copy", true); }; -const onCopyButtonClick = () => { +const downloadEvent = () => { emit("download", true); }; @@ -73,8 +73,8 @@ const isVisibleTags = computed(() => props.tags.length > 0);
@@ -98,7 +98,7 @@ const isVisibleTags = computed(() => props.tags.length > 0); From dcb67b1d26e807241a5434d1c942d7e66e853300 Mon Sep 17 00:00:00 2001 From: Kuchuk Andrey Date: Sun, 3 Dec 2023 20:48:06 +0400 Subject: [PATCH 3/4] [#83] add lock action to preview-card --- plugins/events.client.ts | 85 +++++++++++++------ src/shared/lib/io/use-events-requests.ts | 7 ++ .../use-api-transport/use-api-transport.ts | 10 +++ src/shared/types/events.ts | 2 +- .../ui/preview-card/preview-card-header.vue | 29 +++++++ src/shared/ui/preview-card/preview-card.vue | 21 +++++ stores/cached-ids.ts | 7 +- stores/events.ts | 31 +++++-- stores/locked-ids.ts | 6 +- 9 files changed, 159 insertions(+), 39 deletions(-) diff --git a/plugins/events.client.ts b/plugins/events.client.ts index b1dde4eb..504b1405 100644 --- a/plugins/events.client.ts +++ b/plugins/events.client.ts @@ -10,9 +10,18 @@ export default defineNuxtPlugin(() => { const cachedIdsStore = useCachedIdsStore(); const lockedIdsStore = useLockedIdsStore(); + const { + lockedIds, + } = storeToRefs(lockedIdsStore) + + const { + events, + } = storeToRefs(eventsStore) + const { deleteEvent, deleteEventsAll, + deleteEventsList, deleteEventsByType, getEventsAll, getEvent, @@ -21,7 +30,36 @@ export default defineNuxtPlugin(() => { rayStopExecution, } = useApiTransport(); + const removeList = async (uuids: EventId[]) => { + if (uuids.length === 1) { + const res = await deleteEvent(uuids[0]) + + if (res) { + eventsStore.removeById(uuids[0]); + cachedIdsStore.removeById(uuids[0]); + } + + return + } + + const res = await deleteEventsList(uuids) + + if (res) { + eventsStore.removeByIds(uuids); + cachedIdsStore.removeByIds(uuids); + } + } const removeAll = async () => { + if (lockedIds.value.length) { + const removedIds = events.value + .filter(({ uuid }) => !lockedIds.value.includes(uuid)) + .map(({ uuid }) => uuid) + + await removeList(removedIds) + + return + } + const res = await deleteEventsAll() if (res) { @@ -31,29 +69,34 @@ export default defineNuxtPlugin(() => { } const removeById = async (eventId: EventId) => { - const res = await deleteEvent(eventId) + await removeList([eventId]) + } - if (res) { - eventsStore.removeById(eventId); - cachedIdsStore.removeById(eventId); + const removeByType = async (eventType: EventType) => { + if (lockedIds.value.length) { + const removedIds = events.value + .filter(({ type, uuid }) => type !== eventType || lockedIds.value.includes(uuid)) + .map(({ uuid }) => uuid) + + await removeList(removedIds) + + return } - } - const removeByType = async (type: EventType) => { - const res = await deleteEventsByType(type) + const res = await deleteEventsByType(eventType) if (res) { - eventsStore.removeByType(type); - cachedIdsStore.removeByType(type); + eventsStore.removeByType(eventType); + cachedIdsStore.removeByType(eventType); } } const getAll = () => { - getEventsAll().then((events: ServerEvent[]) => { - if (events.length) { - eventsStore.addList(events); - cachedIdsStore.syncWithActive(events.map(({ uuid }) => uuid)); - lockedIdsStore.syncWithActive(events.map(({ uuid }) => uuid)); + getEventsAll().then((allEvents: ServerEvent[]) => { + if (allEvents.length) { + eventsStore.addList(allEvents); + cachedIdsStore.syncWithActive(allEvents.map(({ uuid }) => uuid)); + lockedIdsStore.syncWithActive(allEvents.map(({ uuid }) => uuid)); } else { // NOTE: clear cached events hardly eventsStore.removeAll(); @@ -64,18 +107,10 @@ export default defineNuxtPlugin(() => { }) } - const { - events, - } = storeToRefs(eventsStore) - const { cachedIds, } = storeToRefs(cachedIdsStore) - const { - lockedIds, - } = storeToRefs(lockedIdsStore) - return { provide: { events: { @@ -96,10 +131,10 @@ export default defineNuxtPlugin(() => { continue: rayContinueExecution, stop: rayStopExecution, }, - lockedEvents: { + lockedIds: { items: lockedIds, - add: lockedIdsStore.addId, - remove: lockedIdsStore.removeId, + add: lockedIdsStore.add, + remove: lockedIdsStore.remove, } } } diff --git a/src/shared/lib/io/use-events-requests.ts b/src/shared/lib/io/use-events-requests.ts index fdf0b89c..4a967604 100644 --- a/src/shared/lib/io/use-events-requests.ts +++ b/src/shared/lib/io/use-events-requests.ts @@ -5,6 +5,7 @@ type TUseEventsRequests = () => { getAll: () => Promise[]>, getSingle: (id: EventId) => Promise | null>, deleteAll: () => Promise, + deleteList: (uuids: EventId[]) => Promise, deleteSingle: (id: EventId) => Promise, deleteByType: (type: EventType) => Promise, getEventRestUrl: (param: EventId | undefined) => string @@ -45,6 +46,11 @@ export const useEventsRequests: TUseEventsRequests = () => { console.error('Fetch Error', err) }) + const deleteList = (uuids: EventId[]) => fetch(getEventRestUrl(), { method: 'DELETE', body: JSON.stringify({ uuids }) }) + .catch((err) => { + console.error('Fetch Error', err) + }) + const deleteByType = (type: EventType) => fetch(getEventRestUrl(), { method: 'DELETE', body: JSON.stringify({type}) }) .catch((err) => { console.error('Fetch Error', err) @@ -54,6 +60,7 @@ export const useEventsRequests: TUseEventsRequests = () => { getAll, getSingle, deleteAll, + deleteList, deleteSingle, deleteByType, getEventRestUrl diff --git a/src/shared/lib/use-api-transport/use-api-transport.ts b/src/shared/lib/use-api-transport/use-api-transport.ts index e889fbcb..d995e392 100644 --- a/src/shared/lib/use-api-transport/use-api-transport.ts +++ b/src/shared/lib/use-api-transport/use-api-transport.ts @@ -12,6 +12,7 @@ export const useApiTransport = () => { getAll, getSingle, deleteAll, + deleteList, deleteSingle, deleteByType, getEventRestUrl @@ -73,6 +74,14 @@ export const useApiTransport = () => { return deleteAll(); } + const deleteEventsList = (uuids: EventId[]) => { + if (getWSConnection()) { + return centrifuge.rpc(`delete:api/events`, { uuids }) + } + + return deleteList(uuids); + } + const deleteEventsByType = (type: EventType) => { if (getWSConnection()) { return centrifuge.rpc(`delete:api/events`, {type}) @@ -98,6 +107,7 @@ export const useApiTransport = () => { getEvent: getSingle, deleteEvent, deleteEventsAll, + deleteEventsList, deleteEventsByType, rayStopExecution, rayContinueExecution, diff --git a/src/shared/types/events.ts b/src/shared/types/events.ts index 9565eb34..82a071cb 100644 --- a/src/shared/types/events.ts +++ b/src/shared/types/events.ts @@ -28,7 +28,7 @@ export interface ServerEvent { export interface NormalizedEvent { id: EventId, - type: EventType | string, + type: EventType, labels: string[], origin: object | null, serverName: string, diff --git a/src/shared/ui/preview-card/preview-card-header.vue b/src/shared/ui/preview-card/preview-card-header.vue index d1cea335..64d1c728 100644 --- a/src/shared/ui/preview-card/preview-card-header.vue +++ b/src/shared/ui/preview-card/preview-card-header.vue @@ -9,6 +9,7 @@ type Props = { eventUrl: string; tags: string[]; isOpen: boolean; + isLocked: boolean; isVisibleControls: boolean; }; @@ -17,6 +18,7 @@ type Emits = { toggleView: [value: boolean]; copy: [value: boolean]; download: [value: boolean]; + lock: [value: boolean]; }; const props = withDefaults(defineProps(), { @@ -42,6 +44,10 @@ const downloadEvent = () => { emit("download", true); }; +const lockEvent = () => { + emit("lock", true); +}; + const isVisibleTags = computed(() => props.tags.length > 0); @@ -98,10 +104,21 @@ const isVisibleTags = computed(() => props.tags.length > 0); + +
@@ -177,6 +194,10 @@ $eventTypeColorsMap: ( @apply bg-#{$color}-600 ring-#{$color}-300; } } + + &:disabled { + @apply opacity-50 cursor-not-allowed; + } } .preview-card-header__button--collapse { @@ -191,6 +212,14 @@ $eventTypeColorsMap: ( @apply text-red-700 bg-white dark:bg-red-700 hover:bg-red-700 hover:text-white; } +.preview-card-header__button--lock { + @apply text-gray-700 bg-gray-200 hover:bg-blue-700 hover:text-white; +} + +.preview-card-header__button--unlock { + @apply dark:text-white dark:bg-blue-700; +} + .preview-card-header__button-icon { @apply p-1 dark:fill-white; } diff --git a/src/shared/ui/preview-card/preview-card.vue b/src/shared/ui/preview-card/preview-card.vue index 81dec0b2..c468397d 100644 --- a/src/shared/ui/preview-card/preview-card.vue +++ b/src/shared/ui/preview-card/preview-card.vue @@ -17,7 +17,12 @@ type Props = { const props = defineProps(); +const { + $lockedIds: { items }, +} = useNuxtApp(); + const isCollapsed = ref(false); +const isLocked = ref((items || []).value.includes(props.event.id)); const isOptimized = ref(false); const isVisibleControls = ref(true); @@ -54,6 +59,20 @@ const deleteEvent = () => { return $events?.removeById(props.event.id); }; +const toggleEventLock = () => { + const { $lockedIds } = useNuxtApp(); + + if (($lockedIds?.items.value || []).includes(props.event.id)) { + $lockedIds?.remove(props.event.id); + + isLocked.value = false; + } else { + $lockedIds?.add(props.event.id); + + isLocked.value = true; + } +}; + const downloadImage = () => { changeVisibleControls(false); @@ -129,11 +148,13 @@ onBeforeUnmount(() => { :event-id="event.id" :tags="normalizedTags" :is-open="!isCollapsed && !isOptimized" + :is-locked="isLocked" :is-visible-controls="isVisibleControls && !isOptimized" @toggle-view="toggleView" @delete="deleteEvent" @copy="copyCode" @download="downloadImage" + @lock="toggleEventLock" />
{ this.cachedIds[type] = this.cachedIds[type].filter( - (uuid: EventId) => uuid !== eventUuid + (uuid: EventId) => uuids.includes(uuid) ); }); syncLocalStorage(this.cachedIds); }, + removeById(eventUuid: EventId) { + this.removeByIds([eventUuid]); + }, removeAll() { this.cachedIds = initialCachedIds; syncLocalStorage(this.cachedIds); diff --git a/stores/events.ts b/stores/events.ts index 79771565..1750a2f1 100644 --- a/stores/events.ts +++ b/stores/events.ts @@ -27,23 +27,38 @@ export const useEventStore = defineStore("useEventStore", { removeAll() { const { lockedIds } = useLockedIdsStore(); - if (!lockedIds.length) { - this.events.length = 0; - } else { + if (lockedIds.length) { this.events = this.events.filter(({ uuid }) => lockedIds.includes(uuid)); + + return + } + + this.events.length = 0; + }, + removeByIds(eventUuids: EventId[]) { + const { lockedIds } = useLockedIdsStore(); + + if (lockedIds.length) { + this.events = this.events.filter(({ uuid }) => !eventUuids.includes(uuid) || lockedIds.includes(uuid)); + + return } + + this.events = this.events.filter(({ uuid }) => !eventUuids.includes(uuid)); }, removeById(eventUuid: EventId) { - this.events = this.events.filter(({ uuid }) => uuid !== eventUuid); + this.removeByIds([eventUuid]); }, removeByType(eventType: EventType) { const { lockedIds } = useLockedIdsStore(); - if (!lockedIds.length) { - this.events = this.events.filter(({ type }) => type !== eventType); - } else { - this.events = this.events.filter(({ type, uuid }) => type !== eventType && !lockedIds.includes(uuid)); + if (lockedIds.length) { + this.events = this.events.filter(({ type, uuid }) => type !== eventType || lockedIds.includes(uuid)); + + return } + + this.events = this.events.filter(({ type }) => type !== eventType); }, }, }); diff --git a/stores/locked-ids.ts b/stores/locked-ids.ts index ffc496f4..aa1a6527 100644 --- a/stores/locked-ids.ts +++ b/stores/locked-ids.ts @@ -22,12 +22,12 @@ export const useLockedIdsStore = defineStore("useLockedIdsStore", { lockedIds: getLockedIds() || [], }), actions: { - removeId(eventUuid: EventId) { - this.lockedIds.filter((id) => id !== eventUuid); + remove(eventUuid: EventId) { + this.lockedIds = this.lockedIds.filter((id) => id !== eventUuid); syncLocalStorage(this.lockedIds); }, - addId(eventUuid: EventId) { + add(eventUuid: EventId) { this.lockedIds.push(eventUuid); syncLocalStorage(this.lockedIds); From 49505d57c7fd9b1b1a626acac7882792282c65eb Mon Sep 17 00:00:00 2001 From: Kuchuk Andrey Date: Sun, 3 Dec 2023 21:05:55 +0400 Subject: [PATCH 4/4] [#83] add styles to card lock icon --- src/shared/ui/preview-card/preview-card-header.vue | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/shared/ui/preview-card/preview-card-header.vue b/src/shared/ui/preview-card/preview-card-header.vue index 64d1c728..599d0c01 100644 --- a/src/shared/ui/preview-card/preview-card-header.vue +++ b/src/shared/ui/preview-card/preview-card-header.vue @@ -113,7 +113,7 @@ const isVisibleTags = computed(() => props.tags.length > 0);