chore: update dependencies
This commit is contained in:
parent
2d9f108ed2
commit
0e1eed664d
14 changed files with 1371 additions and 640 deletions
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
|||
@duskflower:registry=https://git.duskflower.dev/api/packages/duskflower/npm/
|
|
@ -108,13 +108,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ignore": [
|
||||
"dist/**/*.ts",
|
||||
"dist/**",
|
||||
"**/*.mjs",
|
||||
"eslint.config.js",
|
||||
"**/*.js"
|
||||
]
|
||||
"ignore": ["dist/**/*.ts", "dist/**", "**/*.mjs", "eslint.config.js", "**/*.js"]
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
|
|
25
package.json
25
package.json
|
@ -15,30 +15,31 @@
|
|||
"@biomejs/biome": "1.9.4",
|
||||
"@commitlint/cli": "^19.6.1",
|
||||
"@commitlint/config-conventional": "^19.6.0",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/sql.js": "^1.4.9",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"better-sqlite3": "^11.8.1",
|
||||
"husky": "^9.1.7",
|
||||
"kysely-codegen": "^0.17.0",
|
||||
"lint-staged": "^15.2.11",
|
||||
"postcss": "^8.4.49",
|
||||
"lint-staged": "^15.4.1",
|
||||
"postcss": "^8.5.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.6",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.0.9",
|
||||
"vite-plugin-solid": "^2.11.0",
|
||||
"vite-plugin-wasm": "^3.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@duskflower/signal-decrypt-backup-wasm": "^0.2.0",
|
||||
"@duskflower/signal-decrypt-backup-wasm": "^0.2.1",
|
||||
"@kobalte/core": "^0.13.7",
|
||||
"@kobalte/tailwindcss": "^0.9.0",
|
||||
"@solid-primitives/refs": "^1.0.8",
|
||||
"@solid-primitives/storage": "^4.2.1",
|
||||
"@solid-primitives/upload": "^0.0.117",
|
||||
"@solid-primitives/workers": "^0.3.0",
|
||||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.15.2",
|
||||
"@sqlite.org/sqlite-wasm": "3.47.2-build1",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@sqlite.org/sqlite-wasm": "3.48.0-build2",
|
||||
"@tanstack/solid-table": "^8.20.5",
|
||||
"chart.js": "^4.4.7",
|
||||
"chartjs-chart-wordcloud": "^4.4.4",
|
||||
|
@ -49,9 +50,9 @@
|
|||
"date-fns": "^4.1.0",
|
||||
"kysely": "^0.27.5",
|
||||
"kysely-wasm": "^0.7.0",
|
||||
"lucide-solid": "^0.469.0",
|
||||
"seroval": "^1.1.1",
|
||||
"solid-js": "^1.9.3",
|
||||
"lucide-solid": "^0.473.0",
|
||||
"seroval": "^1.2.0",
|
||||
"solid-js": "^1.9.4",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
|
|
871
pnpm-lock.yaml
generated
871
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -1,12 +1,8 @@
|
|||
import { sql, type NotNull } from "kysely";
|
||||
import { db, kyselyDb, SELF_ID, setDbHash } from "./db";
|
||||
import { cached } from "./lib/db-cache";
|
||||
import { hashString } from "./lib/hash";
|
||||
import { db, kyselyDb, SELF_ID } from "./db";
|
||||
import { cached } from "../lib/db-cache";
|
||||
|
||||
export const loadDb = (
|
||||
statements: string[],
|
||||
progressCallback?: (percentage: number) => void,
|
||||
) => {
|
||||
export const loadDb = (statements: string[], progressCallback?: (percentage: number) => void) => {
|
||||
const length = statements.length;
|
||||
let percentage = 0;
|
||||
|
||||
|
@ -15,20 +11,19 @@ export const loadDb = (
|
|||
const newPercentage = Math.round((i / length) * 100);
|
||||
|
||||
try {
|
||||
db.exec(statement);
|
||||
if (progressCallback && newPercentage !== percentage) {
|
||||
progressCallback(newPercentage);
|
||||
|
||||
if (newPercentage !== percentage) {
|
||||
progressCallback?.(newPercentage);
|
||||
percentage = newPercentage;
|
||||
}
|
||||
|
||||
db.exec(statement);
|
||||
} catch (e) {
|
||||
throw new Error(`statement failed: ${statement}`, {
|
||||
cause: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setDbHash(hashString(statements.join()));
|
||||
};
|
||||
|
||||
const allThreadsOverviewQueryRaw = () =>
|
||||
|
@ -38,15 +33,9 @@ const allThreadsOverviewQueryRaw = () =>
|
|||
(eb) =>
|
||||
eb
|
||||
.selectFrom("message")
|
||||
.select((eb) => [
|
||||
"message.thread_id",
|
||||
eb.fn.countAll().as("message_count"),
|
||||
])
|
||||
.select((eb) => ["message.thread_id", eb.fn.countAll().as("message_count")])
|
||||
.where((eb) => {
|
||||
return eb.and([
|
||||
eb("message.body", "is not", null),
|
||||
eb("message.body", "is not", ""),
|
||||
]);
|
||||
return eb.and([eb("message.body", "is not", null), eb("message.body", "is not", "")]);
|
||||
})
|
||||
.groupBy("message.thread_id")
|
||||
.as("message"),
|
||||
|
@ -100,9 +89,7 @@ const dmPartnerRecipientQueryRaw = (dmId: number) =>
|
|||
"recipient.nickname_joined_name",
|
||||
])
|
||||
.innerJoin("thread", "recipient._id", "thread.recipient_id")
|
||||
.where((eb) =>
|
||||
eb.and([eb("thread._id", "=", dmId), eb("recipient._id", "!=", SELF_ID)]),
|
||||
)
|
||||
.where((eb) => eb.and([eb("thread._id", "=", dmId), eb("recipient._id", "!=", SELF_ID)]))
|
||||
.$narrowType<{
|
||||
_id: number;
|
||||
}>()
|
||||
|
@ -113,25 +100,12 @@ export const dmPartnerRecipientQuery = cached(dmPartnerRecipientQueryRaw);
|
|||
const threadSentMessagesOverviewQueryRaw = (threadId: number) =>
|
||||
kyselyDb
|
||||
.selectFrom("message")
|
||||
.select([
|
||||
"from_recipient_id",
|
||||
sql<string>`datetime(date_sent / 1000, 'unixepoch')`.as(
|
||||
"message_datetime",
|
||||
),
|
||||
])
|
||||
.select(["from_recipient_id", sql<string>`datetime(date_sent / 1000, 'unixepoch')`.as("message_datetime")])
|
||||
.orderBy(["message_datetime"])
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb("body", "is not", null),
|
||||
eb("body", "!=", ""),
|
||||
eb("thread_id", "=", threadId),
|
||||
]),
|
||||
)
|
||||
.where((eb) => eb.and([eb("body", "is not", null), eb("body", "!=", ""), eb("thread_id", "=", threadId)]))
|
||||
.execute();
|
||||
|
||||
export const threadSentMessagesOverviewQuery = cached(
|
||||
threadSentMessagesOverviewQueryRaw,
|
||||
);
|
||||
export const threadSentMessagesOverviewQuery = cached(threadSentMessagesOverviewQueryRaw);
|
||||
|
||||
const threadMostUsedWordsQueryRaw = (threadId: number, limit = 10) =>
|
||||
kyselyDb
|
||||
|
@ -139,20 +113,16 @@ const threadMostUsedWordsQueryRaw = (threadId: number, limit = 10) =>
|
|||
return eb
|
||||
.selectFrom("message")
|
||||
.select([
|
||||
sql`LOWER(substr(body, 1, instr(body || " ", " ") - 1))`.as("word"),
|
||||
sql`(substr(body, instr(body || " ", " ") + 1))`.as("rest"),
|
||||
sql`LOWER(substr(body, 1, instr(body || ' ', ' ') - 1))`.as("word"),
|
||||
sql`substr(body, instr(body || ' ', ' ') + 1)`.as("rest"),
|
||||
])
|
||||
.where((eb) =>
|
||||
eb.and([eb("body", "is not", null), eb("thread_id", "=", threadId)]),
|
||||
)
|
||||
.where((eb) => eb.and([eb("body", "is not", null), eb("thread_id", "=", threadId)]))
|
||||
.unionAll((ebInner) => {
|
||||
return ebInner
|
||||
.selectFrom("words")
|
||||
.select([
|
||||
sql`LOWER(substr(rest, 1, instr(rest || " ", " ") - 1))`.as(
|
||||
"word",
|
||||
),
|
||||
sql`(substr(rest, instr(rest || " ", " ") + 1))`.as("rest"),
|
||||
sql`LOWER(substr(rest, 1, instr(rest || ' ', ' ') - 1))`.as("word"),
|
||||
sql`substr(rest, instr(rest || ' ', ' ') + 1)`.as("rest"),
|
||||
])
|
||||
.where("rest", "<>", "");
|
||||
});
|
708
src/db/db-schema.d.ts
vendored
Normal file
708
src/db/db-schema.d.ts
vendored
Normal file
|
@ -0,0 +1,708 @@
|
|||
/**
|
||||
* This file was generated by kysely-codegen.
|
||||
* Please do not edit it manually.
|
||||
*/
|
||||
|
||||
import type { ColumnType } from "kysely";
|
||||
|
||||
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
|
||||
? ColumnType<S, I | undefined, U>
|
||||
: ColumnType<T, T | undefined, T>;
|
||||
|
||||
export interface Attachment {
|
||||
_id: Generated<number | null>;
|
||||
archive_cdn: Generated<number | null>;
|
||||
archive_media_id: Generated<string | null>;
|
||||
archive_media_name: Generated<string | null>;
|
||||
archive_thumbnail_media_id: Generated<string | null>;
|
||||
archive_transfer_file: Generated<string | null>;
|
||||
archive_transfer_state: Generated<number | null>;
|
||||
attachment_uuid: Generated<string | null>;
|
||||
blur_hash: Generated<string | null>;
|
||||
borderless: Generated<number | null>;
|
||||
caption: Generated<string | null>;
|
||||
cdn_number: Generated<number | null>;
|
||||
content_type: string | null;
|
||||
data_file: string | null;
|
||||
data_hash_end: Generated<string | null>;
|
||||
data_hash_start: Generated<string | null>;
|
||||
data_random: Buffer | null;
|
||||
data_size: number | null;
|
||||
display_order: Generated<number | null>;
|
||||
fast_preflight_id: string | null;
|
||||
file_name: string | null;
|
||||
height: Generated<number | null>;
|
||||
message_id: number | null;
|
||||
offload_restored_at: Generated<number | null>;
|
||||
quote: Generated<number | null>;
|
||||
remote_digest: Buffer | null;
|
||||
remote_incremental_digest: Buffer | null;
|
||||
remote_incremental_digest_chunk_size: Generated<number | null>;
|
||||
remote_iv: Generated<Buffer | null>;
|
||||
remote_key: string | null;
|
||||
remote_location: string | null;
|
||||
sticker_emoji: Generated<string | null>;
|
||||
sticker_id: Generated<number | null>;
|
||||
sticker_pack_id: Generated<string | null>;
|
||||
sticker_pack_key: Generated<string | null>;
|
||||
thumbnail_file: Generated<string | null>;
|
||||
thumbnail_random: Generated<Buffer | null>;
|
||||
thumbnail_restore_state: Generated<number | null>;
|
||||
transfer_file: Generated<string | null>;
|
||||
transfer_state: number | null;
|
||||
transform_properties: Generated<string | null>;
|
||||
upload_timestamp: Generated<number | null>;
|
||||
video_gif: Generated<number | null>;
|
||||
voice_note: Generated<number | null>;
|
||||
width: Generated<number | null>;
|
||||
}
|
||||
|
||||
export interface AvatarPicker {
|
||||
_id: Generated<number | null>;
|
||||
avatar: Buffer;
|
||||
group_id: Generated<string | null>;
|
||||
last_used: Generated<number | null>;
|
||||
}
|
||||
|
||||
export interface Call {
|
||||
_id: number | null;
|
||||
call_id: number;
|
||||
deletion_timestamp: Generated<number | null>;
|
||||
direction: number;
|
||||
event: number;
|
||||
group_call_active: Generated<number | null>;
|
||||
local_joined: Generated<number | null>;
|
||||
message_id: Generated<number | null>;
|
||||
peer: number;
|
||||
read: Generated<number | null>;
|
||||
ringer: Generated<number | null>;
|
||||
timestamp: number;
|
||||
type: number;
|
||||
}
|
||||
|
||||
export interface CallLink {
|
||||
_id: number | null;
|
||||
admin_key: Buffer | null;
|
||||
deletion_timestamp: Generated<number>;
|
||||
expiration: number;
|
||||
name: string;
|
||||
recipient_id: number | null;
|
||||
restrictions: number;
|
||||
revoked: number;
|
||||
room_id: string;
|
||||
root_key: Buffer | null;
|
||||
}
|
||||
|
||||
export interface Cds {
|
||||
_id: number | null;
|
||||
e164: string;
|
||||
last_seen_at: Generated<number | null>;
|
||||
}
|
||||
|
||||
export interface ChatColors {
|
||||
_id: Generated<number | null>;
|
||||
chat_colors: Buffer | null;
|
||||
}
|
||||
|
||||
export interface ChatFolder {
|
||||
_id: Generated<number | null>;
|
||||
folder_type: Generated<number | null>;
|
||||
is_muted: Generated<number | null>;
|
||||
name: Generated<string | null>;
|
||||
position: Generated<number | null>;
|
||||
show_groups: Generated<number | null>;
|
||||
show_individual: Generated<number | null>;
|
||||
show_muted: Generated<number | null>;
|
||||
show_unread: Generated<number | null>;
|
||||
}
|
||||
|
||||
export interface ChatFolderMembership {
|
||||
_id: Generated<number | null>;
|
||||
chat_folder_id: number;
|
||||
membership_type: Generated<number | null>;
|
||||
thread_id: number;
|
||||
}
|
||||
|
||||
export interface DistributionList {
|
||||
_id: Generated<number | null>;
|
||||
allows_replies: Generated<number | null>;
|
||||
deletion_timestamp: Generated<number | null>;
|
||||
distribution_id: string;
|
||||
is_unknown: Generated<number | null>;
|
||||
name: string;
|
||||
privacy_mode: Generated<number | null>;
|
||||
recipient_id: number | null;
|
||||
}
|
||||
|
||||
export interface DistributionListMember {
|
||||
_id: Generated<number | null>;
|
||||
list_id: number;
|
||||
privacy_mode: Generated<number | null>;
|
||||
recipient_id: number;
|
||||
}
|
||||
|
||||
export interface DonationReceipt {
|
||||
_id: Generated<number | null>;
|
||||
amount: string;
|
||||
currency: string;
|
||||
receipt_date: number;
|
||||
receipt_type: string;
|
||||
subscription_level: number;
|
||||
}
|
||||
|
||||
export interface Drafts {
|
||||
_id: number | null;
|
||||
thread_id: number | null;
|
||||
type: string | null;
|
||||
value: string | null;
|
||||
}
|
||||
|
||||
export interface EmojiSearch {
|
||||
_id: number | null;
|
||||
emoji: string;
|
||||
label: string;
|
||||
rank: Generated<number | null>;
|
||||
}
|
||||
|
||||
export interface GroupMembership {
|
||||
_id: number | null;
|
||||
endorsement: Generated<Buffer | null>;
|
||||
group_id: string;
|
||||
recipient_id: number;
|
||||
}
|
||||
|
||||
export interface GroupReceipts {
|
||||
_id: number | null;
|
||||
address: number | null;
|
||||
mms_id: number | null;
|
||||
status: number | null;
|
||||
timestamp: number | null;
|
||||
unidentified: Generated<number | null>;
|
||||
}
|
||||
|
||||
export interface Groups {
|
||||
_id: number | null;
|
||||
active: Generated<number | null>;
|
||||
avatar_content_type: Generated<string | null>;
|
||||
avatar_digest: Generated<Buffer | null>;
|
||||
avatar_id: Generated<number | null>;
|
||||
avatar_key: Generated<Buffer | null>;
|
||||
decrypted_group: Generated<Buffer | null>;
|
||||
distribution_id: Generated<string | null>;
|
||||
expected_v2_id: Generated<string | null>;
|
||||
group_id: string;
|
||||
group_send_endorsements_expiration: Generated<number | null>;
|
||||
last_force_update_timestamp: Generated<number | null>;
|
||||
master_key: Generated<Buffer | null>;
|
||||
mms: Generated<number | null>;
|
||||
recipient_id: number;
|
||||
revision: Generated<Buffer | null>;
|
||||
show_as_story_state: Generated<number | null>;
|
||||
timestamp: Generated<number | null>;
|
||||
title: Generated<string | null>;
|
||||
unmigrated_v1_members: Generated<string | null>;
|
||||
}
|
||||
|
||||
export interface Identities {
|
||||
_id: Generated<number | null>;
|
||||
address: number | null;
|
||||
first_use: Generated<number | null>;
|
||||
identity_key: string | null;
|
||||
nonblocking_approval: Generated<number | null>;
|
||||
timestamp: Generated<number | null>;
|
||||
verified: Generated<number | null>;
|
||||
}
|
||||
|
||||
export interface InAppPayment {
|
||||
_id: number | null;
|
||||
data: Buffer;
|
||||
end_of_period: Generated<number | null>;
|
||||
inserted_at: number;
|
||||
notified: Generated<number | null>;
|
||||
state: number;
|
||||
subscriber_id: string | null;
|
||||
type: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
export interface InAppPaymentSubscriber {
|
||||
_id: number | null;
|
||||
currency_code: string;
|
||||
payment_method_type: Generated<number | null>;
|
||||
requires_cancel: Generated<number | null>;
|
||||
subscriber_id: string;
|
||||
type: number;
|
||||
}
|
||||
|
||||
export interface KyberPrekey {
|
||||
_id: number | null;
|
||||
account_id: string;
|
||||
key_id: number;
|
||||
last_resort: number;
|
||||
serialized: Buffer;
|
||||
stale_timestamp: Generated<number>;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface Mention {
|
||||
_id: Generated<number | null>;
|
||||
message_id: number | null;
|
||||
range_length: number | null;
|
||||
range_start: number | null;
|
||||
recipient_id: number | null;
|
||||
thread_id: number | null;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
_id: Generated<number | null>;
|
||||
body: string | null;
|
||||
ct_l: string | null;
|
||||
date_received: number;
|
||||
date_sent: number;
|
||||
date_server: Generated<number | null>;
|
||||
exp: number | null;
|
||||
expire_started: Generated<number | null>;
|
||||
expire_timer_version: Generated<number>;
|
||||
expires_in: Generated<number | null>;
|
||||
export_state: Generated<Buffer | null>;
|
||||
exported: Generated<number | null>;
|
||||
from_device_id: number | null;
|
||||
from_recipient_id: number;
|
||||
has_delivery_receipt: Generated<number | null>;
|
||||
has_read_receipt: Generated<number | null>;
|
||||
latest_revision_id: Generated<number | null>;
|
||||
link_previews: Generated<string | null>;
|
||||
m_size: number | null;
|
||||
m_type: number | null;
|
||||
mentions_self: Generated<number | null>;
|
||||
message_extras: Generated<Buffer | null>;
|
||||
message_ranges: Generated<Buffer | null>;
|
||||
mismatched_identities: Generated<string | null>;
|
||||
network_failures: Generated<string | null>;
|
||||
notified: Generated<number | null>;
|
||||
notified_timestamp: Generated<number | null>;
|
||||
original_message_id: Generated<number | null>;
|
||||
parent_story_id: Generated<number | null>;
|
||||
quote_author: Generated<number | null>;
|
||||
quote_body: Generated<string | null>;
|
||||
quote_id: Generated<number | null>;
|
||||
quote_mentions: Generated<Buffer | null>;
|
||||
quote_missing: Generated<number | null>;
|
||||
quote_type: Generated<number | null>;
|
||||
reactions_last_seen: Generated<number | null>;
|
||||
reactions_unread: Generated<number | null>;
|
||||
read: Generated<number | null>;
|
||||
receipt_timestamp: Generated<number | null>;
|
||||
remote_deleted: Generated<number | null>;
|
||||
revision_number: Generated<number | null>;
|
||||
scheduled_date: Generated<number | null>;
|
||||
server_guid: Generated<string | null>;
|
||||
shared_contacts: Generated<string | null>;
|
||||
st: number | null;
|
||||
story_type: Generated<number | null>;
|
||||
subscription_id: Generated<number | null>;
|
||||
thread_id: number;
|
||||
to_recipient_id: number;
|
||||
tr_id: string | null;
|
||||
type: number;
|
||||
unidentified: Generated<number | null>;
|
||||
view_once: Generated<number | null>;
|
||||
viewed: Generated<number | null>;
|
||||
}
|
||||
|
||||
export interface MessageFts {
|
||||
body: string | null;
|
||||
thread_id: string | null;
|
||||
}
|
||||
|
||||
export interface MessageFtsConfig {
|
||||
k: string;
|
||||
v: string | null;
|
||||
}
|
||||
|
||||
export interface MessageFtsData {
|
||||
block: Buffer | null;
|
||||
id: number | null;
|
||||
}
|
||||
|
||||
export interface MessageFtsDocsize {
|
||||
id: number | null;
|
||||
sz: Buffer | null;
|
||||
}
|
||||
|
||||
export interface MessageFtsIdx {
|
||||
pgno: string | null;
|
||||
segid: string;
|
||||
term: string;
|
||||
}
|
||||
|
||||
export interface MslMessage {
|
||||
_id: number | null;
|
||||
message_id: number;
|
||||
payload_id: number;
|
||||
}
|
||||
|
||||
export interface MslPayload {
|
||||
_id: number | null;
|
||||
content: Buffer;
|
||||
content_hint: number;
|
||||
date_sent: number;
|
||||
urgent: Generated<number>;
|
||||
}
|
||||
|
||||
export interface MslRecipient {
|
||||
_id: number | null;
|
||||
device: number;
|
||||
payload_id: number;
|
||||
recipient_id: number;
|
||||
}
|
||||
|
||||
export interface NameCollision {
|
||||
_id: Generated<number | null>;
|
||||
dismissed: Generated<number | null>;
|
||||
hash: Generated<string | null>;
|
||||
thread_id: number;
|
||||
}
|
||||
|
||||
export interface NameCollisionMembership {
|
||||
_id: Generated<number | null>;
|
||||
collision_id: number;
|
||||
profile_change_details: Generated<Buffer | null>;
|
||||
recipient_id: number;
|
||||
}
|
||||
|
||||
export interface NotificationProfile {
|
||||
_id: Generated<number | null>;
|
||||
allow_all_calls: Generated<number>;
|
||||
allow_all_mentions: Generated<number>;
|
||||
color: string;
|
||||
created_at: number;
|
||||
emoji: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface NotificationProfileAllowedMembers {
|
||||
_id: Generated<number | null>;
|
||||
notification_profile_id: number;
|
||||
recipient_id: number;
|
||||
}
|
||||
|
||||
export interface NotificationProfileSchedule {
|
||||
_id: Generated<number | null>;
|
||||
days_enabled: string;
|
||||
enabled: Generated<number>;
|
||||
end: number;
|
||||
notification_profile_id: number;
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface OneTimePrekeys {
|
||||
_id: number | null;
|
||||
account_id: string;
|
||||
key_id: number;
|
||||
private_key: string;
|
||||
public_key: string;
|
||||
stale_timestamp: Generated<number>;
|
||||
}
|
||||
|
||||
export interface Payments {
|
||||
_id: number | null;
|
||||
amount: Buffer;
|
||||
block_index: Generated<number | null>;
|
||||
block_timestamp: Generated<number | null>;
|
||||
direction: number | null;
|
||||
failure_reason: number | null;
|
||||
fee: Buffer;
|
||||
note: Generated<string | null>;
|
||||
payment_metadata: Generated<Buffer | null>;
|
||||
receipt: Generated<Buffer | null>;
|
||||
receipt_public_key: Generated<string | null>;
|
||||
recipient: Generated<number | null>;
|
||||
recipient_address: Generated<string | null>;
|
||||
seen: number | null;
|
||||
state: number | null;
|
||||
timestamp: number | null;
|
||||
transaction_record: Generated<Buffer | null>;
|
||||
uuid: Generated<string | null>;
|
||||
}
|
||||
|
||||
export interface PendingPniSignatureMessage {
|
||||
_id: number | null;
|
||||
device_id: number;
|
||||
recipient_id: number;
|
||||
sent_timestamp: number;
|
||||
}
|
||||
|
||||
export interface PendingRetryReceipts {
|
||||
_id: Generated<number | null>;
|
||||
author: string;
|
||||
device: number;
|
||||
received_timestamp: string;
|
||||
sent_timestamp: number;
|
||||
thread_id: number;
|
||||
}
|
||||
|
||||
export interface Reaction {
|
||||
_id: number | null;
|
||||
author_id: number;
|
||||
date_received: number;
|
||||
date_sent: number;
|
||||
emoji: string;
|
||||
message_id: number;
|
||||
}
|
||||
|
||||
export interface Recipient {
|
||||
_id: Generated<number | null>;
|
||||
about: Generated<string | null>;
|
||||
about_emoji: Generated<string | null>;
|
||||
aci: Generated<string | null>;
|
||||
avatar_color: Generated<string | null>;
|
||||
badges: Generated<Buffer | null>;
|
||||
blocked: Generated<number | null>;
|
||||
call_link_room_id: Generated<string | null>;
|
||||
call_ringtone: Generated<string | null>;
|
||||
call_vibrate: Generated<number | null>;
|
||||
capabilities: Generated<number | null>;
|
||||
chat_colors: Generated<Buffer | null>;
|
||||
custom_chat_colors_id: Generated<number | null>;
|
||||
distribution_list_id: Generated<number | null>;
|
||||
e164: Generated<string | null>;
|
||||
email: Generated<string | null>;
|
||||
extras: Generated<Buffer | null>;
|
||||
group_id: Generated<string | null>;
|
||||
groups_in_common: Generated<number | null>;
|
||||
hidden: Generated<number | null>;
|
||||
last_profile_fetch: Generated<number | null>;
|
||||
last_session_reset: Generated<Buffer | null>;
|
||||
mention_setting: Generated<number | null>;
|
||||
message_expiration_time: Generated<number | null>;
|
||||
message_expiration_time_version: Generated<number>;
|
||||
message_ringtone: Generated<string | null>;
|
||||
message_vibrate: Generated<number | null>;
|
||||
mute_until: Generated<number | null>;
|
||||
needs_pni_signature: Generated<number | null>;
|
||||
nickname_family_name: Generated<string | null>;
|
||||
nickname_given_name: Generated<string | null>;
|
||||
nickname_joined_name: Generated<string | null>;
|
||||
note: Generated<string | null>;
|
||||
notification_channel: Generated<string | null>;
|
||||
phone_number_discoverable: Generated<number | null>;
|
||||
phone_number_sharing: Generated<number | null>;
|
||||
pni: Generated<string | null>;
|
||||
pni_signature_verified: Generated<number | null>;
|
||||
profile_avatar: Generated<string | null>;
|
||||
profile_family_name: Generated<string | null>;
|
||||
profile_given_name: Generated<string | null>;
|
||||
profile_joined_name: Generated<string | null>;
|
||||
profile_key: Generated<string | null>;
|
||||
profile_key_credential: Generated<string | null>;
|
||||
profile_sharing: Generated<number | null>;
|
||||
registered: Generated<number | null>;
|
||||
reporting_token: Generated<Buffer | null>;
|
||||
sealed_sender_mode: Generated<number | null>;
|
||||
storage_service_id: Generated<string | null>;
|
||||
storage_service_proto: Generated<string | null>;
|
||||
system_contact_uri: Generated<string | null>;
|
||||
system_family_name: Generated<string | null>;
|
||||
system_given_name: Generated<string | null>;
|
||||
system_info_pending: Generated<number | null>;
|
||||
system_joined_name: Generated<string | null>;
|
||||
system_nickname: Generated<string | null>;
|
||||
system_phone_label: Generated<string | null>;
|
||||
system_phone_type: Generated<number | null>;
|
||||
system_photo_uri: Generated<string | null>;
|
||||
type: Generated<number | null>;
|
||||
unregistered_timestamp: Generated<number | null>;
|
||||
username: Generated<string | null>;
|
||||
wallpaper: Generated<Buffer | null>;
|
||||
wallpaper_uri: Generated<string | null>;
|
||||
}
|
||||
|
||||
export interface RemappedRecipients {
|
||||
_id: Generated<number | null>;
|
||||
new_id: number | null;
|
||||
old_id: number | null;
|
||||
}
|
||||
|
||||
export interface RemappedThreads {
|
||||
_id: Generated<number | null>;
|
||||
new_id: number | null;
|
||||
old_id: number | null;
|
||||
}
|
||||
|
||||
export interface RemoteMegaphone {
|
||||
_id: number | null;
|
||||
body: string;
|
||||
conditional_id: string | null;
|
||||
countries: string | null;
|
||||
dont_show_after: number;
|
||||
dont_show_before: number;
|
||||
finished_at: Generated<number | null>;
|
||||
image_uri: Generated<string | null>;
|
||||
image_url: string | null;
|
||||
minimum_version: number;
|
||||
primary_action_data: Generated<string | null>;
|
||||
primary_action_id: string | null;
|
||||
primary_action_text: string | null;
|
||||
priority: number;
|
||||
secondary_action_data: Generated<string | null>;
|
||||
secondary_action_id: string | null;
|
||||
secondary_action_text: string | null;
|
||||
seen_count: Generated<number | null>;
|
||||
show_for_days: number;
|
||||
shown_at: Generated<number | null>;
|
||||
snoozed_at: Generated<number | null>;
|
||||
title: string;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export interface SenderKeys {
|
||||
_id: Generated<number | null>;
|
||||
address: string;
|
||||
created_at: number;
|
||||
device: number;
|
||||
distribution_id: string;
|
||||
record: Buffer;
|
||||
}
|
||||
|
||||
export interface SenderKeyShared {
|
||||
_id: Generated<number | null>;
|
||||
address: string;
|
||||
device: number;
|
||||
distribution_id: string;
|
||||
timestamp: Generated<number | null>;
|
||||
}
|
||||
|
||||
export interface Sessions {
|
||||
_id: Generated<number | null>;
|
||||
account_id: string;
|
||||
address: string;
|
||||
device: number;
|
||||
record: Buffer;
|
||||
}
|
||||
|
||||
export interface SignedPrekeys {
|
||||
_id: number | null;
|
||||
account_id: string;
|
||||
key_id: number;
|
||||
private_key: string;
|
||||
public_key: string;
|
||||
signature: string;
|
||||
timestamp: Generated<number | null>;
|
||||
}
|
||||
|
||||
export interface Sticker {
|
||||
_id: Generated<number | null>;
|
||||
content_type: Generated<string | null>;
|
||||
cover: number | null;
|
||||
emoji: string;
|
||||
file_length: number | null;
|
||||
file_path: string;
|
||||
file_random: Buffer | null;
|
||||
installed: number | null;
|
||||
last_used: number | null;
|
||||
pack_author: string;
|
||||
pack_id: string;
|
||||
pack_key: string;
|
||||
pack_order: number | null;
|
||||
pack_title: string;
|
||||
sticker_id: number | null;
|
||||
}
|
||||
|
||||
export interface StorageKey {
|
||||
_id: Generated<number | null>;
|
||||
key: string | null;
|
||||
type: number | null;
|
||||
}
|
||||
|
||||
export interface StorySends {
|
||||
_id: number | null;
|
||||
allows_replies: number;
|
||||
distribution_id: string;
|
||||
message_id: number;
|
||||
recipient_id: number;
|
||||
sent_timestamp: number;
|
||||
}
|
||||
|
||||
export interface Thread {
|
||||
_id: Generated<number | null>;
|
||||
active: Generated<number | null>;
|
||||
archived: Generated<number | null>;
|
||||
date: Generated<number | null>;
|
||||
error: Generated<number | null>;
|
||||
expires_in: Generated<number | null>;
|
||||
has_delivery_receipt: Generated<number | null>;
|
||||
has_read_receipt: Generated<number | null>;
|
||||
has_sent: Generated<number | null>;
|
||||
last_scrolled: Generated<number | null>;
|
||||
last_seen: Generated<number | null>;
|
||||
meaningful_messages: Generated<number | null>;
|
||||
pinned: Generated<number | null>;
|
||||
read: Generated<number | null>;
|
||||
recipient_id: number;
|
||||
snippet: string | null;
|
||||
snippet_content_type: Generated<string | null>;
|
||||
snippet_extras: Generated<string | null>;
|
||||
snippet_message_extras: Generated<Buffer | null>;
|
||||
snippet_type: Generated<number | null>;
|
||||
snippet_uri: Generated<string | null>;
|
||||
status: Generated<number | null>;
|
||||
type: Generated<number | null>;
|
||||
unread_count: Generated<number | null>;
|
||||
unread_self_mention_count: Generated<number | null>;
|
||||
}
|
||||
|
||||
export interface DB {
|
||||
attachment: Attachment;
|
||||
avatar_picker: AvatarPicker;
|
||||
call: Call;
|
||||
call_link: CallLink;
|
||||
cds: Cds;
|
||||
chat_colors: ChatColors;
|
||||
chat_folder: ChatFolder;
|
||||
chat_folder_membership: ChatFolderMembership;
|
||||
distribution_list: DistributionList;
|
||||
distribution_list_member: DistributionListMember;
|
||||
donation_receipt: DonationReceipt;
|
||||
drafts: Drafts;
|
||||
emoji_search: EmojiSearch;
|
||||
group_membership: GroupMembership;
|
||||
group_receipts: GroupReceipts;
|
||||
groups: Groups;
|
||||
identities: Identities;
|
||||
in_app_payment: InAppPayment;
|
||||
in_app_payment_subscriber: InAppPaymentSubscriber;
|
||||
kyber_prekey: KyberPrekey;
|
||||
mention: Mention;
|
||||
message: Message;
|
||||
message_fts: MessageFts;
|
||||
message_fts_config: MessageFtsConfig;
|
||||
message_fts_data: MessageFtsData;
|
||||
message_fts_docsize: MessageFtsDocsize;
|
||||
message_fts_idx: MessageFtsIdx;
|
||||
msl_message: MslMessage;
|
||||
msl_payload: MslPayload;
|
||||
msl_recipient: MslRecipient;
|
||||
name_collision: NameCollision;
|
||||
name_collision_membership: NameCollisionMembership;
|
||||
notification_profile: NotificationProfile;
|
||||
notification_profile_allowed_members: NotificationProfileAllowedMembers;
|
||||
notification_profile_schedule: NotificationProfileSchedule;
|
||||
one_time_prekeys: OneTimePrekeys;
|
||||
payments: Payments;
|
||||
pending_pni_signature_message: PendingPniSignatureMessage;
|
||||
pending_retry_receipts: PendingRetryReceipts;
|
||||
reaction: Reaction;
|
||||
recipient: Recipient;
|
||||
remapped_recipients: RemappedRecipients;
|
||||
remapped_threads: RemappedThreads;
|
||||
remote_megaphone: RemoteMegaphone;
|
||||
sender_key_shared: SenderKeyShared;
|
||||
sender_keys: SenderKeys;
|
||||
sessions: Sessions;
|
||||
signed_prekeys: SignedPrekeys;
|
||||
sticker: Sticker;
|
||||
storage_key: StorageKey;
|
||||
story_sends: StorySends;
|
||||
thread: Thread;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { makePersisted } from "@solid-primitives/storage";
|
||||
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
||||
import { Kysely } from "kysely";
|
||||
import type { DB } from "kysely-codegen";
|
||||
import type { DB } from "./db-schema";
|
||||
import { OfficialWasmDialect } from "kysely-wasm";
|
||||
import { createSignal } from "solid-js";
|
||||
|
2
src/db/index.ts
Normal file
2
src/db/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./db";
|
||||
export * from "./db-queries";
|
|
@ -1,9 +1,13 @@
|
|||
/* @refresh reload */
|
||||
import { MetaProvider } from "@solidjs/meta";
|
||||
import { Router } from "@solidjs/router";
|
||||
import { Router, useNavigate } from "@solidjs/router";
|
||||
import { render } from "solid-js/web";
|
||||
import { createEffect, enableScheduling } from "solid-js";
|
||||
|
||||
import App from "./App";
|
||||
import { db } from "./db/db";
|
||||
|
||||
enableScheduling();
|
||||
|
||||
const root = document.getElementById("root");
|
||||
|
||||
|
@ -19,18 +23,18 @@ if (root) {
|
|||
<div class="mx-auto max-w-screen-2xl">
|
||||
<MetaProvider>
|
||||
<Router
|
||||
// root={(props) => {
|
||||
// const navigate = useNavigate();
|
||||
// const { pathname } = props.location;
|
||||
root={(props) => {
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = props.location;
|
||||
|
||||
// createEffect(() => {
|
||||
// if (!db() && pathname !== "/") {
|
||||
// navigate("/");
|
||||
// }
|
||||
// });
|
||||
createEffect(() => {
|
||||
if (!db && pathname !== "/") {
|
||||
navigate("/");
|
||||
}
|
||||
});
|
||||
|
||||
// return props.children;
|
||||
// }}
|
||||
return props.children;
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</Router>
|
||||
|
|
|
@ -1,47 +1,44 @@
|
|||
import { deserialize, serialize } from "seroval";
|
||||
import { createEffect, createMemo, createRoot, on } from "solid-js";
|
||||
import { dbHash } from "~/db";
|
||||
import {} from "solid-js";
|
||||
import { hashString } from "./hash";
|
||||
|
||||
export const DATABASE_HASH_PREFIX = "database";
|
||||
|
||||
// clear the cache on new session so that selecting a different database does not result in wrong cache entries
|
||||
const clearDbCache = () => {
|
||||
for (let i = 0, len = localStorage.length; i < len; i++) {
|
||||
const key = localStorage.key(i);
|
||||
// const clearDbCache = () => {
|
||||
// for (let i = 0, len = localStorage.length; i < len; i++) {
|
||||
// const key = localStorage.key(i);
|
||||
|
||||
if (key?.startsWith(DATABASE_HASH_PREFIX)) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
};
|
||||
// if (key?.startsWith(DATABASE_HASH_PREFIX)) {
|
||||
// localStorage.removeItem(key);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
let prevDbHash = dbHash();
|
||||
// let prevDbHash = dbHash();
|
||||
|
||||
createRoot(() => {
|
||||
createEffect(() => {
|
||||
on(
|
||||
dbHash,
|
||||
(currentDbHash) => {
|
||||
if (currentDbHash && currentDbHash !== prevDbHash) {
|
||||
prevDbHash = currentDbHash;
|
||||
clearDbCache();
|
||||
}
|
||||
},
|
||||
{
|
||||
defer: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
// createRoot(() => {
|
||||
// createEffect(() => {
|
||||
// on(
|
||||
// dbHash,
|
||||
// (currentDbHash) => {
|
||||
// if (currentDbHash && currentDbHash !== prevDbHash) {
|
||||
// prevDbHash = currentDbHash;
|
||||
// clearDbCache();
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// defer: true,
|
||||
// },
|
||||
// );
|
||||
// });
|
||||
// });
|
||||
|
||||
class LocalStorageCacheAdapter {
|
||||
keys = new Set<string>(
|
||||
Object.keys(localStorage).filter((key) => key.startsWith(this.prefix)),
|
||||
);
|
||||
keys = new Set<string>(Object.keys(localStorage).filter((key) => key.startsWith(this.prefix)));
|
||||
prefix = "database";
|
||||
// TODO: real way of detecting if the db is loaded, on loading the db and opfs (if persisted db?)
|
||||
#dbLoaded = createMemo(() => !!dbHash());
|
||||
// #dbLoaded = createMemo(() => !!dbHash());
|
||||
|
||||
#createKey(cacheName: string, key: string): string {
|
||||
return `${this.prefix}-${cacheName}-${key}`;
|
||||
|
@ -54,10 +51,7 @@ class LocalStorageCacheAdapter {
|
|||
try {
|
||||
localStorage.setItem(fullKey, serialize({ isPromise, value }));
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
error instanceof DOMException &&
|
||||
error.name === "QUOTA_EXCEEDED_ERR"
|
||||
) {
|
||||
if (error instanceof DOMException && error.name === "QUOTA_EXCEEDED_ERR") {
|
||||
console.error("Storage quota exceeded, not caching new function calls");
|
||||
} else {
|
||||
console.error(error);
|
||||
|
@ -66,13 +60,13 @@ class LocalStorageCacheAdapter {
|
|||
}
|
||||
|
||||
has(cacheName: string, key: string): boolean {
|
||||
if (this.#dbLoaded()) {
|
||||
return this.keys.has(this.#createKey(cacheName, key));
|
||||
}
|
||||
// if (this.#dbLoaded()) {
|
||||
return this.keys.has(this.#createKey(cacheName, key));
|
||||
// }
|
||||
|
||||
console.info("No database loaded");
|
||||
// console.info("No database loaded");
|
||||
|
||||
return false;
|
||||
// return false;
|
||||
}
|
||||
|
||||
get<R>(
|
||||
|
@ -84,18 +78,18 @@ class LocalStorageCacheAdapter {
|
|||
value: R;
|
||||
}
|
||||
| undefined {
|
||||
if (this.#dbLoaded()) {
|
||||
const item = localStorage.getItem(this.#createKey(cacheName, key));
|
||||
// if (this.#dbLoaded()) {
|
||||
const item = localStorage.getItem(this.#createKey(cacheName, key));
|
||||
|
||||
if (item) {
|
||||
return deserialize(item) as {
|
||||
isPromise: boolean;
|
||||
value: R;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
console.info("No database loaded");
|
||||
if (item) {
|
||||
return deserialize(item) as {
|
||||
isPromise: boolean;
|
||||
value: R;
|
||||
};
|
||||
}
|
||||
// } else {
|
||||
// console.info("No database loaded");
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,10 +122,7 @@ const createHashKey = (...args: unknown[]) => {
|
|||
return hashString(stringToHash);
|
||||
};
|
||||
|
||||
export const cached = <T extends unknown[], R, TT>(
|
||||
fn: (...args: T) => R,
|
||||
self?: ThisType<TT>,
|
||||
): ((...args: T) => R) => {
|
||||
export const cached = <T extends unknown[], R, TT>(fn: (...args: T) => R, self?: ThisType<TT>): ((...args: T) => R) => {
|
||||
const cacheName = hashString(fn.toString()).toString();
|
||||
|
||||
return (...args: T) => {
|
||||
|
@ -140,11 +131,7 @@ export const cached = <T extends unknown[], R, TT>(
|
|||
const cachedValue = cache.get<R>(cacheName, cacheKey);
|
||||
|
||||
if (cachedValue) {
|
||||
return (
|
||||
cachedValue.isPromise
|
||||
? Promise.resolve(cachedValue.value)
|
||||
: cachedValue.value
|
||||
) as R;
|
||||
return (cachedValue.isPromise ? Promise.resolve(cachedValue.value) : cachedValue.value) as R;
|
||||
}
|
||||
|
||||
let newValue: R;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Suspense, type Component } from "solid-js";
|
||||
import { createAsync, type RoutePreloadFunc, type RouteSectionProps } from "@solidjs/router";
|
||||
|
||||
import { dmPartnerRecipientQuery, threadMostUsedWordsQuery, threadSentMessagesOverviewQuery } from "~/db-queries";
|
||||
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";
|
||||
|
@ -15,7 +15,6 @@ 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 { SELF_ID } from "~/db";
|
||||
import { Flex } from "~/components/ui/flex";
|
||||
|
||||
const getDmIdData = (dmId: number) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { ChartData } from "chart.js";
|
||||
import { Show, type Accessor, type Component } from "solid-js";
|
||||
import { WordCloudChart } from "~/components/ui/charts";
|
||||
import type { threadMostUsedWordsQuery } from "~/db-queries";
|
||||
import type { threadMostUsedWordsQuery } from "~/db";
|
||||
|
||||
const maxWordSize = 100;
|
||||
|
||||
|
|
|
@ -1,41 +1,60 @@
|
|||
import { useNavigate, type RouteSectionProps } from "@solidjs/router";
|
||||
import { createSignal, Show, type Component, type JSX } from "solid-js";
|
||||
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 { db } from "~/db";
|
||||
import { loadDb } from "~/db-queries";
|
||||
import { Progress, ProgressLabel, ProgressValueLabel } from "~/components/ui/progress";
|
||||
import { loadDb } from "~/db/db-queries";
|
||||
import { decryptBackup } from "~/lib/decryptor";
|
||||
import { createDropzone, createFileUploader } from "@solid-primitives/upload";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { TextField, TextFieldInput, TextFieldLabel } from "~/components/ui/text-field";
|
||||
|
||||
export const Home: Component<RouteSectionProps> = () => {
|
||||
const [decryptionProgress, setDecryptionProgress] = createSignal<number>();
|
||||
const [isLoadingDatabase, setIsLoadingDatabase] = createSignal(false);
|
||||
const [passphrase, setPassphrase] = createSignal("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onFileChange: JSX.ChangeEventHandler<HTMLInputElement, Event> = (
|
||||
event,
|
||||
) => {
|
||||
const file = event.currentTarget.files?.[0];
|
||||
const fileUploader = createFileUploader({
|
||||
accept: ".backup",
|
||||
multiple: false,
|
||||
});
|
||||
|
||||
const dropzone = createDropzone({
|
||||
onDrop: (files) => {
|
||||
const file = files.at(0);
|
||||
|
||||
if (file?.name.endsWith(".backup")) {
|
||||
setBackupFile(file.file);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [passphrase, setPassphrase] = createSignal("");
|
||||
const [backupFile, setBackupFile] = createSignal<File>();
|
||||
|
||||
const [decryptionProgress, setDecryptionProgress] = createSignal<number>();
|
||||
const [loadingProgress, setLoadingProgress] = createSignal<number>();
|
||||
// const [isLoadingDatabase, setIsLoadingDatabase] = createSignal(false);
|
||||
|
||||
const onSubmit: JSX.EventHandler<HTMLFormElement, SubmitEvent> = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const currentBackupFile = backupFile();
|
||||
const currentPassphrase = passphrase();
|
||||
|
||||
if (file && currentPassphrase) {
|
||||
decryptBackup(file, currentPassphrase, setDecryptionProgress)
|
||||
if (currentBackupFile && currentPassphrase) {
|
||||
decryptBackup(currentBackupFile, currentPassphrase, setDecryptionProgress)
|
||||
.then((result) => {
|
||||
setDecryptionProgress(undefined);
|
||||
setIsLoadingDatabase(true);
|
||||
// setIsLoadingDatabase(true);
|
||||
setLoadingProgress(0);
|
||||
|
||||
setTimeout(() => {
|
||||
loadDb(result.database_statements);
|
||||
loadDb(result.database_statements, (newValue) => (console.log(newValue), setLoadingProgress(newValue)));
|
||||
|
||||
setIsLoadingDatabase(false);
|
||||
// setIsLoadingDatabase(false);
|
||||
setLoadingProgress(undefined);
|
||||
|
||||
navigate("/overview");
|
||||
}, 0);
|
||||
|
@ -53,9 +72,10 @@ export const Home: Component<RouteSectionProps> = () => {
|
|||
flexDirection="col"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
class="fixed inset-0 backdrop-blur-lg backdrop-filter gap-y-8"
|
||||
class="fixed inset-0 gap-y-8 backdrop-blur-lg backdrop-filter"
|
||||
classList={{
|
||||
hidden: decryptionProgress() === undefined && !isLoadingDatabase(),
|
||||
// hidden: decryptionProgress() === undefined && !isLoadingDatabase(),
|
||||
hidden: decryptionProgress() === undefined && loadingProgress() === undefined,
|
||||
}}
|
||||
>
|
||||
<Show when={decryptionProgress() !== undefined}>
|
||||
|
@ -73,19 +93,62 @@ export const Home: Component<RouteSectionProps> = () => {
|
|||
</div>
|
||||
</Progress>
|
||||
</Show>
|
||||
<Show when={isLoadingDatabase()}>
|
||||
<Show when={loadingProgress() !== undefined}>
|
||||
{/* <p class="font-bold text-2xl">Loading database</p>
|
||||
<p class="text-muted-foreground">This can take some time</p> */}
|
||||
<p class="font-bold text-2xl">Loading database</p>
|
||||
<Progress
|
||||
value={loadingProgress()}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
getValueLabel={({ value }) => `${value}%`}
|
||||
class="w-[300px] space-y-1"
|
||||
>
|
||||
<div class="flex justify-between">
|
||||
<ProgressLabel>Loading...</ProgressLabel>
|
||||
<ProgressValueLabel />
|
||||
</div>
|
||||
</Progress>
|
||||
</Show>
|
||||
</Flex>
|
||||
</Portal>
|
||||
<Title>Signal stats</Title>
|
||||
<div>
|
||||
<input
|
||||
type="password"
|
||||
onChange={(event) => setPassphrase(event.currentTarget.value)}
|
||||
/>
|
||||
<input type="file" accept=".backup" onChange={onFileChange} />
|
||||
</div>
|
||||
<form class="flex flex-col gap-y-8 p-8" onSubmit={onSubmit}>
|
||||
<TextField onChange={(value) => setPassphrase(value)}>
|
||||
<TextFieldLabel>Passphrase</TextFieldLabel>
|
||||
<TextFieldInput type="password" class="max-w-md" />
|
||||
</TextField>
|
||||
<Flex
|
||||
ref={dropzone.setRef}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
class="relative min-h-32 min-w-96 max-w-xl rounded-lg border-4 border-border border-dashed"
|
||||
classList={{
|
||||
"border-ring": dropzone.isDragging(),
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={() =>
|
||||
fileUploader.selectFiles((files) => {
|
||||
setBackupFile(files.at(0)?.file);
|
||||
})
|
||||
}
|
||||
>
|
||||
Select backup file
|
||||
</Button>
|
||||
<span
|
||||
class="absolute bottom-2"
|
||||
classList={{
|
||||
"text-muted-foreground": !backupFile(),
|
||||
}}
|
||||
>
|
||||
{backupFile() ? backupFile()?.name : "or drop the file here"}
|
||||
</span>
|
||||
</Flex>
|
||||
<Button type="submit" class="max-w-72">
|
||||
Decrypt and load backup
|
||||
</Button>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,67 +1,48 @@
|
|||
import type { RouteSectionProps } from "@solidjs/router";
|
||||
import { type Component, createResource, Show } from "solid-js";
|
||||
|
||||
import {
|
||||
allThreadsOverviewQuery,
|
||||
overallSentMessagesQuery,
|
||||
} from "~/db-queries";
|
||||
import { allThreadsOverviewQuery, overallSentMessagesQuery, SELF_ID } from "~/db";
|
||||
|
||||
import { Title } from "@solidjs/meta";
|
||||
import { SELF_ID } from "~/db";
|
||||
import { getNameFromRecipient } from "~/lib/get-name-from-recipient";
|
||||
import { OverviewTable, type RoomOverview } from "./overview-table";
|
||||
|
||||
export const Overview: Component<RouteSectionProps> = () => {
|
||||
const [allSelfSentMessagesCount] = createResource(() =>
|
||||
overallSentMessagesQuery(SELF_ID),
|
||||
);
|
||||
const [allSelfSentMessagesCount] = createResource(() => overallSentMessagesQuery(SELF_ID));
|
||||
|
||||
const [roomOverview] = createResource<RoomOverview[] | undefined>(
|
||||
async () => {
|
||||
return (await allThreadsOverviewQuery())?.map((row) => {
|
||||
const isGroup = row.title !== null;
|
||||
const [roomOverview] = createResource<RoomOverview[] | undefined>(async () => {
|
||||
return (await allThreadsOverviewQuery())?.map((row) => {
|
||||
const isGroup = row.title !== null;
|
||||
|
||||
let name = "";
|
||||
let name = "";
|
||||
|
||||
if (row.title !== null) {
|
||||
name = row.title;
|
||||
} else {
|
||||
name = getNameFromRecipient(
|
||||
row.nickname_joined_name,
|
||||
row.system_joined_name,
|
||||
row.profile_joined_name,
|
||||
);
|
||||
}
|
||||
if (row.title !== null) {
|
||||
name = row.title;
|
||||
} else {
|
||||
name = getNameFromRecipient(row.nickname_joined_name, row.system_joined_name, row.profile_joined_name);
|
||||
}
|
||||
|
||||
return {
|
||||
threadId: row.thread_id,
|
||||
recipientId: row.recipient_id,
|
||||
archived: Boolean(row.archived),
|
||||
messageCount: row.message_count,
|
||||
lastMessageDate: row.last_message_date
|
||||
? new Date(row.last_message_date)
|
||||
: undefined,
|
||||
name,
|
||||
isGroup,
|
||||
};
|
||||
});
|
||||
},
|
||||
);
|
||||
return {
|
||||
threadId: row.thread_id,
|
||||
recipientId: row.recipient_id,
|
||||
archived: Boolean(row.archived),
|
||||
messageCount: row.message_count,
|
||||
lastMessageDate: row.last_message_date ? new Date(row.last_message_date) : undefined,
|
||||
name,
|
||||
isGroup,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>Signal statistics overview</Title>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
All messages: {allSelfSentMessagesCount()?.messageCount as number}
|
||||
</p>
|
||||
<Show
|
||||
when={!roomOverview.loading && roomOverview()}
|
||||
fallback="Loading..."
|
||||
>
|
||||
<p>All messages: {allSelfSentMessagesCount()?.messageCount as number}</p>
|
||||
<Show when={!roomOverview.loading && roomOverview()} fallback="Loading...">
|
||||
{(currentRoomOverview) => (
|
||||
<OverviewTable data={currentRoomOverview()} />
|
||||
console.log(currentRoomOverview()), (<OverviewTable data={currentRoomOverview()} />)
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue