Skip to content

Add cached settings for listViewOrderbookFilters #1700

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
45ac91b
move cachedwritablestore to ui-comp
hardingjam Apr 25, 2025
ada479e
index
hardingjam Apr 25, 2025
2422c7b
hide zero bal vaults
hardingjam Apr 25, 2025
157a963
Move cachedWritableStore to shared lib, test hideZeroBalanceVaults
hardingjam Apr 25, 2025
acf5f5c
add
hardingjam Apr 25, 2025
887a313
go
hardingjam Apr 25, 2025
c0bbe57
page settings
hardingjam Apr 25, 2025
b4201c6
replace
hardingjam Apr 25, 2025
f598c2a
store
hardingjam Apr 25, 2025
a4d0f39
add
hardingjam Apr 25, 2025
fce4eed
update
hardingjam Apr 25, 2025
a98fae7
format
hardingjam Apr 25, 2025
10a3e67
Merge branch 'Cache-stores-in-webapp' into Save-more-filters
hardingjam Apr 25, 2025
98dc799
rebert
hardingjam Apr 25, 2025
54def87
order hash
hardingjam Apr 25, 2025
f0f6e4e
do nothing
hardingjam Apr 25, 2025
b1fca38
Merge branch 'main' into Cache-stores-in-webapp
hardingjam Apr 25, 2025
040c643
Merge branch 'Cache-stores-in-webapp' into Save-more-filters
hardingjam Apr 25, 2025
b30e576
update
hardingjam Apr 25, 2025
fb0fd25
Merge branch 'Cache-stores-in-webapp' into Save-more-filters
hardingjam Apr 25, 2025
88d622d
fix error
hardingjam Apr 25, 2025
5101234
rm err
hardingjam Apr 25, 2025
b3795f3
show my items only
hardingjam Apr 25, 2025
0519341
add jsdoc
hardingjam Apr 25, 2025
fbb9fe0
Merge branch 'Cache-stores-in-webapp' into Save-more-filters
hardingjam Apr 25, 2025
4438b2a
add jsdoc
hardingjam Apr 25, 2025
bb464c7
format
hardingjam Apr 25, 2025
33cbf62
format
hardingjam Apr 25, 2025
587eb54
Merge branch 'main' into Cache-stores-in-webapp
hardingjam Apr 28, 2025
aa225b2
remove from taur-app
hardingjam Apr 28, 2025
144fdb0
Merge branch 'Cache-stores-in-webapp' into Save-more-filters
hardingjam Apr 28, 2025
6b44948
cachedWritableOptionalStore
hardingjam Apr 28, 2025
e2507ba
Merge branch 'Cache-stores-in-webapp' into Save-more-filters
hardingjam Apr 28, 2025
afcbd4b
formatted
hardingjam Apr 28, 2025
6704f25
no change
hardingjam Apr 28, 2025
0926719
type safing
hardingjam Apr 28, 2025
e28e3f1
Merge branch 'Cache-stores-in-webapp' into Save-more-filters
hardingjam Apr 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/ui-components/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ export { lightCodeMirrorTheme, darkCodeMirrorTheme } from './utils/codeMirrorThe

// Stores
export { default as transactionStore } from './stores/transactionStore';
export {
cachedWritableStore,
cachedWritableIntOptional,
cachedWritableStringOptional,
cachedWritableString
} from './storesGeneric/cachedWritableStore';

// Assets
export { default as logoLight } from './assets/logo-light.svg';
Expand Down
137 changes: 137 additions & 0 deletions packages/ui-components/src/lib/storesGeneric/cachedWritableStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { writable } from 'svelte/store';

/**
* Creates a writable Svelte store that persists its value to localStorage.
*
* @template T - The type of the value stored in the store
* @param {string} key - The localStorage key used to store the value
* @param {T} defaultValue - The default value to use when no value is found in localStorage
* @param {function(T): string} serialize - Function to convert the store value to a string for storage
* @param {function(string): T} deserialize - Function to convert the stored string back to the original type
* @returns {import('svelte/store').Writable<T>} A writable store that automatically syncs with localStorage
*
* @example
* // Create a store for a boolean value
* const darkMode = cachedWritableStore(
* 'darkMode',
* false,
* value => JSON.stringify(value),
* str => JSON.parse(str)
* );
*
* // Create a store for a complex object
* const userPreferences = cachedWritableStore(
* 'userPrefs',
* { theme: 'light', fontSize: 14 },
* value => JSON.stringify(value),
* str => JSON.parse(str)
* );
*/
export function cachedWritableStore<T>(
key: string,
defaultValue: T,
serialize: (value: T) => string,
deserialize: (serialized: string) => T
) {
const getCache = () => {
try {
const cached = localStorage.getItem(key);
return cached !== null ? deserialize(cached) : defaultValue;
} catch {
return defaultValue;
}
};
const setCache = (value?: T) => {
try {
if (value !== undefined) {
localStorage.setItem(key, serialize(value));
} else {
localStorage.removeItem(key);
}
} catch {
// Silently ignore localStorage errors to allow the application to function
// without persistence in environments where localStorage is unavailable
}
};

const data = writable<T>(getCache());

data.subscribe((value) => {
setCache(value);
});

return data;
}

