diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/auth/index.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/auth/index.svelte.ts index a3c2b51de..01869a36b 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/auth/index.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/auth/index.svelte.ts @@ -1,13 +1,30 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { env } from '$env/dynamic/public'; -import { AuthJSONSerializer, persisted } from '$shared/persisted.svelte'; import { useFetchClient } from '@exceptionless/fetchclient'; +import { PersistedState } from 'runed'; import { get } from 'svelte/store'; import type { Login, TokenResult } from './models'; -export const accessToken = persisted('satellizer_token', null, new AuthJSONSerializer()); +const authSerializer = { + deserialize: (value: null | string): null | string => { + if (value === '') { + return null; + } + + return value; + }, + serialize: (value: null | string): string => { + if (value === null) { + return ''; + } + + return value; + } +}; + +export const accessToken = new PersistedState('satellizer_token', null, { serializer: authSerializer }); export const enableAccountCreation = env.PUBLIC_ENABLE_ACCOUNT_CREATION === 'true'; export const facebookClientId = env.PUBLIC_FACEBOOK_APPID; @@ -98,7 +115,7 @@ export async function login(email: string, password: string) { }); if (response.ok && response.data?.token) { - accessToken.value = response.data.token; + accessToken.current = response.data.token; } else if (response.status === 401) { response.problem.setErrorMessage('Invalid email or password'); } @@ -109,7 +126,7 @@ export async function login(email: string, password: string) { export async function logout() { const client = useFetchClient(); await client.get('auth/logout', { expectedStatusCodes: [200, 401] }); - accessToken.value = null; + accessToken.current = null; } async function oauthLogin(options: { @@ -158,7 +175,7 @@ async function oauthLogin(options: { }); if (response.ok && response.data?.token) { - accessToken.value = response.data.token; + accessToken.current = response.data.token; await goto(options.redirectUrl || '/'); } } diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/api.svelte.ts index 8cddbdea0..62664b15a 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/api.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/api.svelte.ts @@ -124,7 +124,7 @@ export interface GetStackEventsRequest { export function deleteEvent(request: DeleteEventsRequest) { const queryClient = useQueryClient(); return createMutation(() => ({ - enabled: () => !!accessToken.value && !!request.route.ids?.length, + enabled: () => !!accessToken.current && !!request.route.ids?.length, mutationFn: async () => { const client = useFetchClient(); const response = await client.deleteJSON(`events/${request.route.ids?.join(',')}`); @@ -145,7 +145,7 @@ export function getCountQuery(request: GetCountRequest) { const queryClient = useQueryClient(); return createQuery(() => ({ - enabled: () => !!accessToken.value, + enabled: () => !!accessToken.current, queryClient, queryFn: async ({ signal }: { signal: AbortSignal }) => { const client = useFetchClient(); @@ -165,7 +165,7 @@ export function getCountQuery(request: GetCountRequest) { export function getEventQuery(request: GetEventRequest) { return createQuery(() => ({ - enabled: () => !!accessToken.value && !!request.route.id, + enabled: () => !!accessToken.current && !!request.route.id, queryFn: async ({ signal }: { signal: AbortSignal }) => { const client = useFetchClient(); const response = await client.getJSON(`events/${request.route.id}`, { @@ -186,7 +186,7 @@ export function getProjectCountQuery(request: GetProjectCountRequest) { const queryClient = useQueryClient(); return createQuery(() => ({ - enabled: () => !!accessToken.value && !!request.route.projectId, + enabled: () => !!accessToken.current && !!request.route.projectId, queryClient, queryFn: async ({ signal }: { signal: AbortSignal }) => { const client = useFetchClient(); @@ -208,7 +208,7 @@ export function getStackCountQuery(request: GetStackCountRequest) { const queryClient = useQueryClient(); return createQuery(() => ({ - enabled: () => !!accessToken.value && !!request.route.stackId, + enabled: () => !!accessToken.current && !!request.route.stackId, queryClient, queryFn: async ({ signal }: { signal: AbortSignal }) => { const client = useFetchClient(); @@ -233,7 +233,7 @@ export function getStackEventsQuery(request: GetStackEventsRequest) { const queryClient = useQueryClient(); return createQuery(() => ({ - enabled: () => !!accessToken.value && !!request.route.stackId, + enabled: () => !!accessToken.current && !!request.route.stackId, onSuccess: (data: PersistentEvent[]) => { data.forEach((event) => { queryClient.setQueryData(queryKeys.id(event.id!), event); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts index 8e13c8af2..ff5f20347 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts @@ -5,7 +5,6 @@ import TimeAgo from '$comp/formatters/TimeAgo.svelte'; import { Checkbox } from '$comp/ui/checkbox'; import { nameof } from '$lib/utils'; import { DEFAULT_LIMIT } from '$shared/api/api.svelte'; -import { persisted } from '$shared/persisted.svelte'; import { type ColumnDef, type ColumnSort, @@ -17,6 +16,7 @@ import { type Updater, type VisibilityState } from '@tanstack/svelte-table'; +import { PersistedState } from 'runed'; import type { GetEventsMode, GetEventsParams } from '../../api.svelte'; import type { EventSummaryModel, StackSummaryModel, SummaryModel, SummaryTemplateKeys } from '../summary/index'; @@ -270,15 +270,15 @@ export function getTableContext(key: string, initialValue: T): [() => T, (updater: Updater) => void] { - const persistedValue = persisted(key, initialValue); + const persistedValue = new PersistedState(key, initialValue); return [ - () => persistedValue.value, + () => persistedValue.current, (updater: Updater) => { if (updater instanceof Function) { - persistedValue.value = updater(persistedValue.value); + persistedValue.current = updater(persistedValue.current); } else { - persistedValue.value = updater; + persistedValue.current = updater; } } ]; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts index 892482336..7e499d185 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts @@ -35,7 +35,7 @@ export function getOrganizationQuery(request: GetOrganizationsRequest) { const queryClient = useQueryClient(); return createQuery(() => ({ - enabled: () => !!accessToken.value, + enabled: () => !!accessToken.current, onSuccess: (data: ViewOrganization[]) => { data.forEach((organization) => { queryClient.setQueryData(queryKeys.id(organization.id!), organization); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/api.svelte.ts index a6216cea6..a51713073 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/api.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/api.svelte.ts @@ -92,7 +92,7 @@ export function getOrganizationProjectsQuery(request: GetOrganizationProjectsReq const queryClient = useQueryClient(); return createQuery(() => ({ - enabled: () => !!accessToken.value && !!request.route.organizationId, + enabled: () => !!accessToken.current && !!request.route.organizationId, onSuccess: (data: ViewProject[]) => { data.forEach((project) => { queryClient.setQueryData(queryKeys.id(project.id!), project); @@ -117,7 +117,7 @@ export function getOrganizationProjectsQuery(request: GetOrganizationProjectsReq export function getProjectQuery(request: GetProjectRequest) { return createQuery(() => ({ - enabled: () => !!accessToken.value && !!request.route.id, + enabled: () => !!accessToken.current && !!request.route.id, queryFn: async ({ signal }: { signal: AbortSignal }) => { const client = useFetchClient(); const response = await client.getJSON(`projects/${request.route.id}`, { diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/filters.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/filters.svelte.ts index 43f205ab7..83193f887 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/filters.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/filters.svelte.ts @@ -1,6 +1,5 @@ import type { PersistentEventKnownTypes } from '$features/events/models'; import type { StackStatus } from '$features/stacks/models'; -import type { Serializer } from '$shared/persisted.svelte'; export interface IFilter { isEmpty(): boolean; @@ -99,29 +98,6 @@ export class DateFilter implements IFilter { } } -export class FilterSerializer implements Serializer { - public deserialize(text: string): IFilter[] { - if (!text) { - return []; - } - - const data: unknown[] = JSON.parse(text); - const filters: IFilter[] = []; - for (const filterData of data) { - const filter = getFilter(filterData as Omit); - if (filter) { - filters.push(filter); - } - } - - return filters; - } - - public serialize(object: IFilter[]): string { - return JSON.stringify(object); - } -} - export class KeywordFilter implements IFilter { public type: string = 'keyword'; @@ -532,6 +508,26 @@ export function filterChanged(filters: IFilter[], updated: IFilter): IFilter[] { return processFilterRules(setFilter(filters, updated), updated); } +export const filterSerializer = { + deserialize: (value: string): IFilter[] => { + if (!value) { + return []; + } + + const data: unknown[] = JSON.parse(value); + const filters: IFilter[] = []; + for (const filterData of data) { + const filter = getFilter(filterData as Omit); + if (filter) { + filters.push(filter); + } + } + + return filters; + }, + serialize: JSON.stringify +}; + export function filterRemoved(filters: IFilter[], defaultFilters: IFilter[], removed?: IFilter): IFilter[] { // If detail is undefined, remove all filters. if (!removed) { diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/persisted.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/persisted.svelte.ts deleted file mode 100644 index 13e9874b1..000000000 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/persisted.svelte.ts +++ /dev/null @@ -1,67 +0,0 @@ -export abstract class Serializer { - abstract deserialize(item: string): T; - abstract serialize(value: T): null | string; -} - -export class AuthJSONSerializer extends Serializer { - deserialize(item: string): null | string { - if (item === 'null') { - return null; - } - - return item; - } - serialize(value: null | string): null | string { - return value; - } -} - -export class JSONSerializer extends Serializer { - deserialize(item: string): T { - return JSON.parse(item); - } - serialize(value: T): null | string { - return JSON.stringify(value); - } -} - -export class LocalStore { - public get value(): T { - return this._value; - } - - public set value(value: T) { - this._value = value; - } - - private _value = $state() as T; - - constructor( - public key: string, - defaultValue: T, - public serializer: Serializer - ) { - this._value = defaultValue; - - const item = localStorage.getItem(key); - if (item !== null) { - this._value = this.serializer.deserialize(item); - } else { - this._value = defaultValue; - } - - $effect.root(() => { - $effect(() => { - if (this._value === undefined || this._value === null) { - localStorage.removeItem(this.key); - } else { - localStorage.setItem(this.key, this.serializer.serialize(this._value) as string); - } - }); - }); - } -} - -export function persisted(key: string, defaultValue: T, serializer: Serializer = new JSONSerializer()) { - return new LocalStore(key, defaultValue, serializer); -} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/api.svelte.ts index ab15cf3ba..c4693d0fc 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/api.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/api.svelte.ts @@ -88,7 +88,7 @@ export interface PostRemoveLinkRequest { export function deleteMarkCritical(request: PostMarkCriticalRequest) { const queryClient = useQueryClient(); return createMutation(() => ({ - enabled: () => !!accessToken.value && !!request.route.ids?.length, + enabled: () => !!accessToken.current && !!request.route.ids?.length, mutationFn: async () => { const client = useFetchClient(); await client.delete(`stacks/${request.route.ids?.join(',')}/mark-critical`); @@ -106,7 +106,7 @@ export function deleteMarkCritical(request: PostMarkCriticalRequest) { export function deleteStack(request: DeleteStackRequest) { const queryClient = useQueryClient(); return createMutation(() => ({ - enabled: () => !!accessToken.value && !!request.route.ids?.length, + enabled: () => !!accessToken.current && !!request.route.ids?.length, mutationFn: async () => { const client = useFetchClient(); const response = await client.deleteJSON(`stacks/${request.route.ids?.join(',')}`); @@ -125,7 +125,7 @@ export function deleteStack(request: DeleteStackRequest) { export function getStackQuery(request: GetStackRequest) { return createQuery(() => ({ - enabled: () => !!accessToken.value && !!request.route.id, + enabled: () => !!accessToken.current && !!request.route.id, queryFn: async ({ signal }: { signal: AbortSignal }) => { const client = useFetchClient(); const response = await client.getJSON(`stacks/${request.route.id}`, { @@ -141,7 +141,7 @@ export function getStackQuery(request: GetStackRequest) { export function postAddLink(request: PostAddLinkRequest) { const queryClient = useQueryClient(); return createMutation(() => ({ - enabled: () => !!accessToken.value && !!request.route.id, + enabled: () => !!accessToken.current && !!request.route.id, mutationFn: async (url: string) => { const client = useFetchClient(); await client.post(`stacks/${request.route.id}/add-link`, { value: url }); @@ -159,7 +159,7 @@ export function postAddLink(request: PostAddLinkRequest) { export function postChangeStatus(request: PostChangeStatusRequest) { const queryClient = useQueryClient(); return createMutation(() => ({ - enabled: () => !!accessToken.value && !!request.route.ids?.length, + enabled: () => !!accessToken.current && !!request.route.ids?.length, mutationFn: async (status: StackStatus) => { const client = useFetchClient(); await client.post(`stacks/${request.route.ids?.join(',')}/change-status`, undefined, { params: { status } }); @@ -177,7 +177,7 @@ export function postChangeStatus(request: PostChangeStatusRequest) { export function postMarkCritical(request: PostMarkCriticalRequest) { const queryClient = useQueryClient(); return createMutation(() => ({ - enabled: () => !!accessToken.value && !!request.route.ids?.length, + enabled: () => !!accessToken.current && !!request.route.ids?.length, mutationFn: async () => { const client = useFetchClient(); await client.post(`stacks/${request.route.ids?.join(',')}/mark-critical`); @@ -195,7 +195,7 @@ export function postMarkCritical(request: PostMarkCriticalRequest) { export function postMarkFixed(request: PostMarkFixedRequest) { const queryClient = useQueryClient(); return createMutation(() => ({ - enabled: () => !!accessToken.value && !!request.route.ids?.length, + enabled: () => !!accessToken.current && !!request.route.ids?.length, mutationFn: async (version?: string) => { const client = useFetchClient(); await client.post(`stacks/${request.route.ids?.join(',')}/mark-fixed`, undefined, { params: { version } }); @@ -213,7 +213,7 @@ export function postMarkFixed(request: PostMarkFixedRequest) { export function postMarkSnoozed(request: PostMarkSnoozedRequest) { const queryClient = useQueryClient(); return createMutation(() => ({ - enabled: () => !!accessToken.value && !!request.route.ids?.length, + enabled: () => !!accessToken.current && !!request.route.ids?.length, mutationFn: async (snoozeUntilUtc: Date) => { const client = useFetchClient(); await client.post(`stacks/${request.route.ids?.join(',')}/mark-snoozed`, undefined, { params: { snoozeUntilUtc: snoozeUntilUtc.toISOString() } }); @@ -231,7 +231,7 @@ export function postMarkSnoozed(request: PostMarkSnoozedRequest) { export function postPromote(request: PostPromoteRequest) { const queryClient = useQueryClient(); return createMutation, ProblemDetails, void>(() => ({ - enabled: () => !!accessToken.value && !!request.route.ids?.length, + enabled: () => !!accessToken.current && !!request.route.ids?.length, mutationFn: async () => { const client = useFetchClient(); const response = await client.post(`stacks/${request.route.ids?.join(',')}/promote`, undefined, { @@ -253,7 +253,7 @@ export function postPromote(request: PostPromoteRequest) { export function postRemoveLink(request: PostRemoveLinkRequest) { const queryClient = useQueryClient(); return createMutation(() => ({ - enabled: () => !!accessToken.value && !!request.route.id, + enabled: () => !!accessToken.current && !!request.route.id, mutationFn: async (url: string) => { const client = useFetchClient(); await client.post(`stacks/${request.route.id}/remove-link`, { value: url }); @@ -269,7 +269,7 @@ export function postRemoveLink(request: PostRemoveLinkRequest) { } export async function prefetchStack(request: GetStackRequest) { - if (!accessToken.value) { + if (!accessToken.current) { return; } diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/users/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/users/api.svelte.ts index d76938b71..392c78980 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/users/api.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/users/api.svelte.ts @@ -44,7 +44,7 @@ export function getMeQuery() { const queryClient = useQueryClient(); return createQuery(() => ({ - enabled: () => !!accessToken.value, + enabled: () => !!accessToken.current, onSuccess: (data: User) => { queryClient.setQueryData(queryKeys.id(data.id!), data); }, @@ -64,7 +64,7 @@ export function getMeQuery() { export function patchUser(request: PatchUserRequest) { const queryClient = useQueryClient(); return createMutation(() => ({ - enabled: () => !!accessToken.value && !!request.route.id, + enabled: () => !!accessToken.current && !!request.route.id, mutationFn: async (data: UpdateUser) => { const client = useFetchClient(); const response = await client.patchJSON(`users/${request.route.id}`, data); @@ -88,7 +88,7 @@ export function patchUser(request: PatchUserRequest) { export function postEmailAddress(request: PostEmailAddressRequest) { const queryClient = useQueryClient(); return createMutation>(() => ({ - enabled: () => !!accessToken.value && !!request.route.id, + enabled: () => !!accessToken.current && !!request.route.id, mutationFn: async (data: Pick) => { const client = useFetchClient(); const response = await client.postJSON(`users/${request.route.id}/email-address/${data.email_address}`); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/WebSocketClient.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/WebSocketClient.svelte.ts index f0b3df6d9..08fa17d54 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/WebSocketClient.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/WebSocketClient.svelte.ts @@ -22,8 +22,8 @@ export class WebSocketClient { const visibility = new DocumentVisibility(); $effect(() => { - if (this.accessToken !== accessToken.value) { - this.accessToken = accessToken.value; + if (this.accessToken !== accessToken.current) { + this.accessToken = accessToken.current; this.close(); } else if (!visibility.visible) { this.close(); diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte index 368c7f2a0..dc042c236 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte @@ -27,7 +27,7 @@ } let { children }: Props = $props(); - let isAuthenticated = $derived(accessToken.value !== null); + let isAuthenticated = $derived(accessToken.current !== null); const sidebar = useSidebar(); let isCommandOpen = $state(false); @@ -36,7 +36,7 @@ await next(); if (ctx.response && ctx.response.status === 401) { - accessToken.value = null; + accessToken.current = null; return; } diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte index 99b3d97ad..c6070321e 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte @@ -5,7 +5,7 @@ import * as DataTable from '$comp/data-table'; import * as FacetedFilter from '$comp/faceted-filter'; import { toFacetedFilters } from '$comp/filters/facets'; - import { DateFilter, filterChanged, filterRemoved, FilterSerializer, getDefaultFilters, type IFilter, toFilter } from '$comp/filters/filters.svelte'; + import { DateFilter, filterChanged, filterRemoved, filterSerializer, getDefaultFilters, type IFilter, toFilter } from '$comp/filters/filters.svelte'; import { Button } from '$comp/ui/button'; import * as Card from '$comp/ui/card'; import * as Sheet from '$comp/ui/sheet'; @@ -16,11 +16,10 @@ import { getTableContext } from '$features/events/components/table/options.svelte'; import { ChangeType, type WebSocketMessageValue } from '$features/websockets/models'; import { useFetchClientStatus } from '$shared/api/api.svelte'; - import { persisted } from '$shared/persisted.svelte'; import { isTableEmpty, removeTableData, removeTableSelection } from '$shared/table'; import { type FetchClientResponse, useFetchClient } from '@exceptionless/fetchclient'; import { createTable } from '@tanstack/svelte-table'; - import { useEventListener } from 'runed'; + import { PersistedState, useEventListener } from 'runed'; import { throttle } from 'throttle-debounce'; import IconOpenInNew from '~icons/mdi/open-in-new'; @@ -29,29 +28,29 @@ selectedEventId = row.id; } - const limit = persisted('events.limit', 10); + const limit = new PersistedState('events.limit', 10); const defaultFilters = getDefaultFilters(); - const persistedFilters = persisted('events.filters', defaultFilters, new FilterSerializer()); - persistedFilters.value.push(...defaultFilters.filter((df) => !persistedFilters.value.some((f) => f.key === df.key))); + const persistedFilters = new PersistedState('events.filters', defaultFilters, { serializer: filterSerializer }); + persistedFilters.current.push(...defaultFilters.filter((df) => !persistedFilters.current.some((f) => f.key === df.key))); - const filter = $derived(toFilter(persistedFilters.value.filter((f) => f.key !== 'date:date'))); - const facets = $derived(toFacetedFilters(persistedFilters.value)); - const time = $derived((persistedFilters.value.find((f) => f.key === 'date:date') as DateFilter).value as string); + const filter = $derived(toFilter(persistedFilters.current.filter((f) => f.key !== 'date:date'))); + const facets = $derived(toFacetedFilters(persistedFilters.current)); + const time = $derived((persistedFilters.current.find((f) => f.key === 'date:date') as DateFilter).value as string); function onDrawerFilterChanged(filter: IFilter): void { - persistedFilters.value = filterChanged(persistedFilters.value, filter); + persistedFilters.current = filterChanged(persistedFilters.current, filter); selectedEventId = null; } function onFilterChanged(filter: IFilter): void { - persistedFilters.value = filterChanged(persistedFilters.value, filter); + persistedFilters.current = filterChanged(persistedFilters.current, filter); } function onFilterRemoved(filter?: IFilter): void { - persistedFilters.value = filterRemoved(persistedFilters.value, defaultFilters, filter); + persistedFilters.current = filterRemoved(persistedFilters.current, defaultFilters, filter); } - const context = getTableContext>({ limit: limit.value, mode: 'summary' }); + const context = getTableContext>({ limit: limit.current, mode: 'summary' }); const table = createTable(context.options); const canRefresh = $derived(!table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected() && !table.getCanPreviousPage()); @@ -93,7 +92,7 @@ } // Do not refresh if the filter criteria doesn't match the web socket message. - if (!shouldRefreshPersistentEventChanged(persistedFilters.value, filter, message.organization_id, message.project_id, message.stack_id, message.id)) { + if (!shouldRefreshPersistentEventChanged(persistedFilters.current, filter, message.organization_id, message.project_id, message.stack_id, message.id)) { return; } @@ -120,7 +119,7 @@ - + {#snippet toolbarChildren()} {/snippet} @@ -131,7 +130,7 @@ {/if} - +
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/+layout.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/+layout.svelte index ec816a552..0e5f25957 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/+layout.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/+layout.svelte @@ -12,7 +12,7 @@ let { children } = $props(); const userQuery = getMeQuery(); - let isAuthenticated = $derived(accessToken.value !== null); + let isAuthenticated = $derived(accessToken.current !== null); const filteredRoutes = $derived.by(() => { const context: NavigationItemContext = { authenticated: isAuthenticated, user: userQuery.data }; return routes.filter((route) => (route.show ? route.show(context) : true)); diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte index b05894c65..65fba3c11 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte @@ -5,7 +5,7 @@ import * as DataTable from '$comp/data-table'; import * as FacetedFilter from '$comp/faceted-filter'; import { toFacetedFilters } from '$comp/filters/facets'; - import { DateFilter, filterChanged, filterRemoved, FilterSerializer, getDefaultFilters, type IFilter, toFilter } from '$comp/filters/filters.svelte'; + import { DateFilter, filterChanged, filterRemoved, filterSerializer, getDefaultFilters, type IFilter, toFilter } from '$comp/filters/filters.svelte'; import { Button } from '$comp/ui/button'; import * as Card from '$comp/ui/card'; import * as Sheet from '$comp/ui/sheet'; @@ -17,11 +17,10 @@ import TableStacksBulkActionsDropdownMenu from '$features/stacks/components/StacksBulkActionsDropdownMenu.svelte'; import { ChangeType, type WebSocketMessageValue } from '$features/websockets/models'; import { useFetchClientStatus } from '$shared/api/api.svelte'; - import { persisted } from '$shared/persisted.svelte'; import { isTableEmpty, removeTableData, removeTableSelection } from '$shared/table'; import { type FetchClientResponse, useFetchClient } from '@exceptionless/fetchclient'; import { createTable } from '@tanstack/svelte-table'; - import { useEventListener } from 'runed'; + import { PersistedState, useEventListener } from 'runed'; import { throttle } from 'throttle-debounce'; import IconOpenInNew from '~icons/mdi/open-in-new'; @@ -44,18 +43,18 @@ }); const eventId = $derived(eventsResponse?.data?.[0]?.id); - const limit = persisted('events.issues.limit', 10); + const limit = new PersistedState('events.issues.limit', 10); const defaultFilters = getDefaultFilters().filter((f) => f.key !== 'type'); - const persistedFilters = persisted('events.issues.filters', defaultFilters, new FilterSerializer()); - persistedFilters.value.push(...defaultFilters.filter((df) => !persistedFilters.value.some((f) => f.key === df.key))); + const persistedFilters = new PersistedState('events.issues.filters', defaultFilters, { serializer: filterSerializer }); + persistedFilters.current.push(...defaultFilters.filter((df) => !persistedFilters.current.some((f) => f.key === df.key))); - const filter = $derived(toFilter(persistedFilters.value.filter((f) => f.key !== 'date:date'))); - const facets = $derived(toFacetedFilters(persistedFilters.value)); - const time = $derived((persistedFilters.value.find((f) => f.key === 'date:date') as DateFilter).value as string); + const filter = $derived(toFilter(persistedFilters.current.filter((f) => f.key !== 'date:date'))); + const facets = $derived(toFacetedFilters(persistedFilters.current)); + const time = $derived((persistedFilters.current.find((f) => f.key === 'date:date') as DateFilter).value as string); function onDrawerFilterChanged(filter: IFilter): void { if (filter.key !== 'type') { - persistedFilters.value = filterChanged(persistedFilters.value, filter); + persistedFilters.current = filterChanged(persistedFilters.current, filter); } selectedStackId = undefined; @@ -63,15 +62,15 @@ function onFilterChanged(filter: IFilter): void { if (filter.key !== 'type') { - persistedFilters.value = filterChanged(persistedFilters.value, filter); + persistedFilters.current = filterChanged(persistedFilters.current, filter); } } function onFilterRemoved(filter?: IFilter): void { - persistedFilters.value = filterRemoved(persistedFilters.value, defaultFilters, filter); + persistedFilters.current = filterRemoved(persistedFilters.current, defaultFilters, filter); } - const context = getTableContext>({ limit: limit.value, mode: 'stack_frequent' }); + const context = getTableContext>({ limit: limit.current, mode: 'stack_frequent' }); const table = createTable(context.options); const canRefresh = $derived(!table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected() && !table.getCanPreviousPage()); @@ -113,7 +112,7 @@ } // Do not refresh if the filter criteria doesn't match the web socket message. - if (!shouldRefreshPersistentEventChanged(persistedFilters.value, filter, message.organization_id, message.project_id, message.id)) { + if (!shouldRefreshPersistentEventChanged(persistedFilters.current, filter, message.organization_id, message.project_id, message.id)) { return; } @@ -137,7 +136,7 @@ Issues - + {#snippet toolbarChildren()} {/snippet} @@ -148,7 +147,7 @@ {/if}
- +
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte index b20a13639..51aa4059f 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte @@ -6,7 +6,7 @@ import ErrorMessage from '$comp/ErrorMessage.svelte'; import * as FacetedFilter from '$comp/faceted-filter'; import { toFacetedFilters } from '$comp/filters/facets'; - import { filterChanged, filterRemoved, FilterSerializer, getDefaultFilters, type IFilter, toFilter } from '$comp/filters/filters.svelte'; + import { filterChanged, filterRemoved, filterSerializer, getDefaultFilters, type IFilter, toFilter } from '$comp/filters/filters.svelte'; import { Button } from '$comp/ui/button'; import * as Card from '$comp/ui/card'; import * as Sheet from '$comp/ui/sheet'; @@ -15,10 +15,10 @@ import { getTableContext } from '$features/events/components/table/options.svelte'; import { ChangeType, type WebSocketMessageValue } from '$features/websockets/models'; import { useFetchClientStatus } from '$shared/api/api.svelte'; - import { persisted } from '$shared/persisted.svelte'; import { isTableEmpty, removeTableData } from '$shared/table'; import { type FetchClientResponse, useFetchClient } from '@exceptionless/fetchclient'; import { createTable } from '@tanstack/svelte-table'; + import { PersistedState } from 'runed'; import { useEventListener } from 'runed'; import { debounce } from 'throttle-debounce'; import IconOpenInNew from '~icons/mdi/open-in-new'; @@ -28,30 +28,30 @@ selectedEventId = row.id; } - const limit = persisted('events.stream.limit', 10); + const limit = new PersistedState('events.stream.limit', 10); const defaultFilters = getDefaultFilters(false); - const persistedFilters = persisted('events.stream.filters', defaultFilters, new FilterSerializer()); - persistedFilters.value.push(...defaultFilters.filter((df) => !persistedFilters.value.some((f) => f.key === df.key))); + const persistedFilters = new PersistedState('events.stream.filters', defaultFilters, { serializer: filterSerializer }); + persistedFilters.current.push(...defaultFilters.filter((df) => !persistedFilters.current.some((f) => f.key === df.key))); - const filter = $derived(toFilter(persistedFilters.value)); - const facets = $derived(toFacetedFilters(persistedFilters.value)); + const filter = $derived(toFilter(persistedFilters.current)); + const facets = $derived(toFacetedFilters(persistedFilters.current)); function onDrawerFilterChanged(filter: IFilter): void { - persistedFilters.value = filterChanged(persistedFilters.value, filter); + persistedFilters.current = filterChanged(persistedFilters.current, filter); selectedEventId = null; } function onFilterChanged(filter: IFilter): void { if (filter.key !== 'date:date') { - persistedFilters.value = filterChanged(persistedFilters.value, filter); + persistedFilters.current = filterChanged(persistedFilters.current, filter); } } function onFilterRemoved(filter?: IFilter): void { - persistedFilters.value = filterRemoved(persistedFilters.value, defaultFilters, filter); + persistedFilters.current = filterRemoved(persistedFilters.current, defaultFilters, filter); } - const context = getTableContext>({ limit: limit.value, mode: 'summary' }, function (options) { + const context = getTableContext>({ limit: limit.current, mode: 'summary' }, function (options) { options.columns = options.columns.filter((c) => c.id !== 'select').map((c) => ({ ...c, enableSorting: false })); options.enableMultiRowSelection = false; options.enableRowSelection = false; @@ -94,7 +94,7 @@ data.push(summary); } - context.data = data.slice(-limit.value); + context.data = data.slice(-limit.current); context.meta = response.meta; } } @@ -112,7 +112,7 @@ } // Do not refresh if the filter criteria doesn't match the web socket message. - if (!shouldRefreshPersistentEventChanged(persistedFilters.value, filter, message.organization_id, message.project_id, message.stack_id, message.id)) { + if (!shouldRefreshPersistentEventChanged(persistedFilters.current, filter, message.organization_id, message.project_id, message.stack_id, message.id)) { return; } @@ -145,7 +145,7 @@
- +
diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(auth)/logout/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(auth)/logout/+page.svelte index e374a16bd..12de615a5 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(auth)/logout/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(auth)/logout/+page.svelte @@ -9,7 +9,7 @@ import { useFetchClientStatus } from '$shared/api/api.svelte'; import { useFetchClient } from '@exceptionless/fetchclient'; - let isAuthenticated = $derived(accessToken.value !== null); + let isAuthenticated = $derived(accessToken.current !== null); $effect(() => { if (!isAuthenticated) { diff --git a/src/Exceptionless.Web/ClientApp/src/routes/+layout.svelte b/src/Exceptionless.Web/ClientApp/src/routes/+layout.svelte index b7a1b9b2b..9e870c87c 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/+layout.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/+layout.svelte @@ -28,7 +28,7 @@ throw response.problem ?? response; } }); - setAccessTokenFunc(() => accessToken.value); + setAccessTokenFunc(() => accessToken.current); useMiddleware(async (ctx: FetchClientContext, next: () => Promise) => { await next();