@rainlanguage/ui-components 0.0.1-alpha.244 → 0.0.1-alpha.246

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ <script>import { ClockOutline } from "flowbite-svelte-icons";
2
+ import { useLocalTime as useLocalTimeStore } from "../storesGeneric/useLocalTime";
3
+ export let useLocalTime = useLocalTimeStore;
4
+ function toggle() {
5
+ useLocalTime.update((value) => !value);
6
+ }
7
+ </script>
8
+
9
+ <button
10
+ type="button"
11
+ on:click={toggle}
12
+ data-testid="timezone-toggle"
13
+ aria-pressed={$useLocalTime}
14
+ title={$useLocalTime
15
+ ? 'Showing local time — click for UTC'
16
+ : 'Showing UTC — click for local time'}
17
+ aria-label={$useLocalTime
18
+ ? 'Switch timestamps to UTC'
19
+ : 'Switch timestamps to local time'}
20
+ class="inline-flex items-center gap-1 rounded-lg px-2 py-1.5 text-xs font-semibold tabular-nums text-gray-500 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
21
+ >
22
+ <ClockOutline class="h-4 w-4 shrink-0" />
23
+ <span>{$useLocalTime ? 'Local' : 'UTC'}</span>
24
+ </button>
@@ -0,0 +1,24 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { Writable } from 'svelte/store';
3
+ declare const __propDef: {
4
+ props: {
5
+ /**
6
+ * Toggle for displaying timestamps in the browser's local timezone vs UTC.
7
+ * Sits next to the dark-mode / scrub buttons in the sidebar.
8
+ *
9
+ * The store is injectable for testing; it defaults to the global preference store.
10
+ */ useLocalTime?: Writable<boolean>;
11
+ };
12
+ events: {
13
+ [evt: string]: CustomEvent<any>;
14
+ };
15
+ slots: {};
16
+ exports?: {} | undefined;
17
+ bindings?: string | undefined;
18
+ };
19
+ export type ButtonTimeZoneProps = typeof __propDef.props;
20
+ export type ButtonTimeZoneEvents = typeof __propDef.events;
21
+ export type ButtonTimeZoneSlots = typeof __propDef.slots;
22
+ export default class ButtonTimeZone extends SvelteComponent<ButtonTimeZoneProps, ButtonTimeZoneEvents, ButtonTimeZoneSlots> {
23
+ }
24
+ export {};
@@ -17,6 +17,7 @@ import {
17
17
  TIME_DELTA_30_DAYS,
18
18
  TIME_DELTA_1_YEAR
19
19
  } from "../../services/time";
20
+ import { useLocalTime } from "../../storesGeneric/useLocalTime";
20
21
  import {
21
22
  Button,
22
23
  ButtonGroup,
@@ -113,7 +114,7 @@ function setupChart() {
113
114
  mode: CrosshairMode.Normal
114
115
  },
115
116
  timeScale: {
116
- tickMarkFormatter: (time) => formatChartTimestamp(time, timeDelta)
117
+ tickMarkFormatter: (time) => formatChartTimestamp(time, timeDelta, $useLocalTime)
117
118
  }
118
119
  });
