From 2ffcc62171f14221a3ad1de4789ea75e488adbea Mon Sep 17 00:00:00 2001 From: Churbanov Valery Date: Tue, 6 Jan 2026 08:29:58 +0300 Subject: [PATCH] [FEATURE] Clear events automatically --- .../settings-page-content.vue | 36 +++- .../lib/use-events/auto-delete-events.ts | 116 ++++++++++++ src/shared/lib/use-events/use-events.ts | 1 - src/shared/stores/events/events-store.ts | 2 + .../stores/settings/local-storage-actions.ts | 24 +++ src/shared/stores/settings/settings-store.ts | 16 ++ src/shared/types/local-storage.ts | 1 + src/shared/ui/dropdown-menu/dropdown-menu.vue | 173 ++++++++++++++++++ src/shared/ui/dropdown-menu/index.ts | 3 + src/shared/ui/index.ts | 2 + src/shared/ui/select-control/index.ts | 3 + .../ui/select-control/select-control.vue | 140 ++++++++++++++ 12 files changed, 512 insertions(+), 5 deletions(-) create mode 100644 src/shared/lib/use-events/auto-delete-events.ts create mode 100644 src/shared/ui/dropdown-menu/dropdown-menu.vue create mode 100644 src/shared/ui/dropdown-menu/index.ts create mode 100644 src/shared/ui/select-control/index.ts create mode 100644 src/shared/ui/select-control/select-control.vue diff --git a/src/pages/settings/ui/settings-page-content/settings-page-content.vue b/src/pages/settings/ui/settings-page-content/settings-page-content.vue index b387cdc7..730d8e37 100644 --- a/src/pages/settings/ui/settings-page-content/settings-page-content.vue +++ b/src/pages/settings/ui/settings-page-content/settings-page-content.vue @@ -3,15 +3,28 @@ import { useTitle } from '@vueuse/core' import { storeToRefs } from 'pinia' import { computed } from 'vue' import { THEME_MODES, useSettingsStore } from '@/shared/stores' -import { BadgeNumber, IconSvg } from '@/shared/ui' +import { BadgeNumber, IconSvg, SelectControl } from '@/shared/ui' const settingsStore = useSettingsStore() -const { changeTheme, changeNavbar, changeEventCountsVisibility, changeActiveCodeEditor } = - settingsStore -const { themeType, isFixedHeader, isVisibleEventCounts, codeEditor } = storeToRefs(settingsStore) +const { + changeTheme, + changeNavbar, + changeEventCountsVisibility, + setAutoDeleteEventsTime, + changeActiveCodeEditor +} = settingsStore +const { themeType, isFixedHeader, isVisibleEventCounts, autoDeleteEventsTime, codeEditor } = + storeToRefs(settingsStore) const isDarkMode = computed(() => themeType.value === THEME_MODES.DARK) +const deleteEventsAfter = computed({ + get: () => (autoDeleteEventsTime.value === 'none' ? 'none' : String(autoDeleteEventsTime.value)), + set: (val) => { + setAutoDeleteEventsTime(val) + } +}) + // TODO: add throttle const changeCodeEditor = (event: Event) => { const editor = (event.target as HTMLInputElement).value @@ -114,6 +127,21 @@ useTitle('Settings | Buggregator') +
+ Delete Events After: +
+ + +
Code Editor Open Link:
diff --git a/src/shared/lib/use-events/auto-delete-events.ts b/src/shared/lib/use-events/auto-delete-events.ts new file mode 100644 index 00000000..63b9b96d --- /dev/null +++ b/src/shared/lib/use-events/auto-delete-events.ts @@ -0,0 +1,116 @@ +import { storeToRefs } from "pinia"; +import { watch } from "vue"; +import { useEventsStore, useSettingsStore } from "../../stores"; +import type { EventId } from "../../types"; +import { useEventsApi } from "./use-events-api"; + +type AutoDeleteTime = number | "none"; + +const autoDeleteTimers = new Map>(); +let autoDeleteInitialized = false; + +const toAutoDeleteMs = (val: AutoDeleteTime): number | null => { + if (val === "none") return null; + return val * 60 * 1000; +}; + +const clearAutoDeleteTimer = (eventId: EventId) => { + const timer = autoDeleteTimers.get(eventId); + if (!timer) return; + globalThis.clearTimeout(timer); + autoDeleteTimers.delete(eventId); +}; + +const clearAllAutoDeleteTimers = () => { + autoDeleteTimers.forEach((timer) => globalThis.clearTimeout(timer)); + autoDeleteTimers.clear(); +}; + +export const ensureAutoDeleteWatcher = () => { + if (autoDeleteInitialized) return; + autoDeleteInitialized = true; + + const eventsStore = useEventsStore(); + const settingsStore = useSettingsStore(); + const eventsApi = useEventsApi(); + + const { events, lockedIds } = storeToRefs(eventsStore); + const { autoDeleteEventsTime } = storeToRefs(settingsStore); + + const schedule = (eventId: EventId) => { + const ms = toAutoDeleteMs(autoDeleteEventsTime.value); + if (ms === null || autoDeleteTimers.has(eventId)) return; + + const timer = globalThis.setTimeout(() => { + autoDeleteTimers.delete(eventId); + + const locked = lockedIds.value ?? []; + if (locked.includes(eventId)) return; + + void eventsApi.removeById(eventId); + }, ms); + + autoDeleteTimers.set(eventId, timer); + }; + + const rescheduleAll = () => { + clearAllAutoDeleteTimers(); + + const ms = toAutoDeleteMs(autoDeleteEventsTime.value); + if (ms === null) return; + + events.value.forEach(({ uuid }) => { + schedule(uuid); + }); + }; + + const syncTimers = (currentIds: Set, prevIds: Set) => { + prevIds.forEach((id) => { + if (!currentIds.has(id)) { + clearAutoDeleteTimer(id); + } + }); + + if (toAutoDeleteMs(autoDeleteEventsTime.value) === null) return; + + currentIds.forEach((id) => { + if (!prevIds.has(id)) { + schedule(id); + } + }); + }; + + watch( + autoDeleteEventsTime, + () => { + rescheduleAll(); + }, + { immediate: true }, + ); + + watch( + () => events.value.map(({ uuid }) => uuid), + (current, prev) => { + const currentIds = new Set(current); + const prevIds = new Set(prev ?? []); + + syncTimers(currentIds, prevIds); + }, + ); + + watch( + () => lockedIds.value.slice(), + (current, prev) => { + const currentIds = new Set(current); + const prevIds = new Set(prev ?? []); + + const eventsIds = new Set(events.value.map(({ uuid }) => uuid)); + const unlockedIds = new Set( + [...prevIds].filter((id) => !currentIds.has(id) && eventsIds.has(id)), + ); + + syncTimers(currentIds, prevIds); + syncTimers(unlockedIds, new Set()); + }, + ); +}; diff --git a/src/shared/lib/use-events/use-events.ts b/src/shared/lib/use-events/use-events.ts index 040ccec8..2457ac96 100644 --- a/src/shared/lib/use-events/use-events.ts +++ b/src/shared/lib/use-events/use-events.ts @@ -10,7 +10,6 @@ import { useApiTransport } from "../use-api-transport"; import { normalizeUnknownEvent } from "./normalize-unknown-event"; import { type TUseEventsApi, useEventsApi } from "./use-events-api"; - type TUseEvents = () => { normalizeUnknownEvent: (event: ServerEvent) => NormalizedEvent events: TUseEventsApi diff --git a/src/shared/stores/events/events-store.ts b/src/shared/stores/events/events-store.ts index 82861aed..930d798a 100644 --- a/src/shared/stores/events/events-store.ts +++ b/src/shared/stores/events/events-store.ts @@ -1,5 +1,6 @@ import { defineStore } from "pinia"; import { PAGE_TYPES} from "../../constants"; +import { ensureAutoDeleteWatcher } from "../../lib/use-events/auto-delete-events"; import {useSettings} from "../../lib/use-settings"; import { type EventId, @@ -79,6 +80,7 @@ export const useEventsStore = defineStore("eventsStore", { async initialize (): Promise { const {api: { getProjects }} = useSettings(); this.initActiveProjectKey(); + ensureAutoDeleteWatcher(); try { const { data } = await getProjects(); diff --git a/src/shared/stores/settings/local-storage-actions.ts b/src/shared/stores/settings/local-storage-actions.ts index cac56fd9..4f6d650d 100644 --- a/src/shared/stores/settings/local-storage-actions.ts +++ b/src/shared/stores/settings/local-storage-actions.ts @@ -70,6 +70,30 @@ export const setStoredEventsCountVisibility = (state: boolean) => { window?.localStorage?.setItem(LocalStorageKeys.EventCounts, String(state)); } +export const getStoredAutoDeleteEventsTime = (): number | 'none' => { + const raw = window?.localStorage?.getItem( + LocalStorageKeys.AutoDeleteEventsTime, + ); + if (raw === null) { + return 'none'; + } + const value = Number(raw); + return Number.isFinite(value) && value > 0 ? value : 'none'; +}; + +export const setStoredAutoDeleteEventsTime = (minutes: number | 'none'): void => { + if (minutes === 'none') { + window?.localStorage?.removeItem( + LocalStorageKeys.AutoDeleteEventsTime, + ); + return; + } + + window?.localStorage?.setItem( + LocalStorageKeys.AutoDeleteEventsTime, + String(minutes), + ); +}; export const getStoredPrimaryCodeEditor = (): string => { const storedCodeEditor = window?.localStorage?.getItem(LocalStorageKeys.CodeEditor); diff --git a/src/shared/stores/settings/settings-store.ts b/src/shared/stores/settings/settings-store.ts index 51cc2180..7312f7b3 100644 --- a/src/shared/stores/settings/settings-store.ts +++ b/src/shared/stores/settings/settings-store.ts @@ -7,6 +7,8 @@ import { getStoredFixedHeader, getStoredActiveTheme, setStoredEventsCountVisibility, + getStoredAutoDeleteEventsTime, + setStoredAutoDeleteEventsTime, setStoredFixedHeader, setStoredActiveTheme, getStoredPrimaryCodeEditor, @@ -23,6 +25,7 @@ export const useSettingsStore = defineStore("settingsStore", { themeType: getStoredActiveTheme(), isFixedHeader: getStoredFixedHeader(), isVisibleEventCounts: getStoredEventsCountVisibility(), + autoDeleteEventsTime: getStoredAutoDeleteEventsTime(), availableEvents: [] as EventType[], }), getters: { @@ -72,6 +75,19 @@ export const useSettingsStore = defineStore("settingsStore", { setStoredEventsCountVisibility(this.isVisibleEventCounts) }, + setAutoDeleteEventsTime(value: string | number | 'none') { + const normalized = + value === 'none' + ? 'none' + : Number(value); + + this.autoDeleteEventsTime = + normalized === 'none' || !Number.isFinite(normalized) || normalized <= 0 + ? 'none' + : normalized; + + setStoredAutoDeleteEventsTime(this.autoDeleteEventsTime); + }, changeActiveCodeEditor(editor: string) { this.codeEditor = editor; diff --git a/src/shared/types/local-storage.ts b/src/shared/types/local-storage.ts index f0bc720d..ecb48e05 100644 --- a/src/shared/types/local-storage.ts +++ b/src/shared/types/local-storage.ts @@ -3,6 +3,7 @@ export enum LocalStorageKeys { Theme = "theme", Navbar = "navbar", EventCounts = "event_counts", + AutoDeleteEventsTime = "autodelete_events_time_in_minutes", CodeEditor = "code_editor", Token = "token", } diff --git a/src/shared/ui/dropdown-menu/dropdown-menu.vue b/src/shared/ui/dropdown-menu/dropdown-menu.vue new file mode 100644 index 00000000..ac64f3d2 --- /dev/null +++ b/src/shared/ui/dropdown-menu/dropdown-menu.vue @@ -0,0 +1,173 @@ + + + diff --git a/src/shared/ui/dropdown-menu/index.ts b/src/shared/ui/dropdown-menu/index.ts new file mode 100644 index 00000000..e700b78e --- /dev/null +++ b/src/shared/ui/dropdown-menu/index.ts @@ -0,0 +1,3 @@ +import DropdownMenu from "./dropdown-menu.vue"; + +export { DropdownMenu }; diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts index fa02b8e8..72f0ab41 100644 --- a/src/shared/ui/index.ts +++ b/src/shared/ui/index.ts @@ -9,4 +9,6 @@ export * from './file-attachment'; export * from './pause-button'; export * from './badge-number'; export * from './app-header'; +export * from './dropdown-menu'; +export * from './select-control'; diff --git a/src/shared/ui/select-control/index.ts b/src/shared/ui/select-control/index.ts new file mode 100644 index 00000000..6a703e86 --- /dev/null +++ b/src/shared/ui/select-control/index.ts @@ -0,0 +1,3 @@ +import SelectControl from "./select-control.vue"; + +export { SelectControl }; diff --git a/src/shared/ui/select-control/select-control.vue b/src/shared/ui/select-control/select-control.vue new file mode 100644 index 00000000..b1efd7df --- /dev/null +++ b/src/shared/ui/select-control/select-control.vue @@ -0,0 +1,140 @@ + + +