Skip to content

Commit

Permalink
♻️ (synced-prefs) separate metadata and local prefs out (actualbudget…
Browse files Browse the repository at this point in the history
  • Loading branch information
MatissJanis authored Sep 20, 2024
1 parent 4485a63 commit a1bc66b
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 104 deletions.
50 changes: 42 additions & 8 deletions packages/desktop-client/src/components/FinancesApp.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-strict-ignore
import React, { type ReactElement, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import {
Route,
Expand All @@ -9,12 +10,12 @@ import {
useHref,
} from 'react-router-dom';

import { sync } from 'loot-core/client/actions';
import { addNotification, sync } from 'loot-core/client/actions';
import { type State } from 'loot-core/src/client/state-types';
import { checkForUpdateNotification } from 'loot-core/src/client/update-notification';
import * as undo from 'loot-core/src/platform/client/undo';

import { useAccounts } from '../hooks/useAccounts';
import { useLocalPref } from '../hooks/useLocalPref';
import { useNavigate } from '../hooks/useNavigate';
import { useResponsive } from '../ResponsiveProvider';
import { theme } from '../style';
Expand Down Expand Up @@ -88,20 +89,53 @@ function RouterBehaviors() {

export function FinancesApp() {
const dispatch = useDispatch();
const { t } = useTranslation();

const [lastUsedVersion, setLastUsedVersion] = useLocalPref(
'flags.updateNotificationShownForVersion',
);

useEffect(() => {
// Wait a little bit to make sure the sync button will get the
// sync start event. This can be improved later.
setTimeout(async () => {
await dispatch(sync());

await checkForUpdateNotification(
dispatch,
getIsOutdated,
getLatestVersion,
);
}, 100);
}, []);

useEffect(() => {
async function run() {
const latestVersion = await getLatestVersion();
const isOutdated = await getIsOutdated(latestVersion);

if (isOutdated && lastUsedVersion !== latestVersion) {
dispatch(
addNotification({
type: 'message',
title: t('A new version of Actual is available!'),
message: t(
'Version {{latestVersion}} of Actual was recently released.',
{ latestVersion },
),
sticky: true,
id: 'update-notification',
button: {
title: t('Open changelog'),
action: () => {
window.open('https://actualbudget.org/docs/releases');
},
},
onClose: () => {
setLastUsedVersion(latestVersion);
},
}),
);
}
}

run();
}, [lastUsedVersion, setLastUsedVersion]);

return (
<View style={{ height: '100%' }}>
<RouterBehaviors />
Expand Down
41 changes: 28 additions & 13 deletions packages/desktop-client/src/hooks/useLocalPref.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect } from 'react';

import { useLocalStorage } from 'usehooks-ts';

import { savePrefs } from 'loot-core/src/client/actions';
import { type State } from 'loot-core/src/client/state-types';
import { type LocalPrefs } from 'loot-core/src/types/prefs';

import { useMetadataPref } from './useMetadataPref';

type SetLocalPrefAction<K extends keyof LocalPrefs> = (
value: LocalPrefs[K],
) => void;

/**
* Local preferences are scoped to a specific budget file.
*/
export function useLocalPref<K extends keyof LocalPrefs>(
prefName: K,
): [LocalPrefs[K], SetLocalPrefAction<K>] {
const dispatch = useDispatch();
const setLocalPref = useCallback<SetLocalPrefAction<K>>(
value => {
dispatch(savePrefs({ [prefName]: value } as LocalPrefs));
const [budgetId] = useMetadataPref('id');

const [value, setValue] = useLocalStorage<LocalPrefs[K]>(
`${budgetId}-${prefName}`,
undefined,
{
deserializer: JSON.parse,
serializer: JSON.stringify,
},
[prefName, dispatch],
);
const localPref = useSelector(
(state: State) => state.prefs.local?.[prefName] as LocalPrefs[K],
);

return [localPref, setLocalPref];
// Migrate from old pref storage location (metadata.json) to local storage
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [metadataPref] = useMetadataPref(prefName as any);
useEffect(() => {
if (value !== undefined || metadataPref === undefined) {
return;
}

setValue(metadataPref);
}, [value, metadataPref, setValue]);

return [value, setValue];
}
22 changes: 17 additions & 5 deletions packages/desktop-client/src/hooks/useMetadataPref.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { type MetadataPrefs } from 'loot-core/src/types/prefs';
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { useLocalPref } from './useLocalPref';
import { savePrefs } from 'loot-core/client/actions';
import { type State } from 'loot-core/client/state-types';
import { type MetadataPrefs } from 'loot-core/types/prefs';

type SetMetadataPrefAction<K extends keyof MetadataPrefs> = (
value: MetadataPrefs[K],
Expand All @@ -9,7 +12,16 @@ type SetMetadataPrefAction<K extends keyof MetadataPrefs> = (
export function useMetadataPref<K extends keyof MetadataPrefs>(
prefName: K,
): [MetadataPrefs[K], SetMetadataPrefAction<K>] {
// TODO: implement logic for fetching the pref exclusively from the
// metadata.json file (in follow-up PR)
return useLocalPref(prefName);
const dispatch = useDispatch();
const setLocalPref = useCallback<SetMetadataPrefAction<K>>(
value => {
dispatch(savePrefs({ [prefName]: value }));
},
[prefName, dispatch],
);
const localPref = useSelector(
(state: State) => state.prefs.local?.[prefName],
);

return [localPref, setLocalPref];
}
7 changes: 3 additions & 4 deletions packages/loot-core/src/client/actions/prefs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// @ts-strict-ignore
import { send } from '../../platform/client/fetch';
import type * as prefs from '../../types/prefs';
import { type GlobalPrefs, type MetadataPrefs } from '../../types/prefs';
import * as constants from '../constants';

import { closeModal } from './modals';
Expand All @@ -26,7 +25,7 @@ export function loadPrefs() {
};
}

export function savePrefs(prefs: prefs.LocalPrefs) {
export function savePrefs(prefs: MetadataPrefs) {
return async (dispatch: Dispatch) => {
await send('save-prefs', prefs);
dispatch({
Expand All @@ -49,7 +48,7 @@ export function loadGlobalPrefs() {
}

export function saveGlobalPrefs(
prefs: prefs.GlobalPrefs,
prefs: GlobalPrefs,
onSaveGlobalPrefs?: () => void,
) {
return async (dispatch: Dispatch) => {
Expand Down
8 changes: 4 additions & 4 deletions packages/loot-core/src/client/state-types/prefs.d.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import type { GlobalPrefs, LocalPrefs, MetadataPrefs } from '../../types/prefs';
import type { GlobalPrefs, MetadataPrefs } from '../../types/prefs';
import type * as constants from '../constants';

export type PrefsState = {
local: LocalPrefs & MetadataPrefs;
local: MetadataPrefs;
global: GlobalPrefs;
};

export type SetPrefsAction = {
type: typeof constants.SET_PREFS;
prefs: LocalPrefs & MetadataPrefs;
prefs: MetadataPrefs;
globalPrefs: GlobalPrefs;
};

export type MergeLocalPrefsAction = {
type: typeof constants.MERGE_LOCAL_PREFS;
prefs: LocalPrefs & MetadataPrefs;
prefs: MetadataPrefs;
};

export type MergeGlobalPrefsAction = {
Expand Down
45 changes: 0 additions & 45 deletions packages/loot-core/src/client/update-notification.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/loot-core/src/server/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import * as connection from '../../platform/server/connection';
import { logger } from '../../platform/server/log';
import { sequential, once } from '../../shared/async';
import { setIn, getIn } from '../../shared/util';
import { LocalPrefs } from '../../types/prefs';
import { type MetadataPrefs } from '../../types/prefs';
import { triggerBudgetChanges, setType as setBudgetType } from '../budget/base';
import * as db from '../db';
import { PostError, SyncError } from '../errors';
Expand Down Expand Up @@ -304,7 +304,7 @@ export const applyMessages = sequential(async (messages: Message[]) => {
return data;
}

const prefsToSet: LocalPrefs = {};
const prefsToSet: MetadataPrefs = {};
const oldData = await fetchData();

undo.appendMessages(messages, oldData);
Expand Down
39 changes: 18 additions & 21 deletions packages/loot-core/src/types/prefs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,28 +53,25 @@ export type MetadataPrefs = Partial<{

/**
* Local preferences applicable to a single device. Stored in local storage.
* TODO: eventually `LocalPrefs` type should not use `MetadataPrefs`;
* this is only a stop-gap solution.
*/
export type LocalPrefs = MetadataPrefs &
Partial<{
'ui.showClosedAccounts': boolean;
'expand-splits': boolean;
'budget.collapsed': string[];
'budget.summaryCollapsed': boolean;
'budget.showHiddenCategories': boolean;
'budget.startMonth': string;
'flags.updateNotificationShownForVersion': string;
reportsViewLegend: boolean;
reportsViewSummary: boolean;
reportsViewLabel: boolean;
spendingReportFilter: string;
spendingReportMode: spendingReportModeType;
spendingReportCompare: string;
spendingReportCompareTo: string;
sidebarWidth: number;
'mobile.showSpentColumn': boolean;
}>;
export type LocalPrefs = Partial<{
'ui.showClosedAccounts': boolean;
'expand-splits': boolean;
'budget.collapsed': string[];
'budget.summaryCollapsed': boolean;
'budget.showHiddenCategories': boolean;
'budget.startMonth': string;
'flags.updateNotificationShownForVersion': string;
reportsViewLegend: boolean;
reportsViewSummary: boolean;
reportsViewLabel: boolean;
spendingReportFilter: string;
spendingReportMode: spendingReportModeType;
spendingReportCompare: string;
spendingReportCompareTo: string;
sidebarWidth: number;
'mobile.showSpentColumn': boolean;
}>;

export type Theme = 'light' | 'dark' | 'auto' | 'midnight' | 'development';
export type DarkTheme = 'dark' | 'midnight';
Expand Down
4 changes: 2 additions & 2 deletions packages/loot-core/src/types/server-handlers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
RuleEntity,
PayeeEntity,
} from './models';
import { GlobalPrefs, LocalPrefs } from './prefs';
import { GlobalPrefs, MetadataPrefs } from './prefs';
import { Query } from './query';
import { EmptyObject } from './util';

Expand Down Expand Up @@ -239,7 +239,7 @@ export interface ServerHandlers {

'save-prefs': (prefsToSet) => Promise<'ok'>;

'load-prefs': () => Promise<LocalPrefs | null>;
'load-prefs': () => Promise<MetadataPrefs | null>;

'sync-reset': () => Promise<{ error?: { reason: string; meta?: unknown } }>;

Expand Down
6 changes: 6 additions & 0 deletions upcoming-release-notes/3458.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---

SyncedPrefs: separate out MetadataPrefs and LocalPrefs in different storage locations.

0 comments on commit a1bc66b

Please sign in to comment.