feat(react/widgets): port sql_result

This commit is contained in:
Elian Doran 2025-08-29 15:50:44 +03:00
parent 4df94d1f20
commit 735e91e636
No known key found for this signature in database
7 changed files with 89 additions and 107 deletions

View File

@ -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() {

View File

@ -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(<SearchResult />)
.child(new SqlResultWidget())
.child(<SqlResults />)
.child(<ScrollPadding />)
)
.child(new ApiLogWidget())

View File

@ -0,0 +1,7 @@
.sql-result-widget {
padding: 15px;
}
.sql-console-result-container td {
white-space: preserve;
}

View File

@ -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*/`
<div class="sql-result-widget">
<style>
.sql-result-widget {
padding: 15px;
}
.sql-console-result-container td {
white-space: preserve;
}
</style>
<div class="sql-query-no-rows alert alert-info" style="display: none;">
${t("sql_result.no_rows")}
</div>
<div class="sql-console-result-container"></div>
</div>`;
export default class SqlResultWidget extends NoteContextAwareWidget {
private $resultContainer!: JQuery<HTMLElement>;
private $noRowsAlert!: JQuery<HTMLElement>;
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($("<pre>").text(JSON.stringify(rows, null, "\t")));
continue;
}
if (!rows.length) {
continue;
}
const $table = $('<table class="table table-striped">');
this.$resultContainer.append($table);
const result = rows[0];
const $row = $("<tr>");
for (const key in result) {
$row.append($("<th>").text(key));
}
$table.append($row);
for (const result of rows) {
const $row = $("<tr>");
for (const key in result) {
$row.append($("<td>").text(result[key]));
}
$table.append($row);
}
}
}
}

View File

@ -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<SqlExecuteResults>();
useTriliumEvent("sqlQueryResults", ({ ntxId: eventNtxId, results }) => {
if (eventNtxId !== ntxId) return;
setResults(results);
})
return (
<div className="sql-result-widget">
{note?.mime === "text/x-sqlite;schema=trilium" && (
results?.length === 1 && Array.isArray(results[0]) && results[0].length === 0 ? (
<Alert type="info">
{t("sql_result.no_rows")}
</Alert>
) : (
<div class="sql-console-result-container">
{results?.map(rows => {
// inserts, updates
if (typeof rows === "object" && !Array.isArray(rows)) {
return <pre>{JSON.stringify(rows, null, "\t")}</pre>
}
// selects
return <SqlResultTable rows={rows} />
})}
</div>
)
)}
</div>
)
}
function SqlResultTable({ rows }: { rows: object[] }) {
if (!rows.length) return;
return (
<table className="table table-striped">
<thead>
<tr>
{Object.keys(rows[0]).map(key => <th>{key}</th>)}
</tr>
</thead>
<tbody>
{rows.map(row => (
<tr>
{Object.values(row).map(cell => <td>{cell}</td>)}
</tr>
))}
</tbody>
</table>
)
}

View File

@ -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) {

View File

@ -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;
}