feat: show message when loading the database
This commit is contained in:
parent
38091f2c1a
commit
3d49a25cae
12 changed files with 99 additions and 150 deletions
|
@ -44,6 +44,7 @@
|
||||||
"noUnreachable": "error",
|
"noUnreachable": "error",
|
||||||
"noUnreachableSuper": "error",
|
"noUnreachableSuper": "error",
|
||||||
"noUnsafeFinally": "error",
|
"noUnsafeFinally": "error",
|
||||||
|
"noUnusedImports": "error",
|
||||||
"noUnsafeOptionalChaining": "error",
|
"noUnsafeOptionalChaining": "error",
|
||||||
"noUnusedLabels": "error",
|
"noUnusedLabels": "error",
|
||||||
"noUnusedPrivateClassMembers": "error",
|
"noUnusedPrivateClassMembers": "error",
|
||||||
|
@ -107,13 +108,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ignore": [
|
"ignore": ["dist/**/*.ts", "dist/**", "**/*.mjs", "eslint.config.js", "**/*.js"]
|
||||||
"dist/**/*.ts",
|
|
||||||
"dist/**",
|
|
||||||
"**/*.mjs",
|
|
||||||
"eslint.config.js",
|
|
||||||
"**/*.js"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"javascript": {
|
"javascript": {
|
||||||
"formatter": {
|
"formatter": {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { type Component } from "solid-js";
|
import { type Component } from "solid-js";
|
||||||
import { Route } from "@solidjs/router";
|
import { Route } from "@solidjs/router";
|
||||||
|
|
||||||
import { allThreadsOverviewQuery } from "./db";
|
|
||||||
import { DmId, GroupId, Home, Overview } from "./pages";
|
import { DmId, GroupId, Home, Overview } from "./pages";
|
||||||
|
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import type { Component, ComponentProps } from "solid-js"
|
import type { Component, ComponentProps } from "solid-js";
|
||||||
import { mergeProps, splitProps } from "solid-js"
|
import { mergeProps, splitProps } from "solid-js";
|
||||||
|
|
||||||
import { cn } from "~/lib/utils"
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
type Cols = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
|
type Cols = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||||
type Span = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13
|
type Span = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;
|
||||||
|
|
||||||
type GridProps = ComponentProps<"div"> & {
|
type GridProps = ComponentProps<"div"> & {
|
||||||
cols?: Cols
|
cols?: Cols;
|
||||||
colsSm?: Cols
|
colsSm?: Cols;
|
||||||
colsMd?: Cols
|
colsMd?: Cols;
|
||||||
colsLg?: Cols
|
colsLg?: Cols;
|
||||||
}
|
};
|
||||||
|
|
||||||
const Grid: Component<GridProps> = (rawProps) => {
|
const Grid: Component<GridProps> = (rawProps) => {
|
||||||
const props = mergeProps({ cols: 1 } satisfies GridProps, rawProps)
|
const props = mergeProps({ cols: 1 } satisfies GridProps, rawProps);
|
||||||
const [local, others] = splitProps(props, ["cols", "colsSm", "colsMd", "colsLg", "class"])
|
const [local, others] = splitProps(props, ["cols", "colsSm", "colsMd", "colsLg", "class"]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -25,23 +25,23 @@ const Grid: Component<GridProps> = (rawProps) => {
|
||||||
local.colsSm && gridColsSm[local.colsSm],
|
local.colsSm && gridColsSm[local.colsSm],
|
||||||
local.colsMd && gridColsMd[local.colsMd],
|
local.colsMd && gridColsMd[local.colsMd],
|
||||||
local.colsLg && gridColsLg[local.colsLg],
|
local.colsLg && gridColsLg[local.colsLg],
|
||||||
local.class
|
local.class,
|
||||||
)}
|
)}
|
||||||
{...others}
|
{...others}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
type ColProps = ComponentProps<"div"> & {
|
type ColProps = ComponentProps<"div"> & {
|
||||||
span?: Span
|
span?: Span;
|
||||||
spanSm?: Span
|
spanSm?: Span;
|
||||||
spanMd?: Span
|
spanMd?: Span;
|
||||||
spanLg?: Span
|
spanLg?: Span;
|
||||||
}
|
};
|
||||||
|
|
||||||
const Col: Component<ColProps> = (rawProps) => {
|
const Col: Component<ColProps> = (rawProps) => {
|
||||||
const props = mergeProps({ span: 1 as Span }, rawProps)
|
const props = mergeProps({ span: 1 as Span }, rawProps);
|
||||||
const [local, others] = splitProps(props, ["span", "spanSm", "spanMd", "spanLg", "class"])
|
const [local, others] = splitProps(props, ["span", "spanSm", "spanMd", "spanLg", "class"]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -50,14 +50,14 @@ const Col: Component<ColProps> = (rawProps) => {
|
||||||
local.spanSm && colSpanSm[local.spanSm],
|
local.spanSm && colSpanSm[local.spanSm],
|
||||||
local.spanMd && colSpanMd[local.spanMd],
|
local.spanMd && colSpanMd[local.spanMd],
|
||||||
local.spanLg && colSpanLg[local.spanLg],
|
local.spanLg && colSpanLg[local.spanLg],
|
||||||
local.class
|
local.class,
|
||||||
)}
|
)}
|
||||||
{...others}
|
{...others}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export { Grid, Col }
|
export { Grid, Col };
|
||||||
|
|
||||||
const gridCols: { [key in Cols]: string } = {
|
const gridCols: { [key in Cols]: string } = {
|
||||||
0: "grid-cols-none",
|
0: "grid-cols-none",
|
||||||
|
@ -72,8 +72,8 @@ const gridCols: { [key in Cols]: string } = {
|
||||||
9: "grid-cols-9",
|
9: "grid-cols-9",
|
||||||
10: "grid-cols-10",
|
10: "grid-cols-10",
|
||||||
11: "grid-cols-11",
|
11: "grid-cols-11",
|
||||||
12: "grid-cols-12"
|
12: "grid-cols-12",
|
||||||
}
|
};
|
||||||
|
|
||||||
const gridColsSm: { [key in Cols]: string } = {
|
const gridColsSm: { [key in Cols]: string } = {
|
||||||
0: "sm:grid-cols-none",
|
0: "sm:grid-cols-none",
|
||||||
|
@ -88,8 +88,8 @@ const gridColsSm: { [key in Cols]: string } = {
|
||||||
9: "sm:grid-cols-9",
|
9: "sm:grid-cols-9",
|
||||||
10: "sm:grid-cols-10",
|
10: "sm:grid-cols-10",
|
||||||
11: "sm:grid-cols-11",
|
11: "sm:grid-cols-11",
|
||||||
12: "sm:grid-cols-12"
|
12: "sm:grid-cols-12",
|
||||||
}
|
};
|
||||||
|
|
||||||
const gridColsMd: { [key in Cols]: string } = {
|
const gridColsMd: { [key in Cols]: string } = {
|
||||||
0: "md:grid-cols-none",
|
0: "md:grid-cols-none",
|
||||||
|
@ -104,8 +104,8 @@ const gridColsMd: { [key in Cols]: string } = {
|
||||||
9: "md:grid-cols-9",
|
9: "md:grid-cols-9",
|
||||||
10: "md:grid-cols-10",
|
10: "md:grid-cols-10",
|
||||||
11: "md:grid-cols-11",
|
11: "md:grid-cols-11",
|
||||||
12: "md:grid-cols-12"
|
12: "md:grid-cols-12",
|
||||||
}
|
};
|
||||||
|
|
||||||
const gridColsLg: { [key in Cols]: string } = {
|
const gridColsLg: { [key in Cols]: string } = {
|
||||||
0: "lg:grid-cols-none",
|
0: "lg:grid-cols-none",
|
||||||
|
@ -120,8 +120,8 @@ const gridColsLg: { [key in Cols]: string } = {
|
||||||
9: "lg:grid-cols-9",
|
9: "lg:grid-cols-9",
|
||||||
10: "lg:grid-cols-10",
|
10: "lg:grid-cols-10",
|
||||||
11: "lg:grid-cols-11",
|
11: "lg:grid-cols-11",
|
||||||
12: "lg:grid-cols-12"
|
12: "lg:grid-cols-12",
|
||||||
}
|
};
|
||||||
|
|
||||||
const colSpan: { [key in Span]: string } = {
|
const colSpan: { [key in Span]: string } = {
|
||||||
1: "col-span-1",
|
1: "col-span-1",
|
||||||
|
@ -136,8 +136,8 @@ const colSpan: { [key in Span]: string } = {
|
||||||
10: "col-span-10",
|
10: "col-span-10",
|
||||||
11: "col-span-11",
|
11: "col-span-11",
|
||||||
12: "col-span-12",
|
12: "col-span-12",
|
||||||
13: "col-span-13"
|
13: "col-span-13",
|
||||||
}
|
};
|
||||||
|
|
||||||
const colSpanSm: { [key in Span]: string } = {
|
const colSpanSm: { [key in Span]: string } = {
|
||||||
1: "sm:col-span-1",
|
1: "sm:col-span-1",
|
||||||
|
@ -152,8 +152,8 @@ const colSpanSm: { [key in Span]: string } = {
|
||||||
10: "sm:col-span-10",
|
10: "sm:col-span-10",
|
||||||
11: "sm:col-span-11",
|
11: "sm:col-span-11",
|
||||||
12: "sm:col-span-12",
|
12: "sm:col-span-12",
|
||||||
13: "sm:col-span-13"
|
13: "sm:col-span-13",
|
||||||
}
|
};
|
||||||
|
|
||||||
const colSpanMd: { [key in Span]: string } = {
|
const colSpanMd: { [key in Span]: string } = {
|
||||||
1: "md:col-span-1",
|
1: "md:col-span-1",
|
||||||
|
@ -168,8 +168,8 @@ const colSpanMd: { [key in Span]: string } = {
|
||||||
10: "md:col-span-10",
|
10: "md:col-span-10",
|
||||||
11: "md:col-span-11",
|
11: "md:col-span-11",
|
||||||
12: "md:col-span-12",
|
12: "md:col-span-12",
|
||||||
13: "md:col-span-13"
|
13: "md:col-span-13",
|
||||||
}
|
};
|
||||||
|
|
||||||
const colSpanLg: { [key in Span]: string } = {
|
const colSpanLg: { [key in Span]: string } = {
|
||||||
1: "lg:col-span-1",
|
1: "lg:col-span-1",
|
||||||
|
@ -184,5 +184,5 @@ const colSpanLg: { [key in Span]: string } = {
|
||||||
10: "lg:col-span-10",
|
10: "lg:col-span-10",
|
||||||
11: "lg:col-span-11",
|
11: "lg:col-span-11",
|
||||||
12: "lg:col-span-12",
|
12: "lg:col-span-12",
|
||||||
13: "lg:col-span-13"
|
13: "lg:col-span-13",
|
||||||
}
|
};
|
||||||
|
|
59
src/db.ts
59
src/db.ts
|
@ -1,12 +1,4 @@
|
||||||
import {
|
import { type Accessor, createEffect, createMemo, createRoot, createSignal, DEV, type Setter } from "solid-js";
|
||||||
type Accessor,
|
|
||||||
createEffect,
|
|
||||||
createMemo,
|
|
||||||
createRoot,
|
|
||||||
createSignal,
|
|
||||||
DEV,
|
|
||||||
type Setter,
|
|
||||||
} from "solid-js";
|
|
||||||
|
|
||||||
import { Kysely, type NotNull, sql } from "kysely";
|
import { Kysely, type NotNull, sql } from "kysely";
|
||||||
import type { DB } from "kysely-codegen";
|
import type { DB } from "kysely-codegen";
|
||||||
|
@ -79,19 +71,13 @@ const allThreadsOverviewQueryRaw = kyselyDb()
|
||||||
(eb) =>
|
(eb) =>
|
||||||
eb
|
eb
|
||||||
.selectFrom("message")
|
.selectFrom("message")
|
||||||
.select((eb) => [
|
.select((eb) => ["message.thread_id", eb.fn.countAll().as("message_count")])
|
||||||
"message.thread_id",
|
|
||||||
eb.fn.countAll().as("message_count"),
|
|
||||||
])
|
|
||||||
.where((eb) => {
|
.where((eb) => {
|
||||||
return eb.and([
|
return eb.and([eb("message.body", "is not", null), eb("message.body", "is not", "")]);
|
||||||
eb("message.body", "is not", null),
|
|
||||||
eb("message.body", "is not", ""),
|
|
||||||
]);
|
|
||||||
})
|
})
|
||||||
.groupBy("message.thread_id")
|
.groupBy("message.thread_id")
|
||||||
.as("message"),
|
.as("message"),
|
||||||
(join) => join.onRef("message.thread_id", "=", "thread._id")
|
(join) => join.onRef("message.thread_id", "=", "thread._id"),
|
||||||
)
|
)
|
||||||
.innerJoin("recipient", "thread.recipient_id", "recipient._id")
|
.innerJoin("recipient", "thread.recipient_id", "recipient._id")
|
||||||
.leftJoin("groups", "recipient._id", "groups.recipient_id")
|
.leftJoin("groups", "recipient._id", "groups.recipient_id")
|
||||||
|
@ -114,9 +100,7 @@ const allThreadsOverviewQueryRaw = kyselyDb()
|
||||||
}>()
|
}>()
|
||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
export const allThreadsOverviewQuery = cached(() =>
|
export const allThreadsOverviewQuery = cached(() => kyselyDb().executeQuery(allThreadsOverviewQueryRaw));
|
||||||
kyselyDb().executeQuery(allThreadsOverviewQueryRaw)
|
|
||||||
);
|
|
||||||
|
|
||||||
const overallSentMessagesQueryRaw = (recipientId: number) =>
|
const overallSentMessagesQueryRaw = (recipientId: number) =>
|
||||||
kyselyDb()
|
kyselyDb()
|
||||||
|
@ -127,7 +111,7 @@ const overallSentMessagesQueryRaw = (recipientId: number) =>
|
||||||
eb("message.from_recipient_id", "=", recipientId),
|
eb("message.from_recipient_id", "=", recipientId),
|
||||||
eb("message.body", "is not", null),
|
eb("message.body", "is not", null),
|
||||||
eb("message.body", "!=", ""),
|
eb("message.body", "!=", ""),
|
||||||
])
|
]),
|
||||||
)
|
)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
@ -143,9 +127,7 @@ const dmPartnerRecipientQueryRaw = (dmId: number) =>
|
||||||
"recipient.nickname_joined_name",
|
"recipient.nickname_joined_name",
|
||||||
])
|
])
|
||||||
.innerJoin("thread", "recipient._id", "thread.recipient_id")
|
.innerJoin("thread", "recipient._id", "thread.recipient_id")
|
||||||
.where((eb) =>
|
.where((eb) => eb.and([eb("thread._id", "=", dmId), eb("recipient._id", "!=", SELF_ID)]))
|
||||||
eb.and([eb("thread._id", "=", dmId), eb("recipient._id", "!=", SELF_ID)])
|
|
||||||
)
|
|
||||||
.$narrowType<{
|
.$narrowType<{
|
||||||
_id: number;
|
_id: number;
|
||||||
}>()
|
}>()
|
||||||
|
@ -156,25 +138,12 @@ export const dmPartnerRecipientQuery = cached(dmPartnerRecipientQueryRaw);
|
||||||
const threadSentMessagesOverviewQueryRaw = (threadId: number) =>
|
const threadSentMessagesOverviewQueryRaw = (threadId: number) =>
|
||||||
kyselyDb()
|
kyselyDb()
|
||||||
.selectFrom("message")
|
.selectFrom("message")
|
||||||
.select([
|
.select(["from_recipient_id", sql<string>`datetime(date_sent / 1000, 'unixepoch')`.as("message_datetime")])
|
||||||
"from_recipient_id",
|
|
||||||
sql<string>`datetime(date_sent / 1000, 'unixepoch')`.as(
|
|
||||||
"message_datetime"
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.orderBy(["message_datetime"])
|
.orderBy(["message_datetime"])
|
||||||
.where((eb) =>
|
.where((eb) => eb.and([eb("body", "is not", null), eb("body", "!=", ""), eb("thread_id", "=", threadId)]))
|
||||||
eb.and([
|
|
||||||
eb("body", "is not", null),
|
|
||||||
eb("body", "!=", ""),
|
|
||||||
eb("thread_id", "=", threadId),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
export const threadSentMessagesOverviewQuery = cached(
|
export const threadSentMessagesOverviewQuery = cached(threadSentMessagesOverviewQueryRaw);
|
||||||
threadSentMessagesOverviewQueryRaw
|
|
||||||
);
|
|
||||||
|
|
||||||
const threadMostUsedWordsQueryRaw = (threadId: number, limit = 10) =>
|
const threadMostUsedWordsQueryRaw = (threadId: number, limit = 10) =>
|
||||||
kyselyDb()
|
kyselyDb()
|
||||||
|
@ -185,16 +154,12 @@ const threadMostUsedWordsQueryRaw = (threadId: number, limit = 10) =>
|
||||||
sql`LOWER(substr(body, 1, instr(body || " ", " ") - 1))`.as("word"),
|
sql`LOWER(substr(body, 1, instr(body || " ", " ") - 1))`.as("word"),
|
||||||
sql`(substr(body, instr(body || " ", " ") + 1))`.as("rest"),
|
sql`(substr(body, instr(body || " ", " ") + 1))`.as("rest"),
|
||||||
])
|
])
|
||||||
.where((eb) =>
|
.where((eb) => eb.and([eb("body", "is not", null), eb("thread_id", "=", threadId)]))
|
||||||
eb.and([eb("body", "is not", null), eb("thread_id", "=", threadId)])
|
|
||||||
)
|
|
||||||
.unionAll((ebInner) => {
|
.unionAll((ebInner) => {
|
||||||
return ebInner
|
return ebInner
|
||||||
.selectFrom("words")
|
.selectFrom("words")
|
||||||
.select([
|
.select([
|
||||||
sql`LOWER(substr(rest, 1, instr(rest || " ", " ") - 1))`.as(
|
sql`LOWER(substr(rest, 1, instr(rest || " ", " ") - 1))`.as("word"),
|
||||||
"word"
|
|
||||||
),
|
|
||||||
sql`(substr(rest, instr(rest || " ", " ") + 1))`.as("rest"),
|
sql`(substr(rest, instr(rest || " ", " ") + 1))`.as("rest"),
|
||||||
])
|
])
|
||||||
.where("rest", "<>", "");
|
.where("rest", "<>", "");
|
||||||
|
|
|
@ -10,18 +10,14 @@ export const getDistanceBetweenDatesInDays = (a: Date, b: Date) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://dev.to/pretaporter/how-to-get-month-list-in-your-language-4lfb
|
// https://dev.to/pretaporter/how-to-get-month-list-in-your-language-4lfb
|
||||||
export const getMonthList = (
|
export const getMonthList = (locales?: string | string[], format: "long" | "short" = "long"): string[] => {
|
||||||
locales?: string | string[],
|
|
||||||
format: "long" | "short" = "long"
|
|
||||||
): string[] => {
|
|
||||||
const year = new Date().getFullYear(); // 2020
|
const year = new Date().getFullYear(); // 2020
|
||||||
const monthList = [...Array(12).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
const monthList = [...Array(12).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
||||||
const formatter = new Intl.DateTimeFormat(locales, {
|
const formatter = new Intl.DateTimeFormat(locales, {
|
||||||
month: format,
|
month: format,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getMonthName = (monthIndex: number) =>
|
const getMonthName = (monthIndex: number) => formatter.format(new Date(year, monthIndex));
|
||||||
formatter.format(new Date(year, monthIndex));
|
|
||||||
|
|
||||||
return monthList.map(getMonthName);
|
return monthList.map(getMonthName);
|
||||||
};
|
};
|
||||||
|
@ -48,10 +44,7 @@ export const getDateList = (startDate: Date, endDate: Date): Date[] => {
|
||||||
return dateArray;
|
return dateArray;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getHourList = (
|
export const getHourList = (locales?: string | string[], format: "numeric" | "2-digit" = "numeric"): string[] => {
|
||||||
locales?: string | string[],
|
|
||||||
format: "numeric" | "2-digit" = "numeric"
|
|
||||||
): string[] => {
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const year = now.getFullYear();
|
const year = now.getFullYear();
|
||||||
const month = now.getMonth();
|
const month = now.getMonth();
|
||||||
|
@ -63,16 +56,12 @@ export const getHourList = (
|
||||||
hourCycle: "h11",
|
hourCycle: "h11",
|
||||||
});
|
});
|
||||||
|
|
||||||
const getHourName = (hourIndex: number) =>
|
const getHourName = (hourIndex: number) => formatter.format(new Date(year, month, day, hourIndex));
|
||||||
formatter.format(new Date(year, month, day, hourIndex));
|
|
||||||
|
|
||||||
return hourList.map(getHourName);
|
return hourList.map(getHourName);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWeekdayList = (
|
export const getWeekdayList = (locales?: string | string[], format: "long" | "short" | "narrow" = "long"): string[] => {
|
||||||
locales?: string | string[],
|
|
||||||
format: "long" | "short" | "narrow" = "long"
|
|
||||||
): string[] => {
|
|
||||||
const monday = new Date();
|
const monday = new Date();
|
||||||
// set day to monday (w/o +1 it would be sunday)
|
// set day to monday (w/o +1 it would be sunday)
|
||||||
monday.setDate(monday.getDate() - monday.getDay() + 1);
|
monday.setDate(monday.getDate() - monday.getDay() + 1);
|
||||||
|
@ -86,8 +75,7 @@ export const getWeekdayList = (
|
||||||
weekday: format,
|
weekday: format,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getWeekDayName = (weekDayIndex: number) =>
|
const getWeekDayName = (weekDayIndex: number) => formatter.format(new Date(year, month, mondayDate + weekDayIndex));
|
||||||
formatter.format(new Date(year, month, mondayDate + weekDayIndex));
|
|
||||||
|
|
||||||
return hourList.map(getWeekDayName);
|
return hourList.map(getWeekDayName);
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,9 +36,7 @@ createRoot(() => {
|
||||||
createDeferred(
|
createDeferred(
|
||||||
on(db, (currentDb) => {
|
on(db, (currentDb) => {
|
||||||
if (currentDb) {
|
if (currentDb) {
|
||||||
const newHash = hashString(
|
const newHash = hashString(new TextDecoder().decode(currentDb.export())).toString();
|
||||||
new TextDecoder().decode(currentDb.export())
|
|
||||||
).toString();
|
|
||||||
|
|
||||||
const oldHash = localStorage.getItem(HASH_STORE_KEY);
|
const oldHash = localStorage.getItem(HASH_STORE_KEY);
|
||||||
|
|
||||||
|
@ -48,15 +46,13 @@ createRoot(() => {
|
||||||
localStorage.setItem(HASH_STORE_KEY, newHash);
|
localStorage.setItem(HASH_STORE_KEY, newHash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
class LocalStorageCacheAdapter {
|
class LocalStorageCacheAdapter {
|
||||||
keys = new Set<string>(
|
keys = new Set<string>(Object.keys(localStorage).filter((key) => key.startsWith(this.prefix)));
|
||||||
Object.keys(localStorage).filter((key) => key.startsWith(this.prefix))
|
|
||||||
);
|
|
||||||
prefix = "database";
|
prefix = "database";
|
||||||
|
|
||||||
#createKey(cacheName: string, key: string): string {
|
#createKey(cacheName: string, key: string): string {
|
||||||
|
@ -70,10 +66,7 @@ class LocalStorageCacheAdapter {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(fullKey, JSON.stringify(value));
|
localStorage.setItem(fullKey, JSON.stringify(value));
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (
|
if (error instanceof DOMException && error.name === "QUOTA_EXCEEDED_ERR") {
|
||||||
error instanceof DOMException &&
|
|
||||||
error.name === "QUOTA_EXCEEDED_ERR"
|
|
||||||
) {
|
|
||||||
console.error("Storage quota exceeded, not caching new function calls");
|
console.error("Storage quota exceeded, not caching new function calls");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,10 +114,7 @@ const createHashKey = (...args: unknown[]) => {
|
||||||
return hashString(stringToHash);
|
return hashString(stringToHash);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cached = <T extends unknown[], R, TT>(
|
export const cached = <T extends unknown[], R, TT>(fn: (...args: T) => R, self?: ThisType<TT>): ((...args: T) => R) => {
|
||||||
fn: (...args: T) => R,
|
|
||||||
self?: ThisType<TT>
|
|
||||||
): ((...args: T) => R) => {
|
|
||||||
const cacheName = hashString(fn.toString()).toString();
|
const cacheName = hashString(fn.toString()).toString();
|
||||||
|
|
||||||
// important to return a promise on follow-up calls even if the data is immediately available
|
// important to return a promise on follow-up calls even if the data is immediately available
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export const getNameFromRecipient = (
|
export const getNameFromRecipient = (
|
||||||
joinedNickname: string | null,
|
joinedNickname: string | null,
|
||||||
joinedSystemName: string | null,
|
joinedSystemName: string | null,
|
||||||
joinedProfileName: string | null
|
joinedProfileName: string | null,
|
||||||
) => {
|
) => {
|
||||||
let name = "Could not determine name";
|
let name = "Could not determine name";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { createEffect, createMemo, on, type Accessor } from "solid-js";
|
import { createMemo, type Accessor } from "solid-js";
|
||||||
import { getDateList, getHourList, getMonthList, getWeekdayList } from "./date";
|
import { getDateList, getHourList, getMonthList, getWeekdayList } from "./date";
|
||||||
import { cached } from "./db-cache";
|
|
||||||
import type { MessageOverview, MessageStats, Recipients } from "~/types";
|
import type { MessageOverview, MessageStats, Recipients } from "~/types";
|
||||||
import { isSameDay } from "date-fns";
|
import { isSameDay } from "date-fns";
|
||||||
|
|
||||||
|
@ -18,10 +17,9 @@ const initialWeekdayMap = [...weekdayNames.keys()];
|
||||||
|
|
||||||
export const createMessageStatsSources = (
|
export const createMessageStatsSources = (
|
||||||
messageOverview: Accessor<MessageOverview>,
|
messageOverview: Accessor<MessageOverview>,
|
||||||
recipients: Accessor<Recipients>
|
recipients: Accessor<Recipients>,
|
||||||
) => {
|
) => {
|
||||||
const initialRecipientMap = () =>
|
const initialRecipientMap = () => Object.fromEntries(recipients().map(({ recipientId }) => [recipientId, 0]));
|
||||||
Object.fromEntries(recipients().map(({ recipientId }) => [recipientId, 0]));
|
|
||||||
|
|
||||||
const dateList = () => {
|
const dateList = () => {
|
||||||
const currentDmMessagesOverview = messageOverview();
|
const currentDmMessagesOverview = messageOverview();
|
||||||
|
@ -63,9 +61,7 @@ export const createMessageStatsSources = (
|
||||||
month[messageDate.getMonth() - 1][message.fromRecipientId] += 1;
|
month[messageDate.getMonth() - 1][message.fromRecipientId] += 1;
|
||||||
|
|
||||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||||
const dateStatsEntry = date.find(({ date }) =>
|
const dateStatsEntry = date.find(({ date }) => isSameDay(date, messageDate))!;
|
||||||
isSameDay(date, messageDate)
|
|
||||||
)!;
|
|
||||||
|
|
||||||
// increment the message count of the message's date for this recipient
|
// increment the message count of the message's date for this recipient
|
||||||
dateStatsEntry[message.fromRecipientId] += 1;
|
dateStatsEntry[message.fromRecipientId] += 1;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { type Component, createResource, Show } from "solid-js";
|
import { type Component, createResource } from "solid-js";
|
||||||
import type { RouteSectionProps } from "@solidjs/router";
|
import type { RouteSectionProps } from "@solidjs/router";
|
||||||
|
|
||||||
import { dmPartnerRecipientQuery, SELF_ID, threadMostUsedWordsQuery, threadSentMessagesOverviewQuery } from "~/db";
|
import { dmPartnerRecipientQuery, SELF_ID, threadMostUsedWordsQuery, threadSentMessagesOverviewQuery } from "~/db";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createEffect, Show, type Accessor, type Component } from "solid-js";
|
import { Show, type Accessor, type Component } from "solid-js";
|
||||||
import type { ChartData } from "chart.js";
|
import type { ChartData } from "chart.js";
|
||||||
import { LineChart } from "~/components/ui/charts";
|
import { LineChart } from "~/components/ui/charts";
|
||||||
import type { MessageStats, Recipients } from "~/types";
|
import type { MessageStats, Recipients } from "~/types";
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { type Component, type JSX } from "solid-js";
|
import { createSignal, Show, type Component, type JSX } from "solid-js";
|
||||||
import { type RouteSectionProps, useNavigate } from "@solidjs/router";
|
import { type RouteSectionProps, useNavigate } from "@solidjs/router";
|
||||||
|
|
||||||
import { setDb, SQL } from "~/db";
|
import { setDb, SQL } from "~/db";
|
||||||
|
import { Portal } from "solid-js/web";
|
||||||
|
import { Flex } from "~/components/ui/flex";
|
||||||
|
|
||||||
export const Home: Component<RouteSectionProps> = () => {
|
export const Home: Component<RouteSectionProps> = () => {
|
||||||
|
const [isLoadingDb, setIsLoadingDb] = createSignal(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const onFileChange: JSX.ChangeEventHandler<HTMLInputElement, Event> = (event) => {
|
const onFileChange: JSX.ChangeEventHandler<HTMLInputElement, Event> = (event) => {
|
||||||
|
@ -12,9 +15,14 @@ export const Home: Component<RouteSectionProps> = () => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
||||||
reader.addEventListener("load", () => {
|
reader.addEventListener("load", () => {
|
||||||
const Uints = new Uint8Array(reader.result as ArrayBuffer);
|
setIsLoadingDb(true);
|
||||||
setDb(new SQL.Database(Uints));
|
|
||||||
navigate("/overview");
|
setTimeout(() => {
|
||||||
|
const Uints = new Uint8Array(reader.result as ArrayBuffer);
|
||||||
|
setDb(new SQL.Database(Uints));
|
||||||
|
setIsLoadingDb(false);
|
||||||
|
navigate("/overview");
|
||||||
|
}, 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
|
@ -22,9 +30,18 @@ export const Home: Component<RouteSectionProps> = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<input type="file" accept=".sqlite" onChange={onFileChange}></input>
|
<Portal>
|
||||||
</div>
|
<Show when={isLoadingDb()}>
|
||||||
|
<Flex alignItems="center" justifyContent="center" class="fixed inset-0 backdrop-blur-lg backdrop-filter">
|
||||||
|
<p class="font-bold text-2xl">Loading database</p>
|
||||||
|
</Flex>
|
||||||
|
</Show>
|
||||||
|
</Portal>
|
||||||
|
<div>
|
||||||
|
<input type="file" accept=".sqlite" onChange={onFileChange}></input>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue