diff --git a/.vscode/settings.json b/.vscode/settings.json index 84a28bd..f693212 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "editor.codeActionsOnSave": { - "quickfix.biome": "explicit" + "quickfix.biome": "explicit", + "source.organizeImports.biome": "explicit" }, "typescript.inlayHints.parameterNames.enabled": "all", "[typescript]": { diff --git a/bun.lock b/bun.lock index 0591354..38fc1e5 100644 --- a/bun.lock +++ b/bun.lock @@ -4,11 +4,11 @@ "": { "name": "signalstats", "dependencies": { - "@duskflower/signal-decrypt-backup-wasm": "^0.2.1", + "@duskflower/signal-decrypt-backup-wasm": "^0.3.0", "@kobalte/core": "^0.13.7", "@kobalte/tailwindcss": "^0.9.0", "@solid-primitives/refs": "^1.1.0", - "@solid-primitives/storage": "^4.3.0", + "@solid-primitives/storage": "^4.3.1", "@solid-primitives/upload": "^0.1.0", "@solid-primitives/workers": "^0.4.0", "@solidjs/meta": "^0.29.4", @@ -37,7 +37,7 @@ "@commitlint/cli": "^19.6.1", "@commitlint/config-conventional": "^19.6.0", "@tailwindcss/vite": "^4.0.0", - "@types/node": "^22.10.9", + "@types/node": "^22.10.10", "better-sqlite3": "^11.8.1", "husky": "^9.1.7", "kysely-codegen": "^0.17.0", @@ -141,7 +141,7 @@ "@corvu/utils": ["@corvu/utils@0.4.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.11" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA=="], - "@duskflower/signal-decrypt-backup-wasm": ["@duskflower/signal-decrypt-backup-wasm@0.2.1", "https://git.duskflower.dev/api/packages/duskflower/npm/%40duskflower%2Fsignal-decrypt-backup-wasm/-/0.2.1/signal-decrypt-backup-wasm-0.2.1.tgz", {}, "sha512-lYwtubVOUU4kuJNw28Ns8N9Gh2nZB4j6BJz/vLpxF0Vc7zww3VIGnMCQToFWWSAwCs5+cABadeZMkv87SGfyQg=="], + "@duskflower/signal-decrypt-backup-wasm": ["@duskflower/signal-decrypt-backup-wasm@0.3.0", "https://git.duskflower.dev/api/packages/duskflower/npm/%40duskflower%2Fsignal-decrypt-backup-wasm/-/0.3.0/signal-decrypt-backup-wasm-0.3.0.tgz", {}, "sha512-1IMNFGz4bV4o29eXVelNbancqovsljmM0iZRrtau81gSy8bFE1lgwGFN05NAOFWlX/aZYzeUEeZVBrjUYIM0/w=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="], diff --git a/package.json b/package.json index 64c8aaa..eac0529 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@commitlint/cli": "^19.6.1", "@commitlint/config-conventional": "^19.6.0", "@tailwindcss/vite": "^4.0.0", - "@types/node": "^22.10.9", + "@types/node": "^22.10.10", "better-sqlite3": "^11.8.1", "husky": "^9.1.7", "kysely-codegen": "^0.17.0", @@ -28,11 +28,11 @@ "vite-plugin-wasm": "^3.4.1" }, "dependencies": { - "@duskflower/signal-decrypt-backup-wasm": "^0.2.1", + "@duskflower/signal-decrypt-backup-wasm": "^0.3.0", "@kobalte/core": "^0.13.7", "@kobalte/tailwindcss": "^0.9.0", "@solid-primitives/refs": "^1.1.0", - "@solid-primitives/storage": "^4.3.0", + "@solid-primitives/storage": "^4.3.1", "@solid-primitives/upload": "^0.1.0", "@solid-primitives/workers": "^0.4.0", "@solidjs/meta": "^0.29.4", diff --git a/src/App.tsx b/src/App.tsx index 4ae175b..cdf0433 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,15 @@ import { A, Route, Router, useNavigate } from "@solidjs/router"; -import { createEffect, Show, Suspense, type Component } from "solid-js"; -import { DmId, GroupId, Home, Overview, preloadDmId, Privacy } from "./pages"; - +import { type Component, Show, Suspense, createEffect } from "solid-js"; +import { DmId, GroupId, Home, Overview, Privacy, preloadDmId } from "./pages"; import "./app.css"; +import { ColorModeProvider, ColorModeScript, createLocalStorageManager } from "@kobalte/core"; import { MetaProvider } from "@solidjs/meta"; import { Portal } from "solid-js/web"; -import { Callout, CalloutTitle, CalloutContent } from "./components/ui/callout"; +import { Callout, CalloutContent, CalloutTitle } from "./components/ui/callout"; +import { ModeToggle } from "./components/ui/mode-toggle"; import { dbLoaded } from "./db"; import { hasCashedData } from "./lib/db-cache"; import { isWasmSupported } from "./lib/utils"; -import { ColorModeProvider, ColorModeScript, createLocalStorageManager } from "@kobalte/core"; -import { ModeToggle } from "./components/ui/mode-toggle"; const NO_DATA_NEEDED_PAGES = ["/", "/privacy"]; diff --git a/src/db/db-queries.ts b/src/db/db-queries.ts index 4d1a87a..7193de8 100644 --- a/src/db/db-queries.ts +++ b/src/db/db-queries.ts @@ -1,7 +1,7 @@ -import { sql, type NotNull } from "kysely"; -import { worker, kyselyDb, SELF_ID, DB_FILENAME, setDbLoaded } from "./db"; -import { cached, clearDbCache } from "../lib/db-cache"; +import { type NotNull, sql } from "kysely"; import type { MainToWorkerMsg, WorkerToMainMsg } from "~/lib/kysely-official-wasm-worker/type"; +import { cached, clearDbCache } from "../lib/db-cache"; +import { DB_FILENAME, SELF_ID, kyselyDb, setDbLoaded, worker } from "./db"; export const loadDb = async (statements: string[], progressCallback?: (percentage: number) => void): Promise => { // try to persist storage, https://web.dev/articles/persistent-storage#request_persistent_storage diff --git a/src/db/db.ts b/src/db/db.ts index 584b868..b2168a7 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -1,8 +1,8 @@ import { Kysely } from "kysely"; -import type { DB } from "./db-schema"; -import { OfficialWasmWorkerDialect } from "~/lib/kysely-official-wasm-worker"; -import WasmWorker from "./db-worker?worker"; import { createSignal } from "solid-js"; +import { OfficialWasmWorkerDialect } from "~/lib/kysely-official-wasm-worker"; +import type { DB } from "./db-schema"; +import WasmWorker from "./db-worker?worker"; export const SELF_ID = 2; diff --git a/src/lib/decryptor.ts b/src/lib/backup-decryptor.ts similarity index 80% rename from src/lib/decryptor.ts rename to src/lib/backup-decryptor.ts index 1ad2d8f..86c13e5 100644 --- a/src/lib/decryptor.ts +++ b/src/lib/backup-decryptor.ts @@ -1,7 +1,4 @@ -import { - BackupDecryptor, - type DecryptionResult, -} from "@duskflower/signal-decrypt-backup-wasm"; +import { BackupDecryptor } from "@duskflower/signal-decrypt-backup-wasm"; const CHUNK_SIZE = 1024 * 1024 * 40; // 40MB chunks @@ -9,7 +6,8 @@ export async function decryptBackup( file: File, passphrase: string, progressCallback: (progress: number) => void, -): Promise { + statementsCallback?: (statements: string[]) => void | Promise, +): Promise { const fileSize = file.size; const decryptor = new BackupDecryptor(); decryptor.set_progress_callback(fileSize, progressCallback); @@ -34,6 +32,8 @@ export async function decryptBackup( } } + await statementsCallback?.(decryptor.get_new_decrypted_statements()); + offset += CHUNK_SIZE; } diff --git a/src/lib/kysely-official-wasm-worker/index.ts b/src/lib/kysely-official-wasm-worker/index.ts index 1886c71..9af4767 100644 --- a/src/lib/kysely-official-wasm-worker/index.ts +++ b/src/lib/kysely-official-wasm-worker/index.ts @@ -1,11 +1,4 @@ -import type { - DatabaseIntrospector, - Dialect, - DialectAdapter, - Driver, - Kysely, - QueryCompiler, -} from "kysely"; +import type { DatabaseIntrospector, Dialect, DialectAdapter, Driver, Kysely, QueryCompiler } from "kysely"; import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from "kysely"; import { OfficialWasmWorkerDriver } from "./driver"; import type { OfficialWasmWorkerDialectConfig } from "./type"; diff --git a/src/lib/messages-worker.ts b/src/lib/messages-worker.ts index d4cb339..94dff12 100644 --- a/src/lib/messages-worker.ts +++ b/src/lib/messages-worker.ts @@ -1,5 +1,5 @@ -import { getHourList, getMonthList, getWeekdayList } from "./date"; import type { MessageOverview, MessageStats, Recipients } from "~/types"; +import { getHourList, getMonthList, getWeekdayList } from "./date"; const hourNames = getHourList(); diff --git a/src/lib/messages.ts b/src/lib/messages.ts index 51c1539..f84d15a 100644 --- a/src/lib/messages.ts +++ b/src/lib/messages.ts @@ -1,6 +1,6 @@ import type { MessageOverview, MessageStats, Recipients } from "~/types"; -import { cached } from "./db-cache"; import { getHourList, getMonthList, getWeekdayList } from "./date"; +import { cached } from "./db-cache"; import MessageStatsWorker from "./messages-worker?worker"; export const hourNames = getHourList(); diff --git a/src/pages/dm/dm-id.tsx b/src/pages/dm/dm-id.tsx index 91d884a..6ee94fd 100644 --- a/src/pages/dm/dm-id.tsx +++ b/src/pages/dm/dm-id.tsx @@ -1,21 +1,20 @@ -import { Suspense, type Component } from "solid-js"; -import { createAsync, type RoutePreloadFunc, type RouteSectionProps } from "@solidjs/router"; - -import { dmPartnerRecipientQuery, threadMostUsedWordsQuery, threadSentMessagesOverviewQuery, SELF_ID } from "~/db"; -import { getNameFromRecipient } from "~/lib/get-name-from-recipient"; -import { Heading } from "~/components/ui/heading"; -import { Grid } from "~/components/ui/grid"; import { Title } from "@solidjs/meta"; +import { type RoutePreloadFunc, type RouteSectionProps, createAsync } from "@solidjs/router"; +import { type Component, Suspense } from "solid-js"; +import { Flex } from "~/components/ui/flex"; +import { Grid } from "~/components/ui/grid"; +import { Heading } from "~/components/ui/heading"; +import { SELF_ID, dmPartnerRecipientQuery, threadMostUsedWordsQuery, threadSentMessagesOverviewQuery } from "~/db"; +import { getNameFromRecipient } from "~/lib/get-name-from-recipient"; +import { createMessageStatsSources } from "~/lib/messages"; +import type { MessageOverview } from "~/types"; import { DmMessagesPerDate } from "./dm-messages-per-date"; -import { DmOverview } from "./dm-overview"; -import { DmWordCloud } from "./dm-wordcloud"; -import { DmMessagesPerMonth } from "./dm-messages-per-month"; import { DmMessagesPerDaytime } from "./dm-messages-per-daytime"; +import { DmMessagesPerMonth } from "./dm-messages-per-month"; import { DmMessagesPerRecipient } from "./dm-messages-per-recipients"; import { DmMessagesPerWeekday } from "./dm-messages-per-weekday"; -import type { MessageOverview } from "~/types"; -import { createMessageStatsSources } from "~/lib/messages"; -import { Flex } from "~/components/ui/flex"; +import { DmOverview } from "./dm-overview"; +import { DmWordCloud } from "./dm-wordcloud"; const getDmIdData = (dmId: number) => { // the other person in the chat with name and id diff --git a/src/pages/dm/dm-messages-per-date.tsx b/src/pages/dm/dm-messages-per-date.tsx index 9d3aea7..5e0a967 100644 --- a/src/pages/dm/dm-messages-per-date.tsx +++ b/src/pages/dm/dm-messages-per-date.tsx @@ -1,5 +1,5 @@ -import { Show, type Accessor, type Component } from "solid-js"; import type { ChartData } from "chart.js"; +import { type Accessor, type Component, Show } from "solid-js"; import { LineChart } from "~/components/ui/charts"; import type { MessageStats, Recipients } from "~/types"; diff --git a/src/pages/dm/dm-messages-per-daytime.tsx b/src/pages/dm/dm-messages-per-daytime.tsx index 1c12ff5..39765aa 100644 --- a/src/pages/dm/dm-messages-per-daytime.tsx +++ b/src/pages/dm/dm-messages-per-daytime.tsx @@ -1,8 +1,8 @@ -import { Show, type Accessor, type Component } from "solid-js"; import type { ChartData } from "chart.js"; +import { type Accessor, type Component, Show } from "solid-js"; import { BarChart } from "~/components/ui/charts"; -import type { MessageStats, Recipients } from "~/types"; import { hourNames } from "~/lib/messages"; +import type { MessageStats, Recipients } from "~/types"; export const DmMessagesPerDaytime: Component<{ daytimeStats: MessageStats["daytime"] | undefined; diff --git a/src/pages/dm/dm-messages-per-month.tsx b/src/pages/dm/dm-messages-per-month.tsx index 7bcceaf..762b6a1 100644 --- a/src/pages/dm/dm-messages-per-month.tsx +++ b/src/pages/dm/dm-messages-per-month.tsx @@ -1,5 +1,5 @@ -import { Show, type Accessor, type Component } from "solid-js"; import type { ChartData } from "chart.js"; +import { type Accessor, type Component, Show } from "solid-js"; import { RadarChart } from "~/components/ui/charts"; import { monthNames } from "~/lib/messages"; import type { MessageStats, Recipients } from "~/types"; diff --git a/src/pages/dm/dm-messages-per-recipients.tsx b/src/pages/dm/dm-messages-per-recipients.tsx index 8d43409..4639183 100644 --- a/src/pages/dm/dm-messages-per-recipients.tsx +++ b/src/pages/dm/dm-messages-per-recipients.tsx @@ -1,5 +1,5 @@ -import { Show, type Accessor, type Component } from "solid-js"; import type { ChartData } from "chart.js"; +import { type Accessor, type Component, Show } from "solid-js"; import { PieChart } from "~/components/ui/charts"; import type { MessageStats, Recipients } from "~/types"; diff --git a/src/pages/dm/dm-messages-per-weekday.tsx b/src/pages/dm/dm-messages-per-weekday.tsx index 4608298..1d66a81 100644 --- a/src/pages/dm/dm-messages-per-weekday.tsx +++ b/src/pages/dm/dm-messages-per-weekday.tsx @@ -1,5 +1,5 @@ -import { Show, type Accessor, type Component } from "solid-js"; import type { ChartData } from "chart.js"; +import { type Accessor, type Component, Show } from "solid-js"; import { RadarChart } from "~/components/ui/charts"; import { weekdayNames } from "~/lib/messages"; import type { MessageStats, Recipients } from "~/types"; diff --git a/src/pages/dm/dm-overview.tsx b/src/pages/dm/dm-overview.tsx index 1a1b464..e2aa17b 100644 --- a/src/pages/dm/dm-overview.tsx +++ b/src/pages/dm/dm-overview.tsx @@ -1,7 +1,7 @@ -import { Show, type Component } from "solid-js"; +import { CalendarArrowDown, CalendarArrowUp, CalendarClock, MessagesSquare } from "lucide-solid"; +import { type Component, Show } from "solid-js"; import { Flex } from "~/components/ui/flex"; import { Grid } from "~/components/ui/grid"; -import { CalendarArrowDown, CalendarArrowUp, CalendarClock, MessagesSquare } from "lucide-solid"; import { getDistanceBetweenDatesInDays } from "~/lib/date"; import type { MessageOverview } from "~/types"; diff --git a/src/pages/dm/dm-wordcloud.tsx b/src/pages/dm/dm-wordcloud.tsx index bd24624..e6237e8 100644 --- a/src/pages/dm/dm-wordcloud.tsx +++ b/src/pages/dm/dm-wordcloud.tsx @@ -1,5 +1,5 @@ import type { ChartData } from "chart.js"; -import { Show, type Accessor, type Component } from "solid-js"; +import { type Accessor, type Component, Show } from "solid-js"; import { WordCloudChart } from "~/components/ui/charts"; import type { threadMostUsedWordsQuery } from "~/db"; diff --git a/src/pages/group/group-id.tsx b/src/pages/group/group-id.tsx index 3ac5993..fa95ecc 100644 --- a/src/pages/group/group-id.tsx +++ b/src/pages/group/group-id.tsx @@ -1,5 +1,5 @@ -import type { Component } from "solid-js"; import type { RouteSectionProps } from "@solidjs/router"; +import type { Component } from "solid-js"; export const GroupId: Component = (props) => { const groupId = () => Number(props.params.groupid); diff --git a/src/pages/home.tsx b/src/pages/home.tsx index fa27c30..4d79e75 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -1,16 +1,14 @@ -import { useNavigate, type RouteSectionProps } from "@solidjs/router"; -import { createSignal, type JSX, Show, type Component } from "solid-js"; - -import { Title } from "@solidjs/meta"; -import { Portal } from "solid-js/web"; -import { Flex } from "~/components/ui/flex"; - -import { Progress, ProgressLabel, ProgressValueLabel } from "~/components/ui/progress"; -import { loadDb } from "~/db"; -import { decryptBackup } from "~/lib/decryptor"; import { createDropzone, createFileUploader } from "@solid-primitives/upload"; +import { Title } from "@solidjs/meta"; +import { type RouteSectionProps, useNavigate } from "@solidjs/router"; +import { type Component, type JSX, Show, createSignal } from "solid-js"; +import { Portal } from "solid-js/web"; import { Button } from "~/components/ui/button"; +import { Flex } from "~/components/ui/flex"; +import { Progress, ProgressLabel, ProgressValueLabel } from "~/components/ui/progress"; import { TextField, TextFieldInput, TextFieldLabel } from "~/components/ui/text-field"; +import { loadDb } from "~/db"; +import { decryptBackup } from "~/lib/backup-decryptor"; export const Home: Component = () => { const navigate = useNavigate(); @@ -34,8 +32,8 @@ export const Home: Component = () => { const [backupFile, setBackupFile] = createSignal(); const [decryptionProgress, setDecryptionProgress] = createSignal(); - const [loadingProgress, setLoadingProgress] = createSignal(); - // const [isLoadingDatabase, setIsLoadingDatabase] = createSignal(false); + const [totalStatements, setTotalStatements] = createSignal(0); + const [executedStatements, setExecutedStatements] = createSignal(0); const onSubmit: JSX.EventHandler = (event) => { event.preventDefault(); @@ -44,28 +42,22 @@ export const Home: Component = () => { const currentPassphrase = passphrase(); if (currentBackupFile && currentPassphrase) { - // const hashChunk = await currentBackupFile.slice(-1000).text(); - // const hash = hashString(hashChunk); + console.time(); + decryptBackup(currentBackupFile, currentPassphrase, setDecryptionProgress, async (statements) => { + const length = statements.length; + setTotalStatements((oldValue) => oldValue + length); - // if (hash === dbHash()) { - // return; - // } + await loadDb(statements, (progress) => { + setExecutedStatements((oldValue) => Math.round(oldValue + (progress / 100) * length)); + }); - // setDbHash(hash); - - decryptBackup(currentBackupFile, currentPassphrase, setDecryptionProgress) - .then(async (decrypted) => { + setExecutedStatements((oldValue) => oldValue + length); + }) + .then(() => { umami.track("Decrypt backup"); - setDecryptionProgress(undefined); - // setIsLoadingDatabase(true); - setLoadingProgress(0); - - await loadDb(decrypted.database_statements, setLoadingProgress); umami.track("Load database"); - // setIsLoadingDatabase(false); - setLoadingProgress(undefined); - + console.timeEnd(); navigate("/overview"); }) .catch((error) => { @@ -84,7 +76,7 @@ export const Home: Component = () => { class="fixed inset-0 gap-y-8 backdrop-blur-lg backdrop-filter" classList={{ // hidden: decryptionProgress() === undefined && !isLoadingDatabase(), - hidden: decryptionProgress() === undefined && loadingProgress() === undefined, + hidden: decryptionProgress() === undefined && totalStatements() === 0, }} > @@ -97,20 +89,18 @@ export const Home: Component = () => { class="w-[300px] space-y-1" >
- Processing... + Decrypting...
- - {/*

Loading database

-

This can take some time

*/} +

Loading database

`${value}%`} + maxValue={totalStatements()} + getValueLabel={({ value, max }) => `${value} of ${max}`} class="w-[300px] space-y-1" >
diff --git a/src/pages/overview/index.tsx b/src/pages/overview/index.tsx index 61e52b6..423bf37 100644 --- a/src/pages/overview/index.tsx +++ b/src/pages/overview/index.tsx @@ -1,9 +1,7 @@ -import type { RouteSectionProps } from "@solidjs/router"; -import { type Component, createResource, Show } from "solid-js"; - -import { allThreadsOverviewQuery, overallSentMessagesQuery, SELF_ID } from "~/db"; - import { Title } from "@solidjs/meta"; +import type { RouteSectionProps } from "@solidjs/router"; +import { type Component, Show, createResource } from "solid-js"; +import { SELF_ID, allThreadsOverviewQuery, overallSentMessagesQuery } from "~/db"; import { getNameFromRecipient } from "~/lib/get-name-from-recipient"; import { OverviewTable, type RoomOverview } from "./overview-table"; diff --git a/src/pages/overview/overview-table.tsx b/src/pages/overview/overview-table.tsx index d0533e0..1ae9656 100644 --- a/src/pages/overview/overview-table.tsx +++ b/src/pages/overview/overview-table.tsx @@ -1,32 +1,29 @@ -import { type Component, createSignal, For, Match, Show, Switch } from "solid-js"; import { useNavigate, usePreloadRoute } from "@solidjs/router"; - import { type ColumnFiltersState, + type FilterFn, + type SortDirection, + type SortingState, createColumnHelper, createSolidTable, - type FilterFn, flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, - type SortDirection, - type SortingState, } from "@tanstack/solid-table"; import { intlFormatDistance } from "date-fns"; import { ArrowDown, ArrowUp, ArrowUpDown } from "lucide-solid"; - +import { type Component, For, Match, Show, Switch, createSignal } from "solid-js"; import { Badge } from "~/components/ui/badge"; import { Button } from "~/components/ui/button"; import { Checkbox } from "~/components/ui/checkbox"; +import { Flex } from "~/components/ui/flex"; import { Label } from "~/components/ui/label"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table"; import { TextField, TextFieldInput } from "~/components/ui/text-field"; - -import { cn } from "~/lib/utils"; -import { Flex } from "~/components/ui/flex"; import { dbLoaded, threadSentMessagesOverviewQuery } from "~/db"; +import { cn } from "~/lib/utils"; export interface RoomOverview { threadId: number; diff --git a/vite.config.ts b/vite.config.ts index c9270f0..d086c34 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,8 @@ import path from "path"; +import tailwind from "@tailwindcss/vite"; import { defineConfig } from "vite"; import solidPlugin from "vite-plugin-solid"; import wasm from "vite-plugin-wasm"; -import tailwind from "@tailwindcss/vite"; export default defineConfig({ plugins: [solidPlugin(), wasm(), tailwind()],