From b27bb47007778d2526f3137fa1bc4568d1b9b59e Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 9 Oct 2023 14:35:21 +0100 Subject: [PATCH] Notification summary --- packages/app/package.json | 2 + packages/app/public/icons.svg | 11 +- packages/app/src/Cache/index.ts | 10 +- packages/app/src/Element/Event/Reactions.css | 2 +- packages/app/src/Element/Tabs.css | 2 +- packages/app/src/Element/Tabs.tsx | 2 +- packages/app/src/Pages/Notifications.css | 26 ++ packages/app/src/Pages/Notifications.tsx | 172 ++++++++- packages/app/src/SnortUtils/index.ts | 4 + packages/app/src/index.css | 47 ++- packages/app/src/index.tsx | 3 +- packages/app/src/lang.json | 10 + packages/app/src/translations/en.json | 5 +- packages/system-web/package.json | 25 ++ .../cache/db.ts => system-web/src/index.ts} | 16 +- packages/system-web/tsconfig.json | 18 + packages/system/src/cache/events.ts | 7 +- packages/system/src/cache/index.ts | 14 +- packages/system/src/cache/relay-metric.ts | 8 +- packages/system/src/cache/user-metadata.ts | 12 +- packages/system/src/cache/user-relays.ts | 8 +- packages/system/src/nostr-system.ts | 13 +- yarn.lock | 353 +++++++++++++++++- 23 files changed, 710 insertions(+), 60 deletions(-) create mode 100644 packages/system-web/package.json rename packages/{system/src/cache/db.ts => system-web/src/index.ts} (61%) create mode 100644 packages/system-web/tsconfig.json diff --git a/packages/app/package.json b/packages/app/package.json index add96928..c6820064 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -13,6 +13,7 @@ "@snort/system": "workspace:*", "@snort/system-react": "workspace:*", "@snort/system-wasm": "workspace:*", + "@snort/system-web": "workspace:*", "@szhsin/react-menu": "^3.3.1", "@types/use-sync-external-store": "^0.0.4", "@void-cat/api": "^1.0.4", @@ -30,6 +31,7 @@ "react-router-dom": "^6.5.0", "react-textarea-autosize": "^8.4.0", "react-twitter-embed": "^4.0.4", + "recharts": "^2.8.0", "use-long-press": "^3.2.0", "use-sync-external-store": "^1.2.0", "uuid": "^9.0.0", diff --git a/packages/app/public/icons.svg b/packages/app/public/icons.svg index f7e27883..ae164d36 100644 --- a/packages/app/public/icons.svg +++ b/packages/app/public/icons.svg @@ -339,9 +339,14 @@ - - - + + + + + + + + \ No newline at end of file diff --git a/packages/app/src/Cache/index.ts b/packages/app/src/Cache/index.ts index 63c1a0f7..ee9a9522 100644 --- a/packages/app/src/Cache/index.ts +++ b/packages/app/src/Cache/index.ts @@ -1,4 +1,6 @@ import { UserProfileCache, UserRelaysCache, RelayMetricCache } from "@snort/system"; +import { SnortSystemDb } from "@snort/system-web"; + import { EventInteractionCache } from "./EventInteractionCache"; import { ChatCache } from "./ChatCache"; import { Payments } from "./PaymentsCache"; @@ -6,9 +8,11 @@ import { GiftWrapCache } from "./GiftWrapCache"; import { NotificationsCache } from "./Notifications"; import { FollowsFeedCache } from "./FollowsFeed"; -export const UserCache = new UserProfileCache(); -export const UserRelays = new UserRelaysCache(); -export const RelayMetrics = new RelayMetricCache(); +export const SystemDb = new SnortSystemDb(); +export const UserCache = new UserProfileCache(SystemDb.users); +export const UserRelays = new UserRelaysCache(SystemDb.userRelays); +export const RelayMetrics = new RelayMetricCache(SystemDb.relayMetrics); + export const Chats = new ChatCache(); export const PaymentsCache = new Payments(); export const InteractionCache = new EventInteractionCache(); diff --git a/packages/app/src/Element/Event/Reactions.css b/packages/app/src/Element/Event/Reactions.css index 1ce3ca5f..f6612b25 100644 --- a/packages/app/src/Element/Event/Reactions.css +++ b/packages/app/src/Element/Event/Reactions.css @@ -1,6 +1,6 @@ .reactions-modal .modal-body { padding: 24px 32px; - background-color: #1b1b1b; + background-color: var(--gray-superdark); border-radius: 16px; position: relative; min-height: 33vh; diff --git a/packages/app/src/Element/Tabs.css b/packages/app/src/Element/Tabs.css index 9ee1a86c..d0e8b380 100644 --- a/packages/app/src/Element/Tabs.css +++ b/packages/app/src/Element/Tabs.css @@ -34,7 +34,7 @@ border-radius: 100px; font-weight: 600; font-size: 16px; - padding: 10px 16px; + padding: 6px 12px; display: flex; align-items: center; justify-items: center; diff --git a/packages/app/src/Element/Tabs.tsx b/packages/app/src/Element/Tabs.tsx index 427610f3..318afefc 100644 --- a/packages/app/src/Element/Tabs.tsx +++ b/packages/app/src/Element/Tabs.tsx @@ -31,7 +31,7 @@ export const TabElement = ({ t, tab, setTab }: TabElementProps) => { const Tabs = ({ tabs, tab, setTab }: TabsProps) => { const horizontalScroll = useHorizontalScroll(); return ( -
+
{tabs.map(t => ( ))} diff --git a/packages/app/src/Pages/Notifications.css b/packages/app/src/Pages/Notifications.css index e4ab399a..b5bb6904 100644 --- a/packages/app/src/Pages/Notifications.css +++ b/packages/app/src/Pages/Notifications.css @@ -52,3 +52,29 @@ max-width: 100%; max-height: 300px; /* Cap images in notifications to 300px height */ } + +.summary-icon { + padding: 4px; + border-radius: 8px; + display: flex; + align-items: center; + cursor: pointer; + color: var(--gray-light) !important; +} + +.summary-icon:not(.active):hover { + background-color: var(--gray-dark); +} + +.summary-icon.active { + background: rgba(255, 255, 255, 0.1); +} + +.summary-tooltip { + display: flex; + gap: 12px; + padding: 12px 16px; + border-radius: 16px; + background: var(--gray-superdark); + box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.05); +} diff --git a/packages/app/src/Pages/Notifications.tsx b/packages/app/src/Pages/Notifications.tsx index a3784007..fbbc835e 100644 --- a/packages/app/src/Pages/Notifications.tsx +++ b/packages/app/src/Pages/Notifications.tsx @@ -1,16 +1,17 @@ import "./Notifications.css"; -import { useEffect, useMemo, useSyncExternalStore } from "react"; +import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react"; import { EventExt, EventKind, NostrEvent, NostrLink, NostrPrefix, TaggedNostrEvent, parseZap } from "@snort/system"; -import { unwrap } from "@snort/shared"; +import { unixNow, unwrap } from "@snort/shared"; import { useUserProfile } from "@snort/system-react"; import { useInView } from "react-intersection-observer"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router-dom"; +import { Bar, BarChart, Tooltip, XAxis, YAxis } from "recharts"; import useLogin from "Hooks/useLogin"; import { markNotificationsRead } from "Login"; import { Notifications, UserCache } from "Cache"; -import { dedupe, findTag, orderDescending } from "SnortUtils"; +import { dedupe, findTag, orderAscending, orderDescending } from "SnortUtils"; import Icon from "Icons/Icon"; import ProfileImage from "Element/User/ProfileImage"; import useModeration from "Hooks/useModeration"; @@ -20,6 +21,8 @@ import { formatShort } from "Number"; import { LiveEvent } from "Element/LiveEvent"; import ProfilePreview from "Element/User/ProfilePreview"; import { getDisplayName } from "Element/User/DisplayName"; +import { Day } from "Const"; +import Tabs, { Tab } from "Element/Tabs"; function notificationContext(ev: TaggedNostrEvent) { switch (ev.kind) { @@ -93,12 +96,175 @@ export default function NotificationsPage({ onClick }: { onClick?: (link: NostrL return (
+ + {login.publicKey && [...timeGrouped.entries()].map(([k, g]) => )}
); } +interface StatSlot { + time: string; + reactions: number; + reposts: number; + quotes: number; + mentions: number; + zaps: number; +} + +const enum NotificationSummaryPeriod { + Daily, + Weekly, +} + +const enum NotificationSummaryFilter { + Reactions = 1, + Reposts = 2, + Mentions = 4, + Zaps = 8, + All = 255, +} + +function NotificationSummary({ evs }: { evs: Array }) { + const ref = useRef(null); + const [period, setPeriod] = useState(NotificationSummaryPeriod.Daily); + const [filter, setFilter] = useState(NotificationSummaryFilter.All); + + const periodTabs = [ + { + value: NotificationSummaryPeriod.Daily, + text: , + }, + { + value: NotificationSummaryPeriod.Weekly, + text: , + }, + ] as Array; + + const hasFlag = (v: number, f: NotificationSummaryFilter) => { + return (v & f) > 0; + }; + + const getWeek = (d: Date) => { + const onejan = new Date(d.getFullYear(), 0, 1); + const today = new Date(d.getFullYear(), d.getMonth(), d.getDate()); + const dayOfYear = (today.getTime() - onejan.getTime() + 86400000) / 86400000; + return Math.ceil(dayOfYear / 7); + }; + + const stats = useMemo(() => { + return orderAscending(evs) + .filter(a => (period === NotificationSummaryPeriod.Daily ? a.created_at > unixNow() - 14 * Day : true)) + .reduce( + (acc, v) => { + const date = new Date(v.created_at * 1000); + const key = + period === NotificationSummaryPeriod.Daily + ? `${date.getMonth() + 1}/${date.getDate()}` + : getWeek(date).toString(); + acc[key] ??= { + time: key, + reactions: 0, + reposts: 0, + quotes: 0, + mentions: 0, + zaps: 0, + }; + + if (v.kind === EventKind.Reaction) { + acc[key].reactions++; + } else if (v.kind === EventKind.Repost) { + acc[key].reposts++; + } else if (v.kind === EventKind.ZapReceipt) { + acc[key].zaps++; + } + if (v.kind === EventKind.TextNote) { + acc[key].mentions++; + } + + return acc; + }, + {} as Record, + ); + }, [evs, period]); + + const filterIcon = (f: NotificationSummaryFilter, icon: string, iconActiveClass?: string) => { + const active = hasFlag(filter, f); + return ( +
setFilter(v => v ^ f)}> + +
+ ); + }; + + return ( +
+
+

+ +

+
+ {filterIcon(NotificationSummaryFilter.Reactions, "heart-solid", "text-heart")} + {filterIcon(NotificationSummaryFilter.Zaps, "zap-solid", "text-zap")} + {filterIcon(NotificationSummaryFilter.Reposts, "reverse-left", "text-repost")} + {filterIcon(NotificationSummaryFilter.Mentions, "at-sign", "text-mention")} +
+
+ a.value === period))} setTab={t => setPeriod(t.value)} /> +
+ + + + {hasFlag(filter, NotificationSummaryFilter.Reactions) && ( + + )} + {hasFlag(filter, NotificationSummaryFilter.Reposts) && ( + + )} + {hasFlag(filter, NotificationSummaryFilter.Mentions) && ( + + )} + {hasFlag(filter, NotificationSummaryFilter.Zaps) && } + { + if (active && payload && payload.length) { + return ( +
+
+ + {formatShort(payload.find(a => a.name === "reactions")?.value as number)} +
+
+ + {formatShort(payload.find(a => a.name === "zaps")?.value as number)} +
+
+ + {formatShort(payload.find(a => a.name === "reposts")?.value as number)} +
+
+ + {formatShort(payload.find(a => a.name === "mentions")?.value as number)} +
+
+ ); + } + return null; + }} + /> +
+
+
+ ); +} + function NotificationGroup({ evs, onClick }: { evs: Array; onClick?: (link: NostrLink) => void }) { const { ref, inView } = useInView({ triggerOnce: true }); const { formatMessage } = useIntl(); diff --git a/packages/app/src/SnortUtils/index.ts b/packages/app/src/SnortUtils/index.ts index 65062b2a..52018793 100644 --- a/packages/app/src/SnortUtils/index.ts +++ b/packages/app/src/SnortUtils/index.ts @@ -328,6 +328,10 @@ export function orderDescending(arr: Array) { return arr.sort((a, b) => (b.created_at > a.created_at ? 1 : -1)); } +export function orderAscending(arr: Array) { + return arr.sort((a, b) => (b.created_at > a.created_at ? -1 : 1)); +} + export interface Magnet { dn?: string | string[]; tr?: string | string[]; diff --git a/packages/app/src/index.css b/packages/app/src/index.css index a038ce28..456f4bba 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -15,6 +15,8 @@ --live: #f83838; --heart: #ef4444; --zap: #ff710a; + --mention: #961ee1; + --repost: #1ecbe1; --gray-superlight: #eee; --bg-secondary: #2a2a2a; --gray-light: #999; @@ -137,6 +139,10 @@ code { border: 1px solid var(--border-color); } +.bb { + border-bottom: 1px solid var(--border-color); +} + .bg-primary { background: var(--primary-gradient); } @@ -149,6 +155,10 @@ code { padding: 12px 16px; } +.p4 { + padding: 4px; +} + .p24 { padding: 24px; } @@ -619,6 +629,38 @@ div.form-col { background-color: var(--success); } +.bg-zap { + background-color: var(--zap); +} + +.bg-heart { + background-color: var(--heart); +} + +.bg-mention { + background-color: var(--mention); +} + +.bg-repost { + background-color: var(--repost); +} + +.text-zap { + color: var(--zap); +} + +.text-heart { + color: var(--heart); +} + +.text-mention { + color: var(--mention); +} + +.text-repost { + color: var(--repost); +} + .tweet { display: flex; align-items: center; @@ -664,11 +706,6 @@ div.form-col { font-weight: 600; font-size: 26px; line-height: 36px; - margin: 12px 0 0 0; -} - -.main-content .h4 { - margin-bottom: 25px; } .main-content .profile-preview { diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 35b5d009..d63bad18 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -42,7 +42,7 @@ import { SubscribeRoutes } from "Pages/subscribe"; import ZapPoolPage from "Pages/ZapPool"; import DebugPage from "Pages/Debug"; import { db } from "Db"; -import { preload, RelayMetrics, UserCache, UserRelays } from "Cache"; +import { preload, RelayMetrics, SystemDb, UserCache, UserRelays } from "Cache"; import { LoginStore } from "Login"; import { SnortDeckLayout } from "Pages/DeckLayout"; import FreeNostrAddressPage from "./Pages/FreeNostrAddressPage"; @@ -77,6 +77,7 @@ export const System = new NostrSystem({ profileCache: UserCache, relayMetrics: RelayMetrics, queryOptimizer: WasmQueryOptimizer, + db: SystemDb, authHandler: async (c, r) => { const { id } = LoginStore.snapshot(); const pub = LoginStore.getPublisher(id); diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index c6ad88b5..db331317 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -42,6 +42,9 @@ "/Xf4UW": { "defaultMessage": "Send anonymous usage metrics" }, + "/clOBU": { + "defaultMessage": "Weekly" + }, "/d6vEc": { "defaultMessage": "Make your profile easier to find and share" }, @@ -704,6 +707,10 @@ "PCSt5T": { "defaultMessage": "Preferences" }, + "PJeJFc": { + "defaultMessage": "Summary", + "description": "Notifications summary" + }, "PLSbmL": { "defaultMessage": "Your mnemonic phrase" }, @@ -1508,5 +1515,8 @@ }, "zwb6LR": { "defaultMessage": "Mint: {url}" + }, + "zxvhnE": { + "defaultMessage": "Daily" } } diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index bfb58612..70111264 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -13,6 +13,7 @@ "/PCavi": "Public", "/RD0e2": "Nostr uses digital signature technology to provide tamper proof notes which can safely be replicated to many relays to provide redundant storage of your content.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "Make your profile easier to find and share", "/n5KSF": "{n} ms", "00LcfG": "Load more", @@ -231,6 +232,7 @@ "P7FD0F": "System (Default)", "P7nJT9": "Total today (UTC): {amount} sats", "PCSt5T": "Preferences", + "PJeJFc": "Summary", "PLSbmL": "Your mnemonic phrase", "PaN7t3": "Preview on {site}", "PamNxw": "Unknown file header: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "You're ready!", "zonsdq": "Failed to load LNURL service", "zvCDao": "Automatically show latest notes", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/system-web/package.json b/packages/system-web/package.json new file mode 100644 index 00000000..08db3588 --- /dev/null +++ b/packages/system-web/package.json @@ -0,0 +1,25 @@ +{ + "name": "@snort/system-web", + "version": "1.0.0", + "description": "Web based components @snort/system", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "repository": "https://git.v0l.io/Kieran/snort", + "author": "Kieran", + "license": "MIT", + "scripts": { + "build": "rm -rf dist && tsc" + }, + "files": [ + "src", + "dist" + ], + "dependencies": { + "@snort/shared": "^1.0.6", + "@snort/system": "^1.0.21", + "dexie": "^3.2.4" + }, + "devDependencies": { + "typescript": "^5.2.2" + } +} diff --git a/packages/system/src/cache/db.ts b/packages/system-web/src/index.ts similarity index 61% rename from packages/system/src/cache/db.ts rename to packages/system-web/src/index.ts index 108c9cbb..1f3366d2 100644 --- a/packages/system/src/cache/db.ts +++ b/packages/system-web/src/index.ts @@ -1,6 +1,5 @@ -import { DexieLike, DexieTableLike } from "@snort/shared"; -import { MetadataCache, RelayMetrics, UsersRelays } from "."; -import { NostrEvent } from "../nostr"; +import { NostrEvent, MetadataCache, RelayMetrics, UsersRelays } from "@snort/system"; +import Dexie, { Table } from "dexie"; const NAME = "snort-system"; const VERSION = 2; @@ -12,13 +11,12 @@ const STORES = { events: "++id, pubkey, created_at", }; -export class SnortSystemDb extends DexieLike { +export class SnortSystemDb extends Dexie { ready = false; - users!: DexieTableLike; - relayMetrics!: DexieTableLike; - userRelays!: DexieTableLike; - events!: DexieTableLike; - dms!: DexieTableLike; + users!: Table; + relayMetrics!: Table; + userRelays!: Table; + events!: Table; constructor() { super(NAME); diff --git a/packages/system-web/tsconfig.json b/packages/system-web/tsconfig.json new file mode 100644 index 00000000..70340f1f --- /dev/null +++ b/packages/system-web/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "baseUrl": "src", + "target": "ESNext", + "moduleResolution": "Bundler", + "esModuleInterop": true, + "noImplicitOverride": true, + "jsx": "react-jsx", + "strict": true, + "declaration": true, + "declarationMap": true, + "inlineSourceMap": true, + "outDir": "dist", + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "files": ["src/index.ts"] +} diff --git a/packages/system/src/cache/events.ts b/packages/system/src/cache/events.ts index 59457cbe..f41ddd5b 100644 --- a/packages/system/src/cache/events.ts +++ b/packages/system/src/cache/events.ts @@ -1,10 +1,9 @@ import { NostrEvent } from "nostr"; -import { db } from "."; -import { FeedCache } from "@snort/shared"; +import { DexieTableLike, FeedCache } from "@snort/shared"; export class EventsCache extends FeedCache { - constructor() { - super("EventsCache", db.events); + constructor(table?: DexieTableLike) { + super("EventsCache", table); } key(of: NostrEvent): string { diff --git a/packages/system/src/cache/index.ts b/packages/system/src/cache/index.ts index 5065a740..f8870e9f 100644 --- a/packages/system/src/cache/index.ts +++ b/packages/system/src/cache/index.ts @@ -1,8 +1,5 @@ import { FullRelaySettings, HexKey, NostrEvent, UserMetadata } from ".."; -import { hexToBech32, unixNowMs } from "@snort/shared"; -import { SnortSystemDb } from "./db"; - -export const db = new SnortSystemDb(); +import { hexToBech32, unixNowMs, DexieTableLike } from "@snort/shared"; export interface MetadataCache extends UserMetadata { /** @@ -71,3 +68,12 @@ export function mapEventToProfile(ev: NostrEvent) { console.error("Failed to parse JSON", ev, e); } } + +export interface SnortSystemDb { + users: DexieTableLike; + relayMetrics: DexieTableLike; + userRelays: DexieTableLike; + events: DexieTableLike; + + isAvailable(): Promise; +} diff --git a/packages/system/src/cache/relay-metric.ts b/packages/system/src/cache/relay-metric.ts index 2eb735bd..dd697c0a 100644 --- a/packages/system/src/cache/relay-metric.ts +++ b/packages/system/src/cache/relay-metric.ts @@ -1,9 +1,9 @@ -import { db, RelayMetrics } from "."; -import { FeedCache } from "@snort/shared"; +import { RelayMetrics } from "."; +import { DexieTableLike, FeedCache } from "@snort/shared"; export class RelayMetricCache extends FeedCache { - constructor() { - super("RelayMetrics", db.relayMetrics); + constructor(table?: DexieTableLike) { + super("RelayMetrics", table); } key(of: RelayMetrics): string { diff --git a/packages/system/src/cache/user-metadata.ts b/packages/system/src/cache/user-metadata.ts index 5693a54b..496ef7f9 100644 --- a/packages/system/src/cache/user-metadata.ts +++ b/packages/system/src/cache/user-metadata.ts @@ -1,12 +1,12 @@ -import { db, MetadataCache } from "."; -import { fetchNip05Pubkey, FeedCache, LNURL } from "@snort/shared"; +import { MetadataCache } from "."; +import { fetchNip05Pubkey, FeedCache, LNURL, DexieTableLike } from "@snort/shared"; export class UserProfileCache extends FeedCache { #zapperQueue: Array<{ pubkey: string; lnurl: string }> = []; #nip5Queue: Array<{ pubkey: string; nip05: string }> = []; - constructor() { - super("UserCache", db.users); + constructor(table?: DexieTableLike) { + super("UserCache", table); this.#processZapperQueue(); this.#processNip5Queue(); } @@ -24,10 +24,10 @@ export class UserProfileCache extends FeedCache { } async search(q: string): Promise> { - if (db.ready) { + if (this.table) { // on-disk cache will always have more data return ( - await db.users + await this.table .where("npub") .startsWithIgnoreCase(q) .or("name") diff --git a/packages/system/src/cache/user-relays.ts b/packages/system/src/cache/user-relays.ts index 7381e341..2e98ab5f 100644 --- a/packages/system/src/cache/user-relays.ts +++ b/packages/system/src/cache/user-relays.ts @@ -1,9 +1,9 @@ -import { db, UsersRelays } from "."; -import { FeedCache } from "@snort/shared"; +import { UsersRelays } from "."; +import { DexieTableLike, FeedCache } from "@snort/shared"; export class UserRelaysCache extends FeedCache { - constructor() { - super("UserRelays", db.userRelays); + constructor(table?: DexieTableLike) { + super("UserRelays", table); } key(of: UsersRelays): string { diff --git a/packages/system/src/nostr-system.ts b/packages/system/src/nostr-system.ts index 31e20987..b06ffed7 100644 --- a/packages/system/src/nostr-system.ts +++ b/packages/system/src/nostr-system.ts @@ -16,8 +16,8 @@ import { UserProfileCache, UserRelaysCache, RelayMetricCache, - db, UsersRelays, + SnortSystemDb, } from "."; import { EventsCache } from "./cache/events"; import { RelayCache } from "./gossip-model"; @@ -87,19 +87,21 @@ export class NostrSystem extends ExternalStore implements System relayMetrics?: FeedCache; eventsCache?: FeedCache; queryOptimizer?: QueryOptimizer; + db?: SnortSystemDb; }) { super(); this.#handleAuth = props.authHandler; - this.#relayCache = props.relayCache ?? new UserRelaysCache(); - this.#profileCache = props.profileCache ?? new UserProfileCache(); - this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(); - this.#eventsCache = props.eventsCache ?? new EventsCache(); + this.#relayCache = props.relayCache ?? new UserRelaysCache(props.db?.userRelays); + this.#profileCache = props.profileCache ?? new UserProfileCache(props.db?.users); + this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(props.db?.relayMetrics); + this.#eventsCache = props.eventsCache ?? new EventsCache(props.db?.events); this.#queryOptimizer = props.queryOptimizer ?? DefaultQueryOptimizer; this.#profileLoader = new ProfileLoaderService(this, this.#profileCache); this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache); this.#cleanup(); } + HandleAuth?: AuthHandler | undefined; get ProfileLoader() { @@ -122,7 +124,6 @@ export class NostrSystem extends ExternalStore implements System * Setup caches */ async Init() { - db.ready = await db.isAvailable(); const t = [ this.#relayCache.preload(), this.#profileCache.preload(), diff --git a/yarn.lock b/yarn.lock index 2c26368b..00f048c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1376,6 +1376,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.1.2": + version: 7.23.1 + resolution: "@babel/runtime@npm:7.23.1" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 0cd0d43e6e7dc7f9152fda8c8312b08321cda2f56ef53d6c22ebdd773abdc6f5d0a69008de90aa41908d00e2c1facb24715ff121274e689305c858355ff02c70 + languageName: node + linkType: hard + "@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.6, @babel/runtime@npm:^7.8.4": version: 7.22.11 resolution: "@babel/runtime@npm:7.22.11" @@ -2692,6 +2701,7 @@ __metadata: "@snort/system": "workspace:*" "@snort/system-react": "workspace:*" "@snort/system-wasm": "workspace:*" + "@snort/system-web": "workspace:*" "@szhsin/react-menu": ^3.3.1 "@types/debug": ^4.1.8 "@types/jest": ^29.5.1 @@ -2735,6 +2745,7 @@ __metadata: react-router-dom: ^6.5.0 react-textarea-autosize: ^8.4.0 react-twitter-embed: ^4.0.4 + recharts: ^2.8.0 source-map-loader: ^4.0.1 terser-webpack-plugin: ^5.3.9 tinybench: ^2.5.1 @@ -2799,6 +2810,17 @@ __metadata: languageName: unknown linkType: soft +"@snort/system-web@workspace:*, @snort/system-web@workspace:packages/system-web": + version: 0.0.0-use.local + resolution: "@snort/system-web@workspace:packages/system-web" + dependencies: + "@snort/shared": ^1.0.6 + "@snort/system": ^1.0.21 + dexie: ^3.2.4 + typescript: ^5.2.2 + languageName: unknown + linkType: soft + "@snort/system@^1.0.21, @snort/system@workspace:*, @snort/system@workspace:packages/system": version: 0.0.0-use.local resolution: "@snort/system@workspace:packages/system" @@ -3161,6 +3183,75 @@ __metadata: languageName: node linkType: hard +"@types/d3-array@npm:^3.0.3": + version: 3.0.8 + resolution: "@types/d3-array@npm:3.0.8" + checksum: d5a678f1dc3af05bc6beb675d59a11d9b2ad4ea59fb5b6c2b99980fec947d89a9562f3ac3a8d192a4f38152d3a4b9ee9cf4e2a30788eaacaed5de4a6da514e10 + languageName: node + linkType: hard + +"@types/d3-color@npm:*": + version: 3.1.1 + resolution: "@types/d3-color@npm:3.1.1" + checksum: 1fa67a6d11386c2727c942ab0ddffaca2289ba01d2f3cd0723afc78c291e9515dbdc6de082466d9e9c360d7c67ddbf313707456c0daa9aa14acb2d48cb3bcabb + languageName: node + linkType: hard + +"@types/d3-ease@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/d3-ease@npm:3.0.0" + checksum: 1be7c993643b5a08332e0ee146375a3845545d8deb423db5d152e0b061524385d2345ceccf968f75f605247b940dd3f9a144335fee2e7d935cddaf187afb7095 + languageName: node + linkType: hard + +"@types/d3-interpolate@npm:^3.0.1": + version: 3.0.2 + resolution: "@types/d3-interpolate@npm:3.0.2" + dependencies: + "@types/d3-color": "*" + checksum: 86a1c4853c70663cba970d5c57dca995f604a70684b17bc5ff3ba83ce4e2c13f0105af29bb383ee70c4ccb1920c0dd4aeb352ae8721864d4a503a110260b9b13 + languageName: node + linkType: hard + +"@types/d3-path@npm:*": + version: 3.0.0 + resolution: "@types/d3-path@npm:3.0.0" + checksum: af7f45ea912cddd794c03384baba856f11e1f9b57a49d05a66a61968dafaeb86e0e42394883118b9b8ccadce21a5f25b1f9a88ad05235e1dc6d24c3e34a379ff + languageName: node + linkType: hard + +"@types/d3-scale@npm:^4.0.2": + version: 4.0.5 + resolution: "@types/d3-scale@npm:4.0.5" + dependencies: + "@types/d3-time": "*" + checksum: f462a3f2ec8767bb6762953ed65087b4037d9f8c57c84b1ffc62d55b7633975611e053c2f36cef063bf123196fbb5741b257760b2a745ede9544851f7d150d60 + languageName: node + linkType: hard + +"@types/d3-shape@npm:^3.1.0": + version: 3.1.3 + resolution: "@types/d3-shape@npm:3.1.3" + dependencies: + "@types/d3-path": "*" + checksum: ad17781ab4ce4b796954b86de7e14566c731726d39a1db7d73eaf50668a71e996d715450a0ff9f2720755e1b8643c3e88d47d45101a75c9d4ddbef51a636f6a0 + languageName: node + linkType: hard + +"@types/d3-time@npm:*, @types/d3-time@npm:^3.0.0": + version: 3.0.1 + resolution: "@types/d3-time@npm:3.0.1" + checksum: 32b0c4d33574df167717f37d5d69f60fa1aeebb0218823239734a48e6a33024a7f5aadd079e94d833b42bfc0c3e2d9fa7d7ac93f75981f59ef2a46838d008a61 + languageName: node + linkType: hard + +"@types/d3-timer@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/d3-timer@npm:3.0.0" + checksum: 1ec86b3808de6ecfa93cfdf34254761069658af0cc1d9540e8353dbcba161cdf1296a0724187bd17433b2ff16563115fd20b85fc89d5e809ff28f9b1ab134b42 + languageName: node + linkType: hard + "@types/debug@npm:^4.1.8": version: 4.1.8 resolution: "@types/debug@npm:4.1.8" @@ -5010,6 +5101,13 @@ __metadata: languageName: node linkType: hard +"classnames@npm:^2.2.5": + version: 2.3.2 + resolution: "classnames@npm:2.3.2" + checksum: 2c62199789618d95545c872787137262e741f9db13328e216b093eea91c85ef2bfb152c1f9e63027204e2559a006a92eb74147d46c800a9f96297ae1d9f96f4e + languageName: node + linkType: hard + "clean-css@npm:^5.2.2": version: 5.3.2 resolution: "clean-css@npm:5.3.2" @@ -5553,6 +5651,13 @@ __metadata: languageName: node linkType: hard +"css-unit-converter@npm:^1.1.1": + version: 1.1.2 + resolution: "css-unit-converter@npm:1.1.2" + checksum: 07888033346a5128f34dbe2f72884c966d24e9f29db24416dcde92860242490617ef9a178ac193a92f730834bbeea026cdc7027701d92ba9bbbe59db7a37eb2a + languageName: node + linkType: hard + "css-what@npm:^6.0.1, css-what@npm:^6.1.0": version: 6.1.0 resolution: "css-what@npm:6.1.0" @@ -5675,6 +5780,99 @@ __metadata: languageName: node linkType: hard +"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:^3.1.6": + version: 3.2.4 + resolution: "d3-array@npm:3.2.4" + dependencies: + internmap: 1 - 2 + checksum: a5976a6d6205f69208478bb44920dd7ce3e788c9dceb86b304dbe401a4bfb42ecc8b04c20facde486e9adcb488b5d1800d49393a3f81a23902b68158e12cddd0 + languageName: node + linkType: hard + +"d3-color@npm:1 - 3": + version: 3.1.0 + resolution: "d3-color@npm:3.1.0" + checksum: 4931fbfda5d7c4b5cfa283a13c91a954f86e3b69d75ce588d06cde6c3628cebfc3af2069ccf225e982e8987c612aa7948b3932163ce15eb3c11cd7c003f3ee3b + languageName: node + linkType: hard + +"d3-ease@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-ease@npm:3.0.1" + checksum: 06e2ee5326d1e3545eab4e2c0f84046a123dcd3b612e68858219aa034da1160333d9ce3da20a1d3486d98cb5c2a06f7d233eee1bc19ce42d1533458bd85dedcd + languageName: node + linkType: hard + +"d3-format@npm:1 - 3": + version: 3.1.0 + resolution: "d3-format@npm:3.1.0" + checksum: f345ec3b8ad3cab19bff5dead395bd9f5590628eb97a389b1dd89f0b204c7c4fc1d9520f13231c2c7cf14b7c9a8cf10f8ef15bde2befbab41454a569bd706ca2 + languageName: node + linkType: hard + +"d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-interpolate@npm:3.0.1" + dependencies: + d3-color: 1 - 3 + checksum: a42ba314e295e95e5365eff0f604834e67e4a3b3c7102458781c477bd67e9b24b6bb9d8e41ff5521050a3f2c7c0c4bbbb6e187fd586daa3980943095b267e78b + languageName: node + linkType: hard + +"d3-path@npm:^3.1.0": + version: 3.1.0 + resolution: "d3-path@npm:3.1.0" + checksum: 2306f1bd9191e1eac895ec13e3064f732a85f243d6e627d242a313f9777756838a2215ea11562f0c7630c7c3b16a19ec1fe0948b1c82f3317fac55882f6ee5d8 + languageName: node + linkType: hard + +"d3-scale@npm:^4.0.2": + version: 4.0.2 + resolution: "d3-scale@npm:4.0.2" + dependencies: + d3-array: 2.10.0 - 3 + d3-format: 1 - 3 + d3-interpolate: 1.2.0 - 3 + d3-time: 2.1.1 - 3 + d3-time-format: 2 - 4 + checksum: a9c770d283162c3bd11477c3d9d485d07f8db2071665f1a4ad23eec3e515e2cefbd369059ec677c9ac849877d1a765494e90e92051d4f21111aa56791c98729e + languageName: node + linkType: hard + +"d3-shape@npm:^3.1.0": + version: 3.2.0 + resolution: "d3-shape@npm:3.2.0" + dependencies: + d3-path: ^3.1.0 + checksum: de2af5fc9a93036a7b68581ca0bfc4aca2d5a328aa7ba7064c11aedd44d24f310c20c40157cb654359d4c15c3ef369f95ee53d71221017276e34172c7b719cfa + languageName: node + linkType: hard + +"d3-time-format@npm:2 - 4": + version: 4.1.0 + resolution: "d3-time-format@npm:4.1.0" + dependencies: + d3-time: 1 - 3 + checksum: 7342bce28355378152bbd4db4e275405439cabba082d9cd01946d40581140481c8328456d91740b0fe513c51ec4a467f4471ffa390c7e0e30ea30e9ec98fcdf4 + languageName: node + linkType: hard + +"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3, d3-time@npm:^3.0.0": + version: 3.1.0 + resolution: "d3-time@npm:3.1.0" + dependencies: + d3-array: 2 - 3 + checksum: 613b435352a78d9f31b7f68540788186d8c331b63feca60ad21c88e9db1989fe888f97f242322ebd6365e45ec3fb206a4324cd4ca0dfffa1d9b5feb856ba00a7 + languageName: node + linkType: hard + +"d3-timer@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-timer@npm:3.0.1" + checksum: 1cfddf86d7bca22f73f2c427f52dfa35c49f50d64e187eb788dcad6e927625c636aa18ae4edd44d084eb9d1f81d8ca4ec305dae7f733c15846a824575b789d73 + languageName: node + linkType: hard + "dargs@npm:^7.0.0": version: 7.0.0 resolution: "dargs@npm:7.0.0" @@ -5728,6 +5926,13 @@ __metadata: languageName: node linkType: hard +"decimal.js-light@npm:^2.4.1": + version: 2.5.1 + resolution: "decimal.js-light@npm:2.5.1" + checksum: f5a2c7eac1c4541c8ab8a5c8abea64fc1761cefc7794bd5f8afd57a8a78d1b51785e0c4e4f85f4895a043eaa90ddca1edc3981d1263eb6ddce60f32bf5fe66c9 + languageName: node + linkType: hard + "decimal.js@npm:^10.4.2": version: 10.4.3 resolution: "decimal.js@npm:10.4.3" @@ -5947,6 +6152,15 @@ __metadata: languageName: node linkType: hard +"dom-helpers@npm:^3.4.0": + version: 3.4.0 + resolution: "dom-helpers@npm:3.4.0" + dependencies: + "@babel/runtime": ^7.1.2 + checksum: 58d9f1c4a96daf77eddc63ae1236b826e1cddd6db66bbf39b18d7e21896d99365b376593352d52a60969d67fa4a8dbef26adc1439fa2c1b355efa37cacbaf637 + languageName: node + linkType: hard + "dom-serializer@npm:^1.0.1": version: 1.4.1 resolution: "dom-serializer@npm:1.4.1" @@ -6490,7 +6704,7 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.4": +"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.1, eventemitter3@npm:^4.0.4": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" checksum: 1875311c42fcfe9c707b2712c32664a245629b42bb0a5a84439762dd0fd637fc54d078155ea83c2af9e0323c9ac13687e03cfba79b03af9f40c89b4960099374 @@ -6605,6 +6819,13 @@ __metadata: languageName: node linkType: hard +"fast-equals@npm:^5.0.0": + version: 5.0.1 + resolution: "fast-equals@npm:5.0.1" + checksum: fbb3b6a74f3a0fa930afac151ff7d01639159b4fddd2678b5d50708e0ba38e9ec14602222d10dadb8398187342692c04fbef5a62b1cfcc7942fe03e754e064bc + languageName: node + linkType: hard + "fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0": version: 3.3.1 resolution: "fast-glob@npm:3.3.1" @@ -7689,6 +7910,13 @@ __metadata: languageName: node linkType: hard +"internmap@npm:1 - 2": + version: 2.0.3 + resolution: "internmap@npm:2.0.3" + checksum: 7ca41ec6aba8f0072fc32fa8a023450a9f44503e2d8e403583c55714b25efd6390c38a87161ec456bf42d7bc83aab62eb28f5aef34876b1ac4e60693d5e1d241 + languageName: node + linkType: hard + "interpret@npm:^1.0.0": version: 1.4.0 resolution: "interpret@npm:1.4.0" @@ -9058,7 +9286,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.20, lodash@npm:^4.17.21": +"lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -10879,6 +11107,13 @@ __metadata: languageName: node linkType: hard +"postcss-value-parser@npm:^3.3.0": + version: 3.3.1 + resolution: "postcss-value-parser@npm:3.3.1" + checksum: 62cd26e1cdbcf2dcc6bcedf3d9b409c9027bc57a367ae20d31dd99da4e206f730689471fd70a2abe866332af83f54dc1fa444c589e2381bf7f8054c46209ce16 + languageName: node + linkType: hard + "postcss-value-parser@npm:^4.1.0, postcss-value-parser@npm:^4.2.0": version: 4.2.0 resolution: "postcss-value-parser@npm:4.2.0" @@ -11040,7 +11275,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -11217,7 +11452,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1, react-is@npm:^16.7.0": +"react-is@npm:^16.10.2, react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -11231,6 +11466,25 @@ __metadata: languageName: node linkType: hard +"react-lifecycles-compat@npm:^3.0.4": + version: 3.0.4 + resolution: "react-lifecycles-compat@npm:3.0.4" + checksum: a904b0fc0a8eeb15a148c9feb7bc17cec7ef96e71188280061fc340043fd6d8ee3ff233381f0e8f95c1cf926210b2c4a31f38182c8f35ac55057e453d6df204f + languageName: node + linkType: hard + +"react-resize-detector@npm:^8.0.4": + version: 8.1.0 + resolution: "react-resize-detector@npm:8.1.0" + dependencies: + lodash: ^4.17.21 + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 45e6b87ea7331406bed2a806d0cea98c1467d53a7cfcdf19c2dd55a3460047917d3b175d9cceea6f314b65eb54858cbb981acffd007d67aa16388e517dafb83e + languageName: node + linkType: hard + "react-router-dom@npm:^6.5.0": version: 6.15.0 resolution: "react-router-dom@npm:6.15.0" @@ -11255,6 +11509,20 @@ __metadata: languageName: node linkType: hard +"react-smooth@npm:^2.0.2": + version: 2.0.4 + resolution: "react-smooth@npm:2.0.4" + dependencies: + fast-equals: ^5.0.0 + react-transition-group: 2.9.0 + peerDependencies: + prop-types: ^15.6.0 + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 21731e2f9ebc9594eae0f0d875526185392a87c00abf013c9769ed642a4077b62c04c1001b2527a196aabafb87af208f6c7107db674538c4bb95c253ed123447 + languageName: node + linkType: hard + "react-textarea-autosize@npm:^8.4.0": version: 8.5.3 resolution: "react-textarea-autosize@npm:8.5.3" @@ -11268,6 +11536,21 @@ __metadata: languageName: node linkType: hard +"react-transition-group@npm:2.9.0": + version: 2.9.0 + resolution: "react-transition-group@npm:2.9.0" + dependencies: + dom-helpers: ^3.4.0 + loose-envify: ^1.4.0 + prop-types: ^15.6.2 + react-lifecycles-compat: ^3.0.4 + peerDependencies: + react: ">=15.0.0" + react-dom: ">=15.0.0" + checksum: d8c9e50aabdc2cfc324e5cdb0ad1c6eecb02e1c0cd007b26d5b30ccf49015e900683dd489348c71fba4055858308d9ba7019e0d37d0e8d37bd46ed098788f670 + languageName: node + linkType: hard + "react-transition-state@npm:^1.1.5": version: 1.1.5 resolution: "react-transition-state@npm:1.1.5" @@ -11421,6 +11704,36 @@ __metadata: languageName: node linkType: hard +"recharts-scale@npm:^0.4.4": + version: 0.4.5 + resolution: "recharts-scale@npm:0.4.5" + dependencies: + decimal.js-light: ^2.4.1 + checksum: e970377190a610e684a32c7461c7684ac3603c2e0ac0020bbba1eea9d099b38138143a8e80bf769bb49c0b7cecf22a2f5c6854885efed2d56f4540d4aa7052bd + languageName: node + linkType: hard + +"recharts@npm:^2.8.0": + version: 2.8.0 + resolution: "recharts@npm:2.8.0" + dependencies: + classnames: ^2.2.5 + eventemitter3: ^4.0.1 + lodash: ^4.17.19 + react-is: ^16.10.2 + react-resize-detector: ^8.0.4 + react-smooth: ^2.0.2 + recharts-scale: ^0.4.4 + reduce-css-calc: ^2.1.8 + victory-vendor: ^36.6.8 + peerDependencies: + prop-types: ^15.6.0 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 4638bd5c6c2af8f5c79de5e13cce0e38f06e0bbb0a3c4df27a9b12632fd72c0a0604c8246f55e830f323dfa84a3da7cb2634c2243bb9c775d899fd71f9d4c87a + languageName: node + linkType: hard + "rechoir@npm:^0.6.2": version: 0.6.2 resolution: "rechoir@npm:0.6.2" @@ -11439,6 +11752,16 @@ __metadata: languageName: node linkType: hard +"reduce-css-calc@npm:^2.1.8": + version: 2.1.8 + resolution: "reduce-css-calc@npm:2.1.8" + dependencies: + css-unit-converter: ^1.1.1 + postcss-value-parser: ^3.3.0 + checksum: 8fd27c06c4b443b84749a69a8b97d10e6ec7d142b625b41923a8807abb22b9e37e44df14e26cc606a802957be07bdce5e8ee2976a6952a7b438a7727007101e9 + languageName: node + linkType: hard + "regenerate-unicode-properties@npm:^10.1.0": version: 10.1.0 resolution: "regenerate-unicode-properties@npm:10.1.0" @@ -13401,6 +13724,28 @@ __metadata: languageName: node linkType: hard +"victory-vendor@npm:^36.6.8": + version: 36.6.11 + resolution: "victory-vendor@npm:36.6.11" + dependencies: + "@types/d3-array": ^3.0.3 + "@types/d3-ease": ^3.0.0 + "@types/d3-interpolate": ^3.0.1 + "@types/d3-scale": ^4.0.2 + "@types/d3-shape": ^3.1.0 + "@types/d3-time": ^3.0.0 + "@types/d3-timer": ^3.0.0 + d3-array: ^3.1.6 + d3-ease: ^3.0.1 + d3-interpolate: ^3.0.1 + d3-scale: ^4.0.2 + d3-shape: ^3.1.0 + d3-time: ^3.0.0 + d3-timer: ^3.0.1 + checksum: 55800076dfa6abedf7758840986a302778a904678d4b66fe47d977c48b6f9484276b780871e6e5105b31c1eb936e9f1331ee39afcc2869bf65ceb7d456143172 + languageName: node + linkType: hard + "vinyl-file@npm:^3.0.0": version: 3.0.0 resolution: "vinyl-file@npm:3.0.0"