diff --git a/apps/desktop/src/lib/analytics/analytics.ts b/apps/desktop/src/lib/analytics/analytics.ts index daad95b77d..41ad6f85f7 100644 --- a/apps/desktop/src/lib/analytics/analytics.ts +++ b/apps/desktop/src/lib/analytics/analytics.ts @@ -1,34 +1,22 @@ import { initPostHog } from '$lib/analytics/posthog'; import { initSentry } from '$lib/analytics/sentry'; -import { appAnalyticsConfirmed } from '$lib/config/appSettings'; -import { - appMetricsEnabled, - appErrorReportingEnabled, - appNonAnonMetricsEnabled -} from '$lib/config/appSettings'; +import { AppSettings } from '$lib/config/appSettings'; import posthog from 'posthog-js'; -export function initAnalyticsIfEnabled() { - const analyticsConfirmed = appAnalyticsConfirmed(); - analyticsConfirmed.onDisk().then((confirmed) => { +export function initAnalyticsIfEnabled(appSettings: AppSettings) { + appSettings.appAnalyticsConfirmed.onDisk().then((confirmed) => { if (confirmed) { - appErrorReportingEnabled() - .onDisk() - .then((enabled) => { - if (enabled) initSentry(); - }); - appMetricsEnabled() - .onDisk() - .then((enabled) => { - if (enabled) initPostHog(); - }); - appNonAnonMetricsEnabled() - .onDisk() - .then((enabled) => { - enabled - ? posthog.capture('nonAnonMetricsEnabled') - : posthog.capture('nonAnonMetricsDisabled'); - }); + appSettings.appErrorReportingEnabled.onDisk().then((enabled) => { + if (enabled) initSentry(); + }); + appSettings.appMetricsEnabled.onDisk().then((enabled) => { + if (enabled) initPostHog(); + }); + appSettings.appNonAnonMetricsEnabled.onDisk().then((enabled) => { + enabled + ? posthog.capture('nonAnonMetricsEnabled') + : posthog.capture('nonAnonMetricsDisabled'); + }); } }); } diff --git a/apps/desktop/src/lib/config/appSettings.ts b/apps/desktop/src/lib/config/appSettings.ts index 732629c461..10a9b978e5 100644 --- a/apps/desktop/src/lib/config/appSettings.ts +++ b/apps/desktop/src/lib/config/appSettings.ts @@ -8,95 +8,82 @@ import { createStore } from '@tauri-apps/plugin-store'; import { writable, type Writable } from 'svelte/store'; -const store = createStore('settings.json', { autoSave: true }); - -/** - * Persisted confirmation that user has confirmed their analytics settings. - */ -export function appAnalyticsConfirmed() { - return persisted(false, 'appAnalyticsConfirmed'); -} - -/** - * Provides a writable store for obtaining or setting the current state of application metrics. - * The application metrics can be enabled or disabled by setting the value of the store to true or false. - * @returns A writable store with the appMetricsEnabled config. - */ -export function appMetricsEnabled() { - return persisted(true, 'appMetricsEnabled'); -} - -/** - * Provides a writable store for obtaining or setting the current state of application error reporting. - * The application error reporting can be enabled or disabled by setting the value of the store to true or false. - * @returns A writable store with the appErrorReportingEnabled config. - */ -export function appErrorReportingEnabled() { - return persisted(true, 'appErrorReportingEnabled'); -} - -/** - * Provides a writable store for obtaining or setting the current state of non-anonemous application metrics. - * The setting can be enabled or disabled by setting the value of the store to true or false. - * @returns A writable store with the appNonAnonMetricsEnabled config. - */ -export function appNonAnonMetricsEnabled() { - return persisted(false, 'appNonAnonMetricsEnabled'); -} - -function persisted(initial: T, key: string): Writable & { onDisk: () => Promise } { - async function setAndPersist(value: T, set: (value: T) => void) { - const storeInstance = await store; - await storeInstance.set(key, value); - await storeInstance.save(); - - set(value); - } - - async function synchronize(set: (value: T) => void): Promise { - const value = await storeValueWithDefault(initial, key); - set(value); - } - - function update() { - throw 'Not implemented'; - } - - const thisStore = writable(initial, (set) => { - synchronize(set); - }); - - async function set(value: T) { - setAndPersist(value, thisStore.set); - } - - async function onDisk() { - return await storeValueWithDefault(initial, key); - } - - const subscribe = thisStore.subscribe; - - return { - subscribe, - set, - update, - onDisk - }; -} - -async function storeValueWithDefault(initial: T, key: string): Promise { - const storeInstance = await store; - try { - await storeInstance.load(); - } catch (e) { - // If file does not exist, reset it - storeInstance.reset(); +export class AppSettings { + // TODO: Remove this once `autoSave: boolean` is upstreamed. + // @ts-expect-error ts-2322 + diskStore = createStore('settings.json', { autoSave: 1 }); + constructor() {} + + /** + * Persisted confirmation that user has confirmed their analytics settings. + */ + readonly appAnalyticsConfirmed = this.persisted(false, 'appAnalyticsConfirmed'); + + /** + * Provides a writable store for obtaining or setting the current state of application metrics. + * The application metrics can be enabled or disabled by setting the value of the store to true or false. + * @returns A writable store with the appMetricsEnabled config. + */ + readonly appMetricsEnabled = this.persisted(true, 'appMetricsEnabled'); + + /** + * Provides a writable store for obtaining or setting the current state of application error reporting. + * The application error reporting can be enabled or disabled by setting the value of the store to true or false. + * @returns A writable store with the appErrorReportingEnabled config. + */ + readonly appErrorReportingEnabled = this.persisted(true, 'appErrorReportingEnabled'); + + /** + * Provides a writable store for obtaining or setting the current state of non-anonemous application metrics. + * The setting can be enabled or disabled by setting the value of the store to true or false. + * @returns A writable store with the appNonAnonMetricsEnabled config. + */ + readonly appNonAnonMetricsEnabled = this.persisted(false, 'appNonAnonMetricsEnabled'); + + private persisted(initial: T, key: string): Writable & { onDisk: () => Promise } { + const diskStore = this.diskStore; + const storeValueWithDefault = this.storeValueWithDefault.bind(this); + + const keySpecificStore = writable(initial, (set) => { + synchronize(set); + }); + + const subscribe = keySpecificStore.subscribe; + + async function setAndPersist(value: T, set: (value: T) => void) { + const store = await diskStore; + store.set(key, value); + set(value); + } + + async function synchronize(set: (value: T) => void): Promise { + const value = await storeValueWithDefault(initial, key); + set(value); + } + + async function set(value: T) { + setAndPersist(value, keySpecificStore.set); + } + + async function onDisk() { + return await storeValueWithDefault(initial, key); + } + + function update() { + throw 'Not implemented'; + } + + return { subscribe, set, update, onDisk }; } - const stored = (await storeInstance.get(key)) as T; - if (stored === null) { - return initial; - } else { - return stored; + async storeValueWithDefault(initial: T, key: string): Promise { + const store = await this.diskStore; + try { + await store.load(); + } catch (e) { + store.reset(); + } + const stored = (await store.get(key)) as T; + return stored === null ? initial : stored; } } diff --git a/apps/desktop/src/lib/settings/AnalyticsSettings.svelte b/apps/desktop/src/lib/settings/AnalyticsSettings.svelte index 49324aa536..1d809610b9 100644 --- a/apps/desktop/src/lib/settings/AnalyticsSettings.svelte +++ b/apps/desktop/src/lib/settings/AnalyticsSettings.svelte @@ -1,16 +1,14 @@
diff --git a/apps/desktop/src/lib/topics/service.ts b/apps/desktop/src/lib/topics/service.ts index 41d82f936c..d55eb5966a 100644 --- a/apps/desktop/src/lib/topics/service.ts +++ b/apps/desktop/src/lib/topics/service.ts @@ -1,5 +1,5 @@ import { persisted } from '$lib/persisted/persisted'; -import { get, type Readable } from 'svelte/store'; +import { get, type Readable, type Writable } from 'svelte/store'; import type { Project } from '$lib/backend/projects'; import type { GitHostIssueService } from '$lib/gitHost/interface/gitHostIssueService'; @@ -12,15 +12,13 @@ export type Topic = { }; export class TopicService { - topics = persisted([], this.localStorageKey); + topics: Writable; constructor( private project: Project, private issueService: Readable - ) {} - - private get localStorageKey(): string { - return `TopicService--${this.project.id}`; + ) { + this.topics = persisted([], `TopicService--${this.project.id}`); } create(title: string, body: string, hasIssue: boolean = false): Topic { diff --git a/apps/desktop/src/routes/+layout.svelte b/apps/desktop/src/routes/+layout.svelte index 205fd86472..8a0fbe1d1c 100644 --- a/apps/desktop/src/routes/+layout.svelte +++ b/apps/desktop/src/routes/+layout.svelte @@ -23,6 +23,7 @@ import AppUpdater from '$lib/components/AppUpdater.svelte'; import PromptModal from '$lib/components/PromptModal.svelte'; import ShareIssueModal from '$lib/components/ShareIssueModal.svelte'; + import { AppSettings } from '$lib/config/appSettings'; import { createGitHubUserServiceStore as createGitHubUserServiceStore, GitHubUserService @@ -63,6 +64,7 @@ setContext(AIPromptService, data.aiPromptService); setContext(LineManagerFactory, data.lineManagerFactory); setContext(StackingLineManagerFactory, data.stackingLineManagerFactory); + setContext(AppSettings, data.appSettings); setNameNormalizationServiceContext(new IpcNameNormalizationService(invoke)); diff --git a/apps/desktop/src/routes/+layout.ts b/apps/desktop/src/routes/+layout.ts index 4b91d3de07..46df482235 100644 --- a/apps/desktop/src/routes/+layout.ts +++ b/apps/desktop/src/routes/+layout.ts @@ -8,6 +8,7 @@ import { ProjectService } from '$lib/backend/projects'; import { PromptService } from '$lib/backend/prompt'; import { Tauri } from '$lib/backend/tauri'; import { UpdaterService } from '$lib/backend/updater'; +import { AppSettings } from '$lib/config/appSettings'; import { RemotesService } from '$lib/remotes/service'; import { RustSecretService } from '$lib/secrets/secretsService'; import { UserService } from '$lib/stores/user'; @@ -25,7 +26,8 @@ export const csr = true; // eslint-disable-next-line export const load: LayoutLoad = async () => { - initAnalyticsIfEnabled(); + const appSettings = new AppSettings(); + initAnalyticsIfEnabled(appSettings); // TODO: Find a workaround to avoid this dynamic import // https://github.com/sveltejs/kit/issues/905 @@ -47,6 +49,7 @@ export const load: LayoutLoad = async () => { const stackingLineManagerFactory = new StackingLineManagerFactory(); return { + appSettings, authService, cloud: httpClient, projectService, diff --git a/apps/desktop/src/routes/onboarding/+page.svelte b/apps/desktop/src/routes/onboarding/+page.svelte index fefdffa0dc..35695432a4 100644 --- a/apps/desktop/src/routes/onboarding/+page.svelte +++ b/apps/desktop/src/routes/onboarding/+page.svelte @@ -3,10 +3,12 @@ import newProjectSvg from '$lib/assets/illustrations/new-project.svg?raw'; import DecorativeSplitView from '$lib/components/DecorativeSplitView.svelte'; import Welcome from '$lib/components/Welcome.svelte'; - import { appAnalyticsConfirmed } from '$lib/config/appSettings'; + import { AppSettings } from '$lib/config/appSettings'; import AnalyticsConfirmation from '$lib/settings/AnalyticsConfirmation.svelte'; + import { getContext } from '$lib/utils/context'; - const analyticsConfirmed = appAnalyticsConfirmed(); + const appSettings = getContext(AppSettings); + const analyticsConfirmed = appSettings.appAnalyticsConfirmed; diff --git a/crates/gitbutler-tauri/Cargo.toml b/crates/gitbutler-tauri/Cargo.toml index 218e16682b..a9f224fedd 100644 --- a/crates/gitbutler-tauri/Cargo.toml +++ b/crates/gitbutler-tauri/Cargo.toml @@ -48,7 +48,7 @@ tauri-plugin-os = "^2.0.0" tauri-plugin-process = "^2.0.0" tauri-plugin-shell = "^2.0.0" tauri-plugin-single-instance = { version = "^2.0.0" } -tauri-plugin-store = { version = "^2.0.0" } +tauri-plugin-store = { version = "^2.0.1" } tauri-plugin-updater = "^2.0.0" tauri-plugin-window-state = { version = "^2.0.0" } parking_lot.workspace = true