119
120
  buyVolumeSeries = chart.addHistogramSeries({
@@ -179,14 +180,18 @@ function updateChartData() {
179
180
  }
180
181
  setTimeScale();
181
182
  }
182
- function setTimeScale() {
183
+ function applyTickMarkFormatter(useLocal = $useLocalTime, delta = timeDelta) {
183
184
  if (!chart) return;
184
185
  chart.timeScale().applyOptions({
185
- tickMarkFormatter: (time) => formatChartTimestamp(time, timeDelta)
186
+ tickMarkFormatter: (time) => formatChartTimestamp(time, delta, useLocal)
186
187
  });
188
+ }
189
+ function setTimeScale(delta = timeDelta) {
190
+ if (!chart) return;
191
+ applyTickMarkFormatter($useLocalTime, delta);
187
192
  if (chartData && chartData.pricePoints.length > 0) {
188
193
  const now = Math.floor(Date.now() / 1e3);
189
- const from = now - timeDelta;
194
+ const from = now - delta;
190
195
  chart.timeScale().setVisibleRange({ from, to: now });
191
196
  } else {
192
197
  chart.timeScale().fitContent();
@@ -205,6 +210,8 @@ $: if (chartElement && trades.length > 0 && selectedPair && !chart)
205
210
  setupChart();
206
211
  $: if (chart && chartData) updateChartData();
207
212
  $: if (chart && $lightweightChartsTheme) setChartOptions();
213
+ $: if (chart) applyTickMarkFormatter($useLocalTime, timeDelta);
214
+ $: if (chart) setTimeScale(timeDelta);
208
215
  onMount(() => {
209
216
  if (trades.length > 0 && selectedPair) setupChart();
210
217
  });
@@ -6,6 +6,7 @@ import TanstackOrderQuote from "./TanstackOrderQuote.svelte";
6
6
  import TanstackPageContentDetail from "./TanstackPageContentDetail.svelte";
7
7
  import CardProperty from "../CardProperty.svelte";
8
8
  import { formatTimestampSecondsAsLocal } from "../../services/time";
9
+ import { useLocalTime } from "../../storesGeneric/useLocalTime";
9
10
  import ButtonVaultLink from "../ButtonVaultLink.svelte";
10
11
  import OrderVaultsVolTable from "../tables/OrderVaultsVolTable.svelte";
11
12
  import { QKEY_ORDER } from "../../queries/keys";
@@ -160,7 +161,7 @@ const formatBuilderState = (builderState) => {
160
161
  <CardProperty>
161
162
  <svelte:fragment slot="key">Created</svelte:fragment>
162
163
  <svelte:fragment slot="value">
163
- {formatTimestampSecondsAsLocal(data.timestampAdded)}
164
+ {formatTimestampSecondsAsLocal(data.timestampAdded, $useLocalTime)}
164
165
  </svelte:fragment>
165
166
  </CardProperty>
166
167
 
@@ -3,6 +3,7 @@ import TanstackAppTable from "../TanstackAppTable.svelte";
3
3
  import { QKEY_ORDER_TRADES_LIST } from "../../queries/keys";
4
4
  import { TableBodyCell, TableHeadCell } from "flowbite-svelte";
5
5
  import { formatTimestampSecondsAsLocal } from "../../services/time";
6
+ import { useLocalTime } from "../../storesGeneric/useLocalTime";
6
7
  import Hash, { HashType } from "../Hash.svelte";
7
8
  import { BugOutline } from "flowbite-svelte-icons";
8
9
  import TableTimeFilters from "../charts/TableTimeFilters.svelte";
@@ -71,7 +72,7 @@ const AppTable = TanstackAppTable;
71
72
  {@const oiRatio = Math.abs(outputAmt / inputAmt)}
72
73
  {@const validRatio = Number.isFinite(ioRatio) && Number.isFinite(oiRatio)}
73
74
  <TableBodyCell tdClass="px-4 py-2">
74
- {formatTimestampSecondsAsLocal(BigInt(item.timestamp))}
75
+ {formatTimestampSecondsAsLocal(BigInt(item.timestamp), $useLocalTime)}
75
76
  </TableBodyCell>
76
77
  <TableBodyCell tdClass="px-4 py-2">
77
78
  <div class="flex flex-col gap-1 text-sm">
@@ -5,6 +5,7 @@ import { createInfiniteQuery, createQuery } from "@tanstack/svelte-query";
5
5
  import { RaindexOrder } from "@rainlanguage/raindex";
6
6
  import TanstackAppTable from "../TanstackAppTable.svelte";
7
7
  import { formatTimestampSecondsAsLocal } from "../../services/time";
8
+ import { useLocalTime } from "../../storesGeneric/useLocalTime";
8
9
  import ListViewRaindexFilters from "../ListViewRaindexFilters.svelte";
9
10
  import Hash, { HashType } from "../Hash.svelte";
10
11
  import VaultCard from "../VaultCard.svelte";
@@ -177,7 +178,7 @@ const AppTable = TanstackAppTable;
177
178
  {/if}
178
179
  </div>
179
180
  <span class="text-xs text-gray-500 dark:text-gray-400">
180
- Added: {formatTimestampSecondsAsLocal(item.timestampAdded)}
181
+ Added: {formatTimestampSecondsAsLocal(item.timestampAdded, $useLocalTime)}
181
182
  </span>
182
183
  </div>
183
184
  </TableBodyCell>
@@ -4,6 +4,7 @@ import {
4
4
  RaindexVault
5
5
  } from "@rainlanguage/raindex";
6
6
  import { formatTimestampSecondsAsLocal } from "../../services/time";
7
+ import { useLocalTime } from "../../storesGeneric/useLocalTime";
7
8
  import Hash, { HashType } from "../Hash.svelte";
8
9
  import { QKEY_VAULT_CHANGES } from "../../queries/keys";
9
10
  import { DEFAULT_PAGE_SIZE } from "../../queries/constants";
@@ -70,7 +71,7 @@ const AppTable = TanstackAppTable;
70
71
  </Tooltip>
71
72
  </div>
72
73
  <span class="text-xs text-gray-500 dark:text-gray-400">
73
- {formatTimestampSecondsAsLocal(BigInt(item.timestamp))}
74
+ {formatTimestampSecondsAsLocal(BigInt(item.timestamp), $useLocalTime)}
74
75
  </span>
75
76
  </div>
76
77
  </TableBodyCell>
package/dist/index.d.ts CHANGED
@@ -60,6 +60,7 @@ export { default as OrderOrVaultHash } from "./components/OrderOrVaultHash.svelt
60
60
  export { default as License } from "./components/License.svelte";
61
61
  export { default as ButtonDarkMode } from "./components/ButtonDarkMode.svelte";
62
62
  export { default as ButtonScrub } from "./components/ButtonScrub.svelte";
63
+ export { default as ButtonTimeZone } from "./components/ButtonTimeZone.svelte";
63
64
  export { default as Sensitive } from "./components/Sensitive.svelte";
64
65
  export { default as OrderPage } from "./components/deployment/OrderPage.svelte";
65
66
  export { default as InputHex } from "./components/input/InputHex.svelte";
@@ -98,6 +99,7 @@ export { darkChartTheme, lightChartTheme, } from "./utils/lightweightChartsTheme
98
99
  export { lightCodeMirrorTheme, darkCodeMirrorTheme, } from "./utils/codeMirrorThemes";
99
100
  export { cachedWritableStore, cachedWritableIntOptional, cachedWritableStringOptional, cachedWritableString, } from "./storesGeneric/cachedWritableStore";
100
101
  export { scrub } from "./storesGeneric/scrub";
102
+ export { useLocalTime } from "./storesGeneric/useLocalTime";
101
103
  export { default as logoLight } from "./assets/logo-light.svg";
102
104
  export { default as logoDark } from "./assets/logo-dark.svg";
103
105
  export { default as RaindexOrderBuilderProvider } from "./providers/RaindexOrderBuilderProvider.svelte";
package/dist/index.js CHANGED
@@ -61,6 +61,7 @@ export { default as OrderOrVaultHash } from "./components/OrderOrVaultHash.svelt
61
61
  export { default as License } from "./components/License.svelte";
62
62
  export { default as ButtonDarkMode } from "./components/ButtonDarkMode.svelte";
63
63
  export { default as ButtonScrub } from "./components/ButtonScrub.svelte";
64
+ export { default as ButtonTimeZone } from "./components/ButtonTimeZone.svelte";
64
65
  export { default as Sensitive } from "./components/Sensitive.svelte";
65
66
  export { default as OrderPage } from "./components/deployment/OrderPage.svelte";
66
67
  export { default as InputHex } from "./components/input/InputHex.svelte";
@@ -95,6 +96,7 @@ export { lightCodeMirrorTheme, darkCodeMirrorTheme, } from "./utils/codeMirrorTh
95
96
  // Stores
96
97
  export { cachedWritableStore, cachedWritableIntOptional, cachedWritableStringOptional, cachedWritableString, } from "./storesGeneric/cachedWritableStore";
97
98
  export { scrub } from "./storesGeneric/scrub";
99
+ export { useLocalTime } from "./storesGeneric/useLocalTime";
98
100
  // Assets
99
101
  export { default as logoLight } from "./assets/logo-light.svg";
100
102
  export { default as logoDark } from "./assets/logo-dark.svg";
@@ -37,7 +37,12 @@ export declare function getTokenLabel(token: RaindexVaultToken): string;
37
37
  export declare function pairsAreEqual(pairA: TradingPair, pairB: TradingPair): boolean;
38
38
  export declare function findPairIndex(pairs: TradingPair[], targetPair: TradingPair): number;
39
39
  export declare function flipTradingPair(pair: TradingPair): TradingPair;
40
- export declare function formatChartTimestamp(timestampSeconds: number, timeDeltaSeconds: number): string;
40
+ /**
41
+ * Formats a chart axis tick.
42
+ *
43
+ * @param useLocalTime - when true, use the browser timezone; otherwise UTC
44
+ */
45
+ export declare function formatChartTimestamp(timestampSeconds: number, timeDeltaSeconds: number, useLocalTime?: boolean): string;
41
46
  export type TransformPairTradesInput = {
42
47
  trades: RaindexTrade[];
43
48
  baseTokenAddress: string;
@@ -108,12 +108,23 @@ export function flipTradingPair(pair) {
108
108
  quoteToken: pair.baseToken,
109
109
  };
110
110
  }
111
- export function formatChartTimestamp(timestampSeconds, timeDeltaSeconds) {
111
+ /**
112
+ * Formats a chart axis tick.
113
+ *
114
+ * @param useLocalTime - when true, use the browser timezone; otherwise UTC
115
+ */
116
+ export function formatChartTimestamp(timestampSeconds, timeDeltaSeconds, useLocalTime = false) {
112
117
  const date = new Date(timestampSeconds * 1000);
113
- const day = date.getDate();
114
- const month = date.toLocaleString("en-US", { month: "short" });
115
- const hours = date.getHours().toString().padStart(2, "0");
116
- const minutes = date.getMinutes().toString().padStart(2, "0");
118
+ const day = useLocalTime ? date.getDate() : date.getUTCDate();
119
+ const month = useLocalTime
120
+ ? date.toLocaleString("en-US", { month: "short" })
121
+ : date.toLocaleString("en-US", { month: "short", timeZone: "UTC" });
122
+ const hours = (useLocalTime ? date.getHours() : date.getUTCHours())
123
+ .toString()
124
+ .padStart(2, "0");
125
+ const minutes = (useLocalTime ? date.getMinutes() : date.getUTCMinutes())
126
+ .toString()
127
+ .padStart(2, "0");
117
128
  if (timeDeltaSeconds <= TIME_DELTA_24_HOURS) {
118
129
  return `${month} ${day} ${hours}:${minutes}`;
119
130
  }
@@ -5,7 +5,13 @@ export declare const TIME_DELTA_7_DAYS: number;
5
5
  export declare const TIME_DELTA_30_DAYS: number;
6
6
  export declare const TIME_DELTA_1_YEAR: number;
7
7
  export declare function dateTimestamp(date: Date): number;
8
- export declare function formatTimestampSecondsAsLocal(timestampSeconds: bigint): string;
8
+ /**
9
+ * Formats a unix-seconds timestamp for display.
10
+ *
11
+ * @param timestampSeconds - unix epoch seconds
12
+ * @param useLocalTime - when true, format in the browser timezone; otherwise UTC
13
+ */
14
+ export declare function formatTimestampSecondsAsLocal(timestampSeconds: bigint, useLocalTime?: boolean): string;
9
15
  export declare function timestampSecondsToUTCTimestamp(timestampSeconds: bigint): UTCTimestamp;
10
16
  /**
11
17
  * Method to put a timeout on a promise, throws the exception if promise is not settled within the time
@@ -13,10 +13,15 @@ export const TIME_DELTA_1_YEAR = TIME_DELTA_24_HOURS * 365;
13
13
  export function dateTimestamp(date) {
14
14
  return Math.floor(date.getTime() / 1000);
15
15
  }
16
- export function formatTimestampSecondsAsLocal(timestampSeconds) {
17
- return dayjs(timestampSeconds * BigInt("1000"))
18
- .utc()
19
- .format("L LT");
16
+ /**
17
+ * Formats a unix-seconds timestamp for display.
18
+ *
19
+ * @param timestampSeconds - unix epoch seconds
20
+ * @param useLocalTime - when true, format in the browser timezone; otherwise UTC
21
+ */
22
+ export function formatTimestampSecondsAsLocal(timestampSeconds, useLocalTime = false) {
23
+ const date = dayjs(timestampSeconds * BigInt("1000"));
24
+ return (useLocalTime ? date : date.utc()).format("L LT");
20
25
  }
21
26
  export function timestampSecondsToUTCTimestamp(timestampSeconds) {
22
27
  return dayjs(timestampSeconds * BigInt("1000")).unix();
@@ -37,63 +42,3 @@ export async function promiseTimeout(promise, time, exception) {
37
42
  new Promise((_resolve, reject) => (timeout = setTimeout(reject, time, exception))),
38
43
  ]).finally(() => clearTimeout(timeout));
39
44
  }
40
- if (import.meta.vitest) {
41
- const { describe, it, expect, vi } = import.meta.vitest;
42
- describe("Date and timestamp utilities", () => {
43
- describe("formatTimestampSecondsAsLocal", () => {
44
- it("converts timestamp to local format", () => {
45
- const result = formatTimestampSecondsAsLocal(BigInt("1672531200")); // Jan 1, 2023 12:00 AM
46
- expect(result).toBe("01/01/2023 12:00 AM");
47
- });
48
- });
49
- describe("timestampSecondsToUTCTimestamp", () => {
50
- it("converts bigint timestamp to UTCTimestamp", () => {
51
- const result = timestampSecondsToUTCTimestamp(BigInt("1672531200"));
52
- expect(result).toBe(1672531200);
53
- });
54
- });
55
- });
56
- describe("promiseTimeout", () => {
57
- it("resolves when promise resolves before timeout", async () => {
58
- const testValue = "test";
59
- const promise = Promise.resolve(testValue);
60
- const result = await promiseTimeout(promise, 100, new Error("Timeout"));
61
- expect(result).toBe(testValue);
62
- });
63
- it("rejects when promise times out", async () => {
64
- const promise = new Promise((resolve) => setTimeout(resolve, 200));
65
- const exception = new Error("Timeout");
66
- await expect(promiseTimeout(promise, 100, exception)).rejects.toThrow(exception);
67
- });
68
- it("rejects when original promise rejects", async () => {
69
- const error = new Error("Original rejection");
70
- const promise = Promise.reject(error);
71
- await expect(promiseTimeout(promise, 100, new Error("Timeout"))).rejects.toThrow(error);
72
- });
73
- it("clears timeout after promise resolution", async () => {
74
- vi.spyOn(global, "clearTimeout");
75
- const promise = Promise.resolve("test");
76
- await promiseTimeout(promise, 100, new Error("Timeout"));
77
- expect(clearTimeout).toHaveBeenCalled();
78
- });
79
- it("clears timeout after promise rejection", async () => {
80
- vi.spyOn(global, "clearTimeout");
81
- const promise = Promise.reject(new Error("Original rejection"));
82
- try {
83
- await promiseTimeout(promise, 100, new Error("Timeout"));
84
- }
85
- catch {
86
- // Ignore the error
87
- }
88
- expect(clearTimeout).toHaveBeenCalled();
89
- });
90
- });
91
- describe("dateTimestamp", () => {
92
- it("should get date timestamp in seconds", () => {
93
- const date = new Date(2022, 1, 16, 17, 32, 11, 168);
94
- const result = dateTimestamp(date);
95
- const expected = Math.floor(date.getTime() / 1000);
96
- expect(result).toEqual(expected);
97
- });
98
- });
99
- }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Global preference for displaying timestamps in the browser's local timezone
3
+ * instead of UTC.
4
+ *
5
+ * When `false` (default), all formatted timestamps stay in UTC — matching the
6
+ * historical Raindex webapp behaviour. When `true`, the same values are shown
7
+ * in the user's local timezone.
8
+ *
9
+ * Persisted under `settings.useLocalTime` so the preference survives reloads.
10
+ */
11
+ export declare const useLocalTime: import("svelte/store").Writable<boolean>;
@@ -0,0 +1,19 @@
1
+ import { cachedWritableStore } from "./cachedWritableStore";
2
+ /**
3
+ * Global preference for displaying timestamps in the browser's local timezone
4
+ * instead of UTC.
5
+ *
6
+ * When `false` (default), all formatted timestamps stay in UTC — matching the
7
+ * historical Raindex webapp behaviour. When `true`, the same values are shown
8
+ * in the user's local timezone.
9
+ *
10
+ * Persisted under `settings.useLocalTime` so the preference survives reloads.
11
+ */
12
+ export const useLocalTime = cachedWritableStore("settings.useLocalTime", false, (value) => JSON.stringify(value), (serialized) => {
13
+ try {
14
+ return JSON.parse(serialized) === true;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainlanguage/ui-components",
3
- "version": "0.0.1-alpha.244",
3
+ "version": "0.0.1-alpha.246",
4
4
  "description": "A component library for building Svelte applications to be used with Raindex.",
5
5
  "license": "LicenseRef-DCL-1.0",
6
6
  "author": "Rain Open Source Software Ltd",
@@ -57,7 +57,7 @@
57
57
  "@fontsource/dm-sans": "5.1.0",
58
58
  "@imask/svelte": "7.6.1",
59
59
  "@observablehq/plot": "0.6.16",
60
- "@rainlanguage/raindex": "0.0.1-alpha.245",
60
+ "@rainlanguage/raindex": "0.0.1-alpha.246",
61
61
  "@reown/appkit": "1.6.4",
62
62
  "@reown/appkit-adapter-wagmi": "1.6.4",
63
63
  "@sentry/sveltekit": "7.120.0",