@runwisp/ui 0.10.0 → 0.12.0
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.
- package/package.json +4 -4
- package/src/lib/assets/runwisp-logo.svg +2 -2
- package/src/lib/components/LogConsole.svelte +518 -68
- package/src/lib/components/Popover.svelte +64 -31
- package/src/lib/components/dashboard/RunDetailPanel.svelte +779 -213
- package/src/lib/components/dashboard/RunFilterPopover.svelte +299 -0
- package/src/lib/components/dashboard/RunsList.svelte +441 -235
- package/src/lib/components/dashboard/cron-format.ts +4 -1
- package/src/lib/components/dashboard/run-filters.test.ts +299 -0
- package/src/lib/components/dashboard/run-filters.ts +384 -0
- package/src/lib/components/dashboard/run-helpers.test.ts +33 -0
- package/src/lib/components/dashboard/run-helpers.ts +20 -2
- package/src/lib/index.ts +21 -4
- package/src/lib/log-console/LogCache.svelte.ts +55 -1
- package/src/lib/log-console/types.ts +17 -0
- package/src/lib/theme-tokens.css +74 -62
- package/src/lib/theme.css +9 -5
- package/src/lib/utils/format.test.ts +38 -0
- package/src/lib/utils/format.ts +36 -0
- package/src/lib/utils/ticking-now.svelte.test.ts +45 -0
- package/src/lib/utils/ticking-now.svelte.ts +32 -0
- package/src/stories/assets/runwisp_c.svg +2 -2
package/src/lib/theme-tokens.css
CHANGED
|
@@ -20,73 +20,76 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
:root {
|
|
23
|
-
/* Primary — Wisp
|
|
24
|
-
|
|
25
|
-
--rw-color-wisp-
|
|
26
|
-
--rw-color-wisp-
|
|
27
|
-
--rw-color-wisp-
|
|
28
|
-
--rw-color-wisp-
|
|
29
|
-
--rw-color-wisp-
|
|
30
|
-
--rw-color-wisp-
|
|
31
|
-
--rw-color-wisp-
|
|
32
|
-
--rw-color-wisp-
|
|
33
|
-
--rw-color-wisp-
|
|
34
|
-
--rw-color-wisp-
|
|
23
|
+
/* Primary — Wisp Teal (brand = #0E7C86, the artifact's deep teal). Deeper
|
|
24
|
+
* and less cyan than Aurora (running/info) so the two stay distinct. */
|
|
25
|
+
--rw-color-wisp-50: oklch(0.97 0.02 205);
|
|
26
|
+
--rw-color-wisp-100: oklch(0.95 0.035 205);
|
|
27
|
+
--rw-color-wisp-200: oklch(0.89 0.055 204);
|
|
28
|
+
--rw-color-wisp-300: oklch(0.8 0.075 204);
|
|
29
|
+
--rw-color-wisp-400: oklch(0.68 0.088 203);
|
|
30
|
+
--rw-color-wisp-500: oklch(0.6 0.09 204);
|
|
31
|
+
--rw-color-wisp-600: oklch(0.536 0.089 205); /* ≈ #0E7C86 */
|
|
32
|
+
--rw-color-wisp-700: oklch(0.46 0.08 206);
|
|
33
|
+
--rw-color-wisp-800: oklch(0.38 0.066 207);
|
|
34
|
+
--rw-color-wisp-900: oklch(0.3 0.048 208);
|
|
35
|
+
--rw-color-wisp-950: oklch(0.2 0.034 210);
|
|
35
36
|
|
|
36
|
-
/* Accent — Aurora Teal */
|
|
37
|
-
--rw-color-aurora-50: oklch(0.97 0.02
|
|
38
|
-
--rw-color-aurora-100: oklch(0.94 0.
|
|
39
|
-
--rw-color-aurora-200: oklch(0.88 0.
|
|
40
|
-
--rw-color-aurora-300: oklch(0.78 0.
|
|
41
|
-
--rw-color-aurora-400: oklch(0.68 0.
|
|
42
|
-
--rw-color-aurora-500: oklch(0.58 0.
|
|
43
|
-
--rw-color-aurora-600: oklch(0.5 0.
|
|
44
|
-
--rw-color-aurora-700: oklch(0.42 0.
|
|
45
|
-
--rw-color-aurora-800: oklch(0.35 0.
|
|
46
|
-
--rw-color-aurora-900: oklch(0.28 0.
|
|
37
|
+
/* Accent — Aurora Teal (running status = #13A0A8, a cyan-leaning teal) */
|
|
38
|
+
--rw-color-aurora-50: oklch(0.97 0.02 192);
|
|
39
|
+
--rw-color-aurora-100: oklch(0.94 0.045 192);
|
|
40
|
+
--rw-color-aurora-200: oklch(0.88 0.075 190);
|
|
41
|
+
--rw-color-aurora-300: oklch(0.78 0.105 189);
|
|
42
|
+
--rw-color-aurora-400: oklch(0.68 0.115 190);
|
|
43
|
+
--rw-color-aurora-500: oklch(0.58 0.115 191);
|
|
44
|
+
--rw-color-aurora-600: oklch(0.5 0.105 192);
|
|
45
|
+
--rw-color-aurora-700: oklch(0.42 0.09 192);
|
|
46
|
+
--rw-color-aurora-800: oklch(0.35 0.07 192);
|
|
47
|
+
--rw-color-aurora-900: oklch(0.28 0.055 192);
|
|
47
48
|
|
|
48
|
-
/* Mist —
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
--rw-color-mist-
|
|
52
|
-
--rw-color-mist-
|
|
53
|
-
--rw-color-mist-
|
|
54
|
-
--rw-color-mist-
|
|
55
|
-
--rw-color-mist-
|
|
56
|
-
--rw-color-mist-
|
|
57
|
-
--rw-color-mist-
|
|
58
|
-
--rw-color-mist-
|
|
59
|
-
--rw-color-mist-
|
|
49
|
+
/* Mist — cool slate-blue neutrals (the artifact's #EDF1F4 / #E0E6EC /
|
|
50
|
+
* #18212E family). No purple tint; hue ~248, low chroma rising into the
|
|
51
|
+
* mids the way the design's slate does. */
|
|
52
|
+
--rw-color-mist-50: oklch(0.985 0.004 240);
|
|
53
|
+
--rw-color-mist-100: oklch(0.956 0.006 240); /* ≈ ground #EDF1F4 */
|
|
54
|
+
--rw-color-mist-200: oklch(0.922 0.01 248); /* ≈ line #E0E6EC */
|
|
55
|
+
--rw-color-mist-300: oklch(0.83 0.016 248);
|
|
56
|
+
--rw-color-mist-400: oklch(0.676 0.026 248); /* ≈ faint #8B99A7 */
|
|
57
|
+
--rw-color-mist-500: oklch(0.6 0.03 248);
|
|
58
|
+
--rw-color-mist-600: oklch(0.531 0.032 248); /* ≈ muted #5E6E7E */
|
|
59
|
+
--rw-color-mist-700: oklch(0.4 0.031 252);
|
|
60
|
+
--rw-color-mist-800: oklch(0.31 0.029 255);
|
|
61
|
+
--rw-color-mist-900: oklch(0.245 0.028 258); /* ≈ ink #18212E */
|
|
62
|
+
--rw-color-mist-950: oklch(0.17 0.022 258);
|
|
60
63
|
|
|
61
|
-
/* Success —
|
|
62
|
-
--rw-color-success-50: oklch(0.97 0.
|
|
63
|
-
--rw-color-success-100: oklch(0.94 0.
|
|
64
|
-
--rw-color-success-200: oklch(0.88 0.
|
|
65
|
-
--rw-color-success-300: oklch(0.78 0.
|
|
66
|
-
--rw-color-success-400: oklch(0.68 0.
|
|
67
|
-
--rw-color-success-500: oklch(0.58 0.
|
|
68
|
-
--rw-color-success-600: oklch(0.5 0.
|
|
69
|
-
--rw-color-success-700: oklch(0.42 0.
|
|
64
|
+
/* Success — Field Green (success status = #1F9D57, a true grass green) */
|
|
65
|
+
--rw-color-success-50: oklch(0.97 0.02 150);
|
|
66
|
+
--rw-color-success-100: oklch(0.94 0.04 150);
|
|
67
|
+
--rw-color-success-200: oklch(0.88 0.07 150);
|
|
68
|
+
--rw-color-success-300: oklch(0.78 0.11 150);
|
|
69
|
+
--rw-color-success-400: oklch(0.68 0.14 150);
|
|
70
|
+
--rw-color-success-500: oklch(0.58 0.145 150);
|
|
71
|
+
--rw-color-success-600: oklch(0.5 0.13 150);
|
|
72
|
+
--rw-color-success-700: oklch(0.42 0.11 150);
|
|
70
73
|
|
|
71
|
-
/* Danger —
|
|
72
|
-
--rw-color-danger-50: oklch(0.97 0.
|
|
73
|
-
--rw-color-danger-100: oklch(0.94 0.
|
|
74
|
-
--rw-color-danger-200: oklch(0.88 0.
|
|
75
|
-
--rw-color-danger-300: oklch(0.78 0.
|
|
76
|
-
--rw-color-danger-400: oklch(0.68 0.
|
|
77
|
-
--rw-color-danger-500: oklch(0.58 0.
|
|
78
|
-
--rw-color-danger-600: oklch(0.5 0.
|
|
79
|
-
--rw-color-danger-700: oklch(0.42 0.
|
|
74
|
+
/* Danger — Signal Red (failed status = #D6453D, a warm tomato red) */
|
|
75
|
+
--rw-color-danger-50: oklch(0.97 0.016 27);
|
|
76
|
+
--rw-color-danger-100: oklch(0.94 0.03 27);
|
|
77
|
+
--rw-color-danger-200: oklch(0.88 0.06 27);
|
|
78
|
+
--rw-color-danger-300: oklch(0.78 0.1 27);
|
|
79
|
+
--rw-color-danger-400: oklch(0.68 0.14 27);
|
|
80
|
+
--rw-color-danger-500: oklch(0.58 0.175 27);
|
|
81
|
+
--rw-color-danger-600: oklch(0.5 0.17 27);
|
|
82
|
+
--rw-color-danger-700: oklch(0.42 0.145 27);
|
|
80
83
|
|
|
81
|
-
/* Warning */
|
|
82
|
-
--rw-color-warning-50: oklch(0.
|
|
83
|
-
--rw-color-warning-100: oklch(0.
|
|
84
|
-
--rw-color-warning-200: oklch(0.9 0.
|
|
85
|
-
--rw-color-warning-300: oklch(0.
|
|
86
|
-
--rw-color-warning-400: oklch(0.
|
|
87
|
-
--rw-color-warning-500: oklch(0.
|
|
88
|
-
--rw-color-warning-600: oklch(0.
|
|
89
|
-
--rw-color-warning-700: oklch(0.
|
|
84
|
+
/* Warning — Ember Amber (stopped status = #D98324, a warm orange-amber) */
|
|
85
|
+
--rw-color-warning-50: oklch(0.97 0.03 78);
|
|
86
|
+
--rw-color-warning-100: oklch(0.94 0.07 74);
|
|
87
|
+
--rw-color-warning-200: oklch(0.9 0.11 72);
|
|
88
|
+
--rw-color-warning-300: oklch(0.84 0.14 70);
|
|
89
|
+
--rw-color-warning-400: oklch(0.77 0.155 67);
|
|
90
|
+
--rw-color-warning-500: oklch(0.69 0.155 64);
|
|
91
|
+
--rw-color-warning-600: oklch(0.6 0.14 62);
|
|
92
|
+
--rw-color-warning-700: oklch(0.52 0.12 60);
|
|
90
93
|
|
|
91
94
|
/* ANSI console palette — 16-color map for the log console, tuned for
|
|
92
95
|
* its always-dark surface (no .dark override needed). Hues follow the
|
|
@@ -109,6 +112,15 @@
|
|
|
109
112
|
--rw-ansi-bright-cyan: var(--rw-color-aurora-300);
|
|
110
113
|
--rw-ansi-bright-white: var(--rw-color-mist-50);
|
|
111
114
|
|
|
115
|
+
/* Console device surface — the log console is a "black box" instrument
|
|
116
|
+
* with its own fixed palette that never follows the light/dark UI theme.
|
|
117
|
+
* Exact values from the approved execution-detail design. */
|
|
118
|
+
--rw-con-bg: #0e141b; /* body / gutter backing */
|
|
119
|
+
--rw-con-panel: #11181f; /* toolbar + footer chrome */
|
|
120
|
+
--rw-con-text: #c3cfda; /* default log text */
|
|
121
|
+
--rw-con-dim: #6c7d8c; /* footer + muted readouts */
|
|
122
|
+
--rw-con-gutter: #3c4a58; /* line numbers + sentinel rule */
|
|
123
|
+
|
|
112
124
|
/* Light-mode semantic tokens (default) */
|
|
113
125
|
/* Surfaces */
|
|
114
126
|
--rw-surface: var(--rw-color-mist-50);
|
package/src/lib/theme.css
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
/* Copyright (c) PoppyCake, s.r.o. SPDX-License-Identifier: Apache-2.0 */
|
|
2
2
|
|
|
3
|
-
@import "tailwindcss";
|
|
4
|
-
@plugin "@tailwindcss/forms";
|
|
5
|
-
@plugin "@tailwindcss/typography";
|
|
6
|
-
|
|
7
3
|
/* RunWisp Design System — Tailwind v4 entrypoint.
|
|
8
4
|
*
|
|
9
5
|
* Raw OKLch palette + semantic tokens live in ./theme-tokens.css (plain CSS),
|
|
10
6
|
* so apps/docs (Starlight, no Tailwind pipeline) can consume the same source.
|
|
11
7
|
* This file republishes those tokens under Tailwind's --color-* namespace so
|
|
12
8
|
* utilities like `bg-wisp-500`, `text-surface`, etc. generate.
|
|
9
|
+
*
|
|
10
|
+
* All @import rules precede the @plugin directives so the stylesheet stays
|
|
11
|
+
* valid CSS (an @import must come before any other at-rule besides @charset
|
|
12
|
+
* / @layer); Tailwind's @plugin registrations are order-independent here.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
@import "tailwindcss";
|
|
15
16
|
@import "./theme-tokens.css";
|
|
16
|
-
|
|
17
17
|
@import "@fontsource/tasa-orbiter/400.css";
|
|
18
18
|
@import "@fontsource/tasa-orbiter/500.css";
|
|
19
19
|
@import "@fontsource/tasa-orbiter/600.css";
|
|
20
20
|
@import "@fontsource/tasa-orbiter/700.css";
|
|
21
21
|
|
|
22
|
+
@plugin "@tailwindcss/forms";
|
|
23
|
+
@plugin "@tailwindcss/typography";
|
|
24
|
+
|
|
22
25
|
@theme {
|
|
23
26
|
/* Palette aliases — Tailwind utilities (bg-wisp-500, text-mist-900, …)
|
|
24
27
|
* resolve through to --rw-color-* declared in theme-tokens.css. */
|
|
@@ -197,6 +200,7 @@
|
|
|
197
200
|
--color-warning-soft-text: var(--rw-warning-soft-text);
|
|
198
201
|
/* Info */
|
|
199
202
|
--color-info: var(--rw-info);
|
|
203
|
+
--color-info-surface: var(--rw-info);
|
|
200
204
|
--color-info-hover: var(--rw-info-hover);
|
|
201
205
|
--color-on-info: var(--rw-on-info);
|
|
202
206
|
--color-info-soft: var(--rw-info-soft);
|
|
@@ -4,11 +4,21 @@
|
|
|
4
4
|
import { describe, expect, it } from "vitest";
|
|
5
5
|
import {
|
|
6
6
|
formatBytes,
|
|
7
|
+
formatCalendarDate,
|
|
8
|
+
formatClockTime,
|
|
9
|
+
formatDayMonth,
|
|
7
10
|
formatDuration,
|
|
8
11
|
formatRelativeTime,
|
|
9
12
|
formatRelativeTimeWithAbsolute,
|
|
13
|
+
formatTimeHM,
|
|
10
14
|
} from "./format.js";
|
|
11
15
|
|
|
16
|
+
// The clock/date helpers below delegate to toLocaleTimeString / toLocaleDateString,
|
|
17
|
+
// whose exact rendering is locale- and timezone-dependent. We assert the
|
|
18
|
+
// locale-invariant *shape* of each format (how many numeric groups, year present
|
|
19
|
+
// or not) rather than a literal string, mirroring formatRelativeTimeWithAbsolute.
|
|
20
|
+
const numericGroups = (s: string): number => (s.match(/\d+/g) ?? []).length;
|
|
21
|
+
|
|
12
22
|
describe("formatBytes", () => {
|
|
13
23
|
it("formats 0 bytes", () => {
|
|
14
24
|
expect(formatBytes(0)).toBe("0 B");
|
|
@@ -122,3 +132,31 @@ describe("formatRelativeTime", () => {
|
|
|
122
132
|
);
|
|
123
133
|
});
|
|
124
134
|
});
|
|
135
|
+
|
|
136
|
+
describe("formatClockTime", () => {
|
|
137
|
+
it("renders three numeric groups (hours, minutes, seconds)", () => {
|
|
138
|
+
// 24-hour h:m:s — the seconds component is what sets it apart from formatTimeHM.
|
|
139
|
+
expect(numericGroups(formatClockTime("2026-06-22T17:15:02Z"))).toBe(3);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("formatTimeHM", () => {
|
|
144
|
+
it("renders two numeric groups (hours, minutes) — no seconds", () => {
|
|
145
|
+
expect(numericGroups(formatTimeHM("2026-06-22T17:15:02Z"))).toBe(2);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe("formatCalendarDate", () => {
|
|
150
|
+
it("includes the four-digit year", () => {
|
|
151
|
+
// Midday UTC keeps the calendar year stable across every timezone.
|
|
152
|
+
expect(formatCalendarDate("2026-06-22T12:00:00Z")).toContain("2026");
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("formatDayMonth", () => {
|
|
157
|
+
it("includes a day digit but omits the year", () => {
|
|
158
|
+
const r = formatDayMonth("2026-06-22T12:00:00Z");
|
|
159
|
+
expect(r).toMatch(/\d/);
|
|
160
|
+
expect(r).not.toContain("2026");
|
|
161
|
+
});
|
|
162
|
+
});
|
package/src/lib/utils/format.ts
CHANGED
|
@@ -53,6 +53,42 @@ export function formatDateTime(dateStr: string): string {
|
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/** Wall-clock time of day, seconds included, 24-hour — e.g. "17:15:02". */
|
|
57
|
+
export function formatClockTime(dateStr: string): string {
|
|
58
|
+
return new Date(dateStr).toLocaleTimeString(undefined, {
|
|
59
|
+
hour: "2-digit",
|
|
60
|
+
minute: "2-digit",
|
|
61
|
+
second: "2-digit",
|
|
62
|
+
hour12: false,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Calendar date without the time — e.g. "22 Jun 2026". */
|
|
67
|
+
export function formatCalendarDate(dateStr: string): string {
|
|
68
|
+
return new Date(dateStr).toLocaleDateString(undefined, {
|
|
69
|
+
year: "numeric",
|
|
70
|
+
month: "short",
|
|
71
|
+
day: "numeric",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Time of day, no seconds, 24-hour — e.g. "17:15". */
|
|
76
|
+
export function formatTimeHM(dateStr: string): string {
|
|
77
|
+
return new Date(dateStr).toLocaleTimeString(undefined, {
|
|
78
|
+
hour: "2-digit",
|
|
79
|
+
minute: "2-digit",
|
|
80
|
+
hour12: false,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Day and month, no year — e.g. "22 Jun". */
|
|
85
|
+
export function formatDayMonth(dateStr: string): string {
|
|
86
|
+
return new Date(dateStr).toLocaleDateString(undefined, {
|
|
87
|
+
month: "short",
|
|
88
|
+
day: "numeric",
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
56
92
|
export function formatDuration(ms: number): string {
|
|
57
93
|
if (ms < 1000) return String(ms) + "ms";
|
|
58
94
|
const s = Math.floor(ms / 1000);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { TickingNow } from "./ticking-now.svelte.js";
|
|
6
|
+
|
|
7
|
+
describe("TickingNow", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.useFakeTimers();
|
|
10
|
+
vi.setSystemTime(new Date("2024-06-15T12:00:00.000Z"));
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
vi.useRealTimers();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("reports the current time before it starts ticking", () => {
|
|
17
|
+
const clock = new TickingNow();
|
|
18
|
+
expect(clock.now.getTime()).toBe(new Date("2024-06-15T12:00:00.000Z").getTime());
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("advances on its default 30s interval", () => {
|
|
22
|
+
const clock = new TickingNow();
|
|
23
|
+
const stop = clock.start();
|
|
24
|
+
vi.advanceTimersByTime(30_000);
|
|
25
|
+
expect(clock.now.getTime()).toBe(new Date("2024-06-15T12:00:30.000Z").getTime());
|
|
26
|
+
stop();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("advances on a custom interval", () => {
|
|
30
|
+
const clock = new TickingNow(1000);
|
|
31
|
+
const stop = clock.start();
|
|
32
|
+
vi.advanceTimersByTime(3000);
|
|
33
|
+
expect(clock.now.getTime()).toBe(new Date("2024-06-15T12:00:03.000Z").getTime());
|
|
34
|
+
stop();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("stops advancing once the cleanup is called", () => {
|
|
38
|
+
const clock = new TickingNow(1000);
|
|
39
|
+
const stop = clock.start();
|
|
40
|
+
vi.advanceTimersByTime(1000);
|
|
41
|
+
stop();
|
|
42
|
+
vi.advanceTimersByTime(9000);
|
|
43
|
+
expect(clock.now.getTime()).toBe(new Date("2024-06-15T12:00:01.000Z").getTime());
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: PoppyCake, s.r.o.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { SvelteDate } from "svelte/reactivity";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_TICK_INTERVAL_MS = 30_000;
|
|
7
|
+
|
|
8
|
+
// TickingNow is a reactive clock that advances on a fixed interval, so
|
|
9
|
+
// relative-time labels re-render without waiting for a data event.
|
|
10
|
+
// Call start() inside an $effect and return its cleanup.
|
|
11
|
+
export class TickingNow {
|
|
12
|
+
#ms = $state(Date.now());
|
|
13
|
+
readonly #intervalMs: number;
|
|
14
|
+
|
|
15
|
+
constructor(intervalMs: number = DEFAULT_TICK_INTERVAL_MS) {
|
|
16
|
+
this.#intervalMs = intervalMs;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
start(): () => void {
|
|
20
|
+
this.#ms = Date.now();
|
|
21
|
+
const timer = setInterval(() => {
|
|
22
|
+
this.#ms = Date.now();
|
|
23
|
+
}, this.#intervalMs);
|
|
24
|
+
return () => {
|
|
25
|
+
clearInterval(timer);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get now(): Date {
|
|
30
|
+
return new SvelteDate(this.#ms);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<svg width="512" height="512" version="1.1" viewBox="0 0 5120 5120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
2
2
|
<defs>
|
|
3
3
|
<linearGradient id="b">
|
|
4
|
-
<stop stop-color="#
|
|
5
|
-
<stop stop-color="#
|
|
4
|
+
<stop stop-color="#15a0a8" offset="0"/>
|
|
5
|
+
<stop stop-color="#0e7c86" offset="1"/>
|
|
6
6
|
</linearGradient>
|
|
7
7
|
<radialGradient id="a" cx="2375.3" cy="3240.7" r="1333.9" gradientTransform="matrix(1.01 -.012494 .026846 2.1701 -110.74 -3341.2)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
|
|
8
8
|
</defs>
|