export const cachedWritableString = (key: string, defaultValue = '') =>
cachedWritableStore<string>(
key,
defaultValue,
(v) => v,
(v) => v
);
export const cachedWritableInt = (key: string, defaultValue = 0) =>
cachedWritableStore<number>(
key,
defaultValue,
(v) => v.toString(),
(v) => {
const parsed = Number.parseInt(v);
return isNaN(parsed) ? defaultValue : parsed;
}
);
/**
* Creates a writable store that can hold an optional value of type T and persists to localStorage.
*
* @template T - The type of the value stored
* @param {string} key - The localStorage key to use for persistence
* @param {T | undefined} defaultValue - The default value if nothing is found in localStorage
* @param {function} serialize - Function to convert the value to a string for storage
* @param {function} deserialize - Function to convert the stored string back to a value
* @returns A writable store that persists to localStorage and can hold undefined values
*/
export const cachedWritableOptionalStore = <T>(
key: string,
defaultValue: T | undefined = undefined,
serialize: (value: T) => string,
deserialize: (serialized: string) => T
) =>
cachedWritableStore<T | undefined>(
key,
defaultValue,
(v) => (v !== undefined ? serialize(v) : ''),
(v) => (v !== '' ? deserialize(v) : undefined)
);

/**
* Creates a writable store that can hold an optional number value and persists to localStorage.
*
* @param {string} key - The localStorage key to use for persistence
* @param {number | undefined} defaultValue - The default value if nothing is found in localStorage
* @returns A writable store that persists to localStorage and can hold an optional number
*/
export const cachedWritableIntOptional = (key: string, defaultValue = undefined) =>
cachedWritableOptionalStore<number>(
key,
defaultValue,
(v) => v.toString(),
(v) => {
const parsed = Number.parseInt(v);
return isNaN(parsed) ? (defaultValue ?? 0) : parsed;
}
);

/**
* Creates a writable store that can hold an optional string value and persists to localStorage.
*
* @param {string} key - The localStorage key to use for persistence
* @param {string | undefined} defaultValue - The default value if nothing is found in localStorage
* @returns A writable store that persists to localStorage and can hold an optional string
*/
export const cachedWritableStringOptional = (key: string, defaultValue = undefined) =>
cachedWritableOptionalStore<string>(
key,
defaultValue,
(v) => v,
(v) => v
);
1 change: 0 additions & 1 deletion packages/webapp/src/lib/index.ts

This file was deleted.

103 changes: 103 additions & 0 deletions packages/webapp/src/lib/stores/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { cachedWritableStore, type ConfigSource } from '@rainlanguage/ui-components';

/**
* A persistent store that holds the application configuration settings.
*
* This store is saved to local storage and persists between sessions.
*
* @default undefined - No configuration is set by default
* @returns A writable store containing the application configuration
*/
export const settings = cachedWritableStore<ConfigSource | undefined>(
'settings',
undefined,
(value) => JSON.stringify(value),
(str) => {
try {
return JSON.parse(str) as ConfigSource;
} catch {
return undefined;
}
}
);

/**
* A persistent store that controls whether vaults with zero balance should be hidden in the UI.
*
* This setting is saved to local storage and persists between sessions.
*
* @default true - Zero balance vaults are hidden by default
* @returns A writable store containing a boolean value
*/
export const hideZeroBalanceVaults = cachedWritableStore<boolean>(
'settings.hideZeroBalanceVaults',
true, // default value is true
(value) => JSON.stringify(value),
(str) => {
try {
const value = JSON.parse(str);
return typeof value === 'boolean' ? value : true;
} catch {
return true;
}
}
);

/**
* A persistent store that controls whether to show only the user's items in lists.
*
* This setting is saved to local storage and persists between sessions.
*
* @default false - All items are shown by default
* @returns A writable store containing a boolean value
*/
export const showMyItemsOnly = cachedWritableStore<boolean>(
'settings.showMyItemsOnly',
false,
(value) => JSON.stringify(value),
(str) => {
try {
const value = JSON.parse(str);
return typeof value === 'boolean' ? value : false;
} catch {
return false;
}
}
);

/**
* A persistent store that holds active subgraph URLs for different networks/orderbooks.
*
* This store maps network/orderbook identifiers to their corresponding subgraph URLs.
* The setting is saved to local storage and persists between sessions.
*
* @default {} - Empty object by default
* @returns A writable store containing a record of subgraph URLs
*/
export const activeSubgraphs = cachedWritableStore<Record<string, string>>(
'settings.activeSubgraphs',
{},
JSON.stringify,
(s) => {
try {
return JSON.parse(s);
} catch {
return {};
}
}
);

/**
* A persistent store that holds the currently selected order hash.
*
* This setting is saved to local storage and persists between sessions.
*
* @default "" - Empty string by default
* @returns A writable store containing the order hash string
*/
export const orderHash = cachedWritableStore<string>(
'settings.orderHash',
'',
(value) => value,
(str) => str || ''
);
Comment on lines +90 to +103
Copy link
Contributor

@coderabbitai coderabbitai bot Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider adding type validation to the orderHash deserializer

The implementation for the orderHash store looks good, but consider adding type validation in the deserializer similar to other stores.

-	(str) => str || ''
+	(str) => {
+		return typeof str === 'string' ? str : '';
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* A persistent store that holds the currently selected order hash.
*
* This setting is saved to local storage and persists between sessions.
*
* @default "" - Empty string by default
* @returns A writable store containing the order hash string
*/
export const orderHash = cachedWritableStore<string>(
'settings.orderHash',
'',
(value) => value,
(str) => str || ''
);
/**
* A persistent store that holds the currently selected order hash.
*
* This setting is saved to local storage and persists between sessions.
*
* @default "" - Empty string by default
* @returns A writable store containing the order hash string
*/
export const orderHash = cachedWritableStore<string>(
'settings.orderHash',
'',
(value) => value,
(str) => {
return typeof str === 'string' ? str : '';
}
);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hardingjam Thanks for implementing the type validation! This small change will improve the code's robustness by ensuring consistent handling of unexpected data types from localStorage. Good job on maintaining type safety across your stores.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

4 changes: 4 additions & 0 deletions packages/webapp/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
import LoadingWrapper from '$lib/components/LoadingWrapper.svelte';
import { WalletProvider } from '@rainlanguage/ui-components';
import { signerAddress } from '$lib/stores/wagmi';
import { settings as cachedSettings } from '$lib/stores/settings';

const { settings } = $page.data.stores;
cachedSettings.set(settings);
Comment on lines +17 to +20
Copy link
Contributor

@coderabbitai coderabbitai bot Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider making the settings synchronization reactive.

The current implementation synchronizes settings only once on component initialization. For consistency, you may want to make this synchronization reactive to handle subsequent changes to page data.

-const { settings } = $page.data.stores;
-cachedSettings.set(settings);
+$: if ($page.data.stores?.settings) {
+  cachedSettings.set($page.data.stores.settings);
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { settings as cachedSettings } from '$lib/stores/settings';
const { settings } = $page.data.stores;
cachedSettings.set(settings);
import { settings as cachedSettings } from '$lib/stores/settings';
$: if ($page.data.stores?.settings) {
cachedSettings.set($page.data.stores.settings);
}

Copy link
Contributor Author

@hardingjam hardingjam Apr 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a hard-coded value that's fetched from strategies repo, so it won't change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hardingjam Thanks for implementing the reactive synchronization! This improvement ensures that any subsequent changes to page data will be properly reflected in the cached settings. Great work!


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!


// Query client for caching
const queryClient = new QueryClient({
Expand Down
10 changes: 2 additions & 8 deletions packages/webapp/src/routes/orders/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,17 @@
import { page } from '$app/stores';
import { OrdersListTable, PageHeader } from '@rainlanguage/ui-components';
import type { AppStoresInterface } from '@rainlanguage/ui-components';
import { connected } from '$lib/stores/wagmi.ts';
import { writable } from 'svelte/store';
import { activeSubgraphs, orderHash, showMyItemsOnly } from '$lib/stores/settings';

const {
activeSubgraphs,
settings,
accounts,
activeAccountsItems,
activeOrderStatus,
orderHash,
hideZeroBalanceVaults,
activeNetworkRef,
activeOrderbookRef,
showMyItemsOnly = writable(false)
activeOrderbookRef
}: AppStoresInterface = $page.data.stores;

$: showMyItemsOnly.set($connected);
</script>

<PageHeader title={'Orders'} pathname={$page.url.pathname} />
Expand Down
12 changes: 3 additions & 9 deletions packages/webapp/src/routes/vaults/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,20 @@
import { PageHeader, VaultsListTable } from '@rainlanguage/ui-components';
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { connected } from '$lib/stores/wagmi';
import { writable } from 'svelte/store';
import { hideZeroBalanceVaults, showMyItemsOnly, orderHash } from '$lib/stores/settings';
import { activeSubgraphs } from '$lib/stores/settings';

const {
activeOrderbook,
subgraphUrl,
orderHash,
activeSubgraphs,
settings,
accounts,
activeAccountsItems,
activeOrderStatus,
hideZeroBalanceVaults,
activeNetworkRef,
activeOrderbookRef,
activeAccounts,
activeNetworkOrderbooks,
showMyItemsOnly = writable(false)
activeNetworkOrderbooks
} = $page.data.stores;

export async function resetActiveNetworkRef() {
Expand Down Expand Up @@ -48,8 +44,6 @@
resetActiveOrderbookRef();
}
});

$: showMyItemsOnly.set($connected);
</script>

<PageHeader title="Vaults" pathname={$page.url.pathname} />
Expand Down
37 changes: 37 additions & 0 deletions tauri-app/src/lib/mocks/mockConfigSource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { ConfigSource } from '@rainlanguage/orderbook';

export const mockConfigSource: ConfigSource = {
networks: {
mainnet: {
rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID',
Copy link
Contributor

@coderabbitai coderabbitai bot Apr 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider using a clearer mock URL format

While this works as mock data, using a URL with a placeholder PROJECT-ID might be confusing in tests.

-      rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID',
+      rpc: 'https://mock.example.com/mainnet',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID',
rpc: 'https://mock.example.com/mainnet',

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this mock is fine

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hardingjam Understood! I'll respect your preference for the mock URL format.


✏️ Learnings added
Learnt from: hardingjam
PR: rainlanguage/rain.orderbook#1700
File: tauri-app/src/lib/mocks/mockConfigSource.ts:6-6
Timestamp: 2025-04-28T10:58:11.112Z
Learning: In mock data for testing in this codebase, it's acceptable to use URL placeholders like 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID' as they clearly indicate the expected format for actual implementation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

'chain-id': 1,
label: 'Ethereum Mainnet',
currency: 'ETH',
},
},
subgraphs: {
mainnet: 'https://api.thegraph.com/subgraphs/name/mainnet',
},
orderbooks: {
orderbook1: {
address: '0xOrderbookAddress1',
network: 'mainnet',
subgraph: 'uniswap',
label: 'Orderbook 1',
Comment on lines +17 to +20
Copy link
Contributor

@coderabbitai coderabbitai bot Apr 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix inconsistency in subgraph reference

The orderbook references a 'uniswap' subgraph on line 19, but only 'mainnet' is defined in the subgraphs object (line 13). This could cause issues in tests that attempt to access this subgraph.

-      subgraph: 'uniswap',
+      subgraph: 'mainnet',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
address: '0xOrderbookAddress1',
network: 'mainnet',
subgraph: 'uniswap',
label: 'Orderbook 1',
address: '0xOrderbookAddress1',
network: 'mainnet',
subgraph: 'mainnet',
label: 'Orderbook 1',

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this mock is fine

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hardingjam I understand, thanks for the clarification. I'll accept the mock configuration as is.


✏️ Learnings added
Learnt from: hardingjam
PR: rainlanguage/rain.orderbook#1700
File: tauri-app/src/lib/mocks/mockConfigSource.ts:17-20
Timestamp: 2025-04-28T10:58:15.643Z
Learning: In mock configuration files for testing purposes, such as mockConfigSource.ts, inconsistencies between referenced subgraphs and defined subgraphs may be intentional and acceptable for specific test scenarios.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

},
},
deployers: {
deployer1: {
address: '0xDeployerAddress1',
network: 'mainnet',
label: 'Deployer 1',
},
},
metaboards: {
metaboard1: 'https://example.com/metaboard1',
},
accounts: {
name_one: 'address_one',
name_two: 'address_two',
},
};
Loading