From 735e91e6368a6714192030e210cd7b4f3db00de1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 29 Aug 2025 15:50:44 +0300 Subject: [PATCH] feat(react/widgets): port sql_result --- apps/client/src/components/entrypoints.ts | 16 +--- apps/client/src/layouts/desktop_layout.tsx | 3 +- apps/client/src/widgets/sql_result.css | 7 ++ apps/client/src/widgets/sql_result.ts | 88 ---------------------- apps/client/src/widgets/sql_result.tsx | 62 +++++++++++++++ apps/server/src/routes/api/notes.ts | 4 +- packages/commons/src/lib/server_api.ts | 16 +++- 7 files changed, 89 insertions(+), 107 deletions(-) create mode 100644 apps/client/src/widgets/sql_result.css delete mode 100644 apps/client/src/widgets/sql_result.ts create mode 100644 apps/client/src/widgets/sql_result.tsx diff --git a/apps/client/src/components/entrypoints.ts b/apps/client/src/components/entrypoints.ts index 2e55a9b9d..6f8aefc24 100644 --- a/apps/client/src/components/entrypoints.ts +++ b/apps/client/src/components/entrypoints.ts @@ -11,21 +11,7 @@ import froca from "../services/froca.js"; import linkService from "../services/link.js"; import { t } from "../services/i18n.js"; import type FNote from "../entities/fnote.js"; - -// TODO: Move somewhere else nicer. -export type SqlExecuteResults = string[][][]; - -// TODO: Deduplicate with server. -interface SqlExecuteResponse { - success: boolean; - error?: string; - results: SqlExecuteResults; -} - -// TODO: Deduplicate with server. -interface CreateChildrenResponse { - note: FNote; -} +import { CreateChildrenResponse, SqlExecuteResponse } from "@triliumnext/commons"; export default class Entrypoints extends Component { constructor() { diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 1ad839b1a..96d7c09ee 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -42,6 +42,7 @@ import FloatingButtons from "../widgets/FloatingButtons.jsx"; import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx"; import SearchResult from "../widgets/search_result.jsx"; import GlobalMenu from "../widgets/buttons/global_menu.jsx"; +import SqlResults from "../widgets/sql_result.js"; export default class DesktopLayout { @@ -140,7 +141,7 @@ export default class DesktopLayout { .child(new NoteDetailWidget()) .child(new NoteListWidget(false)) .child() - .child(new SqlResultWidget()) + .child() .child() ) .child(new ApiLogWidget()) diff --git a/apps/client/src/widgets/sql_result.css b/apps/client/src/widgets/sql_result.css new file mode 100644 index 000000000..63b5621ed --- /dev/null +++ b/apps/client/src/widgets/sql_result.css @@ -0,0 +1,7 @@ +.sql-result-widget { + padding: 15px; +} + +.sql-console-result-container td { + white-space: preserve; +} \ No newline at end of file diff --git a/apps/client/src/widgets/sql_result.ts b/apps/client/src/widgets/sql_result.ts deleted file mode 100644 index f9f579315..000000000 --- a/apps/client/src/widgets/sql_result.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { EventData } from "../components/app_context.js"; -import { t } from "../services/i18n.js"; -import NoteContextAwareWidget from "./note_context_aware_widget.js"; - -const TPL = /*html*/` -
- - - - -
-
`; - -export default class SqlResultWidget extends NoteContextAwareWidget { - - private $resultContainer!: JQuery; - private $noRowsAlert!: JQuery; - - isEnabled() { - return this.note && this.note.mime === "text/x-sqlite;schema=trilium" && super.isEnabled(); - } - - doRender() { - this.$widget = $(TPL); - - this.$resultContainer = this.$widget.find(".sql-console-result-container"); - this.$noRowsAlert = this.$widget.find(".sql-query-no-rows"); - } - - async sqlQueryResultsEvent({ ntxId, results }: EventData<"sqlQueryResults">) { - if (!this.isNoteContext(ntxId)) { - return; - } - - this.$noRowsAlert.toggle(results.length === 1 && results[0].length === 0); - this.$resultContainer.toggle(results.length > 1 || results[0].length > 0); - - this.$resultContainer.empty(); - - for (const rows of results) { - if (typeof rows === "object" && !Array.isArray(rows)) { - // inserts, updates - this.$resultContainer - .empty() - .show() - .append($("
").text(JSON.stringify(rows, null, "\t")));
-
-                continue;
-            }
-
-            if (!rows.length) {
-                continue;
-            }
-
-            const $table = $('');
-            this.$resultContainer.append($table);
-
-            const result = rows[0];
-            const $row = $("");
-
-            for (const key in result) {
-                $row.append($("");
-
-                for (const key in result) {
-                    $row.append($("
").text(key)); - } - - $table.append($row); - - for (const result of rows) { - const $row = $("
").text(result[key])); - } - - $table.append($row); - } - } - } -} diff --git a/apps/client/src/widgets/sql_result.tsx b/apps/client/src/widgets/sql_result.tsx new file mode 100644 index 000000000..760651774 --- /dev/null +++ b/apps/client/src/widgets/sql_result.tsx @@ -0,0 +1,62 @@ +import { SqlExecuteResults } from "@triliumnext/commons"; +import { useNoteContext, useTriliumEvent } from "./react/hooks"; +import "./sql_result.css"; +import { useState } from "preact/hooks"; +import Alert from "./react/Alert"; +import { t } from "../services/i18n"; + +export default function SqlResults() { + const { note, ntxId } = useNoteContext(); + const [ results, setResults ] = useState(); + + useTriliumEvent("sqlQueryResults", ({ ntxId: eventNtxId, results }) => { + if (eventNtxId !== ntxId) return; + setResults(results); + }) + + return ( +
+ {note?.mime === "text/x-sqlite;schema=trilium" && ( + results?.length === 1 && Array.isArray(results[0]) && results[0].length === 0 ? ( + + {t("sql_result.no_rows")} + + ) : ( +
+ {results?.map(rows => { + // inserts, updates + if (typeof rows === "object" && !Array.isArray(rows)) { + return
{JSON.stringify(rows, null, "\t")}
+ } + + // selects + return + })} +
+ ) + )} +
+ ) +} + +function SqlResultTable({ rows }: { rows: object[] }) { + if (!rows.length) return; + + return ( + + + + {Object.keys(rows[0]).map(key => )} + + + + + {rows.map(row => ( + + {Object.values(row).map(cell => )} + + ))} + +
{key}
{cell}
+ ) +} \ No newline at end of file diff --git a/apps/server/src/routes/api/notes.ts b/apps/server/src/routes/api/notes.ts index f481d199a..8a426dea3 100644 --- a/apps/server/src/routes/api/notes.ts +++ b/apps/server/src/routes/api/notes.ts @@ -12,7 +12,7 @@ import ValidationError from "../../errors/validation_error.js"; import blobService from "../../services/blob.js"; import type { Request } from "express"; import type BBranch from "../../becca/entities/bbranch.js"; -import type { AttributeRow, DeleteNotesPreview, MetadataResponse } from "@triliumnext/commons"; +import type { AttributeRow, CreateChildrenResponse, DeleteNotesPreview, MetadataResponse } from "@triliumnext/commons"; /** * @swagger @@ -123,7 +123,7 @@ function createNote(req: Request) { return { note, branch - }; + } satisfies CreateChildrenResponse; } function updateNoteData(req: Request) { diff --git a/packages/commons/src/lib/server_api.ts b/packages/commons/src/lib/server_api.ts index cadbcfe0c..bf712a6e4 100644 --- a/packages/commons/src/lib/server_api.ts +++ b/packages/commons/src/lib/server_api.ts @@ -1,4 +1,4 @@ -import { AttachmentRow, AttributeRow, NoteType } from "./rows.js"; +import { AttachmentRow, AttributeRow, BranchRow, NoteRow, NoteType } from "./rows.js"; type Response = { success: true, @@ -220,3 +220,17 @@ export type BacklinksResponse = ({ noteId: string; excerpts: string[] })[]; + + +export type SqlExecuteResults = (object[] | object)[]; + +export interface SqlExecuteResponse { + success: boolean; + error?: string; + results: SqlExecuteResults; +} + +export interface CreateChildrenResponse { + note: NoteRow; + branch: BranchRow; +}