v0.5.3-pre.005-fix010
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_decode_replay.ts
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -9,11 +9,21 @@ import DataTable, { type Api } from "datatables.net-bs5";
|
||||
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log.ts";
|
||||
import {
|
||||
applyStoreBlockMetadata,
|
||||
createStoreBlockState,
|
||||
cursorForBlock,
|
||||
markStoreBlockFiltersDirty,
|
||||
resetStoreBlockState,
|
||||
renderStoreBlockControls,
|
||||
type StoreBlockControls,
|
||||
} from "./demo_store_block_pagination.ts";
|
||||
import type { DemoDecodeDiagnosticsPayload } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeDiagnosticsPayload.ts";
|
||||
import type { DemoDecodeReplayOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeReplayOptionsPayload.ts";
|
||||
import type { DemoDecodeReplayProgressPayload } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeReplayProgressPayload.ts";
|
||||
import type { DemoDecodeReplayRequest } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeReplayRequest.ts";
|
||||
import type { DemoDecodeReplaySummaryPayload } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoDecodeReplaySummaryPayload.ts";
|
||||
import type { DemoTransactionAnnotationPagePayload } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoTransactionAnnotationPagePayload.ts";
|
||||
import type { DemoTransactionAnnotationRequest } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoTransactionAnnotationRequest.ts";
|
||||
import type { DemoTransactionAnnotationRow } from "./bindings/kb_app_demo_desktop/demo_decode_replay/DemoTransactionAnnotationRow.ts";
|
||||
|
||||
@@ -23,6 +33,15 @@ import type { DemoTransactionAnnotationRow } from "./bindings/kb_app_demo_deskto
|
||||
const logLines: string[] = [];
|
||||
let running = false;
|
||||
let materializationAvailable = false;
|
||||
const annotationBlockControls: StoreBlockControls = {
|
||||
firstSelector: "#annotationFirstBlockButton",
|
||||
previousSelector: "#annotationPreviousBlockButton",
|
||||
nextSelector: "#annotationNextBlockButton",
|
||||
summarySelector: "#annotationBlockSummary",
|
||||
};
|
||||
const annotationBlockState = createStoreBlockState();
|
||||
type AnnotationBaseRequest = Omit<DemoTransactionAnnotationRequest, "cursor" | "blockIndex">;
|
||||
let annotationBaseRequest: AnnotationBaseRequest | null = null;
|
||||
|
||||
function element<T extends HTMLElement>(selector: string): T {
|
||||
const value = document.querySelector<T>(selector);
|
||||
@@ -250,7 +269,7 @@ function renderAnnotations(rows: DemoTransactionAnnotationRow[]): void {
|
||||
annotationJournalTable = new DataTable<DemoTransactionAnnotationRow>("#annotationJournalTable", {
|
||||
autoWidth: false,
|
||||
pageLength: 25,
|
||||
lengthMenu: [10, 25, 50, 100],
|
||||
lengthMenu: [10, 25, 50, 100, 250, 500],
|
||||
order: [[0, "desc"]],
|
||||
scrollX: true,
|
||||
language: {
|
||||
@@ -265,23 +284,53 @@ function renderAnnotations(rows: DemoTransactionAnnotationRow[]): void {
|
||||
element<HTMLElement>("#annotationJournalStatus").textContent = `${rows.length} annotation(s) committed chargée(s).`;
|
||||
}
|
||||
|
||||
async function loadAnnotations(): Promise<void> {
|
||||
const request: DemoTransactionAnnotationRequest = {
|
||||
async function startAnnotationLoad(): Promise<void> {
|
||||
annotationBaseRequest = {
|
||||
signatureContains: inputValue("#annotationSignatureInput") || null,
|
||||
limit: integerValue("#annotationLimitInput"),
|
||||
};
|
||||
resetStoreBlockState(annotationBlockState);
|
||||
await loadAnnotationBlock(1, null);
|
||||
}
|
||||
|
||||
async function loadAnnotationBlock(blockIndex: number, cursor: string | null): Promise<void> {
|
||||
if (annotationBaseRequest === null) {
|
||||
throw new Error("Rechargez les annotations pour initialiser les filtres Store.");
|
||||
}
|
||||
const request: DemoTransactionAnnotationRequest = {
|
||||
...annotationBaseRequest,
|
||||
cursor,
|
||||
blockIndex,
|
||||
};
|
||||
try {
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_annotations limit=${request.limit}`);
|
||||
const rows = await invoke<DemoTransactionAnnotationRow[]>("demo_decode_replay_annotations", { request });
|
||||
renderAnnotations(rows);
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_annotations completed rows=${rows.length}`);
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_annotations block=${blockIndex}`);
|
||||
const payload = await invoke<DemoTransactionAnnotationPagePayload>("demo_decode_replay_annotations", { request });
|
||||
renderAnnotations(payload.rows);
|
||||
applyStoreBlockMetadata(annotationBlockState, payload.page, annotationBlockControls);
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke demo_decode_replay_annotations completed block=${payload.page.blockIndex}/${payload.page.totalBlocks} rows=${payload.rows.length}`);
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
element<HTMLElement>("#annotationJournalStatus").textContent = `Erreur : ${message}`;
|
||||
renderStoreBlockControls(annotationBlockState, annotationBlockControls);
|
||||
frontendError("kb_app_demo_desktop.frontend.demo_decode_replay", `Annotation journal loading failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPreviousAnnotationBlock(): Promise<void> {
|
||||
const targetBlock = annotationBlockState.currentBlock - 1;
|
||||
const cursor = cursorForBlock(annotationBlockState, targetBlock);
|
||||
if (targetBlock < 1 || cursor === undefined) {
|
||||
return;
|
||||
}
|
||||
await loadAnnotationBlock(targetBlock, cursor);
|
||||
}
|
||||
|
||||
async function loadNextAnnotationBlock(): Promise<void> {
|
||||
if (annotationBlockState.nextCursor === null) {
|
||||
return;
|
||||
}
|
||||
await loadAnnotationBlock(annotationBlockState.currentBlock + 1, annotationBlockState.nextCursor);
|
||||
}
|
||||
|
||||
async function openWindow(command: "open_demo_store_core_window" | "open_demo_store_replay_candidates_window"): Promise<void> {
|
||||
try {
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_decode_replay", `Invoke ${command}`);
|
||||
@@ -357,7 +406,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
element<HTMLButtonElement>("#startDecodeReplayButton").addEventListener("click", () => void executeDecodeReplay());
|
||||
element<HTMLButtonElement>("#cancelDecodeReplayButton").addEventListener("click", () => void cancelDecodeReplay());
|
||||
element<HTMLButtonElement>("#loadDecodeDiagnosticsButton").addEventListener("click", () => void loadDiagnostics());
|
||||
element<HTMLButtonElement>("#loadAnnotationsButton").addEventListener("click", () => void loadAnnotations());
|
||||
element<HTMLButtonElement>("#loadAnnotationsButton").addEventListener("click", () => void startAnnotationLoad());
|
||||
element<HTMLButtonElement>(annotationBlockControls.firstSelector).addEventListener("click", () => void loadAnnotationBlock(1, null));
|
||||
element<HTMLButtonElement>(annotationBlockControls.previousSelector).addEventListener("click", () => void loadPreviousAnnotationBlock());
|
||||
element<HTMLButtonElement>(annotationBlockControls.nextSelector).addEventListener("click", () => void loadNextAnnotationBlock());
|
||||
element<HTMLInputElement>("#annotationSignatureInput").addEventListener("input", () => {
|
||||
annotationBaseRequest = null;
|
||||
markStoreBlockFiltersDirty(annotationBlockState, annotationBlockControls);
|
||||
});
|
||||
element<HTMLButtonElement>("#openReplayCandidatesFromDecodeButton").addEventListener("click", () => void openWindow("open_demo_store_replay_candidates_window"));
|
||||
element<HTMLButtonElement>("#openCoreDiagnosticsFromDecodeButton").addEventListener("click", () => void openWindow("open_demo_store_core_window"));
|
||||
void listen<DemoDecodeReplayProgressPayload>("demo-decode-replay-progress", event => appendLog(event.payload));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_execution_metadata.ts
|
||||
// version: 14
|
||||
// version: 15
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -479,7 +479,9 @@ async function prepareProfile(): Promise<void> {
|
||||
setExecutionRunning(executionRunning);
|
||||
updateProfileStatus("Préparation", "badge text-bg-warning");
|
||||
try {
|
||||
frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", `Invoke demo_execution_devnet_prepare_profile profile=${profileName}`);
|
||||
const readiness = await invoke<DemoExecutionDevnetStoreReadinessPayload>("demo_execution_devnet_prepare_profile", { profileName });
|
||||
frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", `Invoke demo_execution_devnet_prepare_profile completed profile=${profileName} resources=${readiness.availableResourcesAfter}/${readiness.expectedResources}`);
|
||||
synchronizeProfileSelections(profileName);
|
||||
window.localStorage.setItem(profileStorageKey, profileName);
|
||||
for (const selector of profileOutputSelectors) {
|
||||
@@ -580,9 +582,18 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
void prepareProfile();
|
||||
});
|
||||
}
|
||||
element<HTMLButtonElement>("#prepareMetadataProfileButton").addEventListener("click", () => void prepareProfile());
|
||||
element<HTMLButtonElement>("#prepareToken2022MetadataProfileButton").addEventListener("click", () => void prepareProfile());
|
||||
element<HTMLButtonElement>("#prepareSolanaProgramMetadataProfileButton").addEventListener("click", () => void prepareProfile());
|
||||
element<HTMLButtonElement>("#prepareMetadataProfileButton").addEventListener("click", () => {
|
||||
frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", "Metaplex profile load button clicked");
|
||||
void prepareProfile();
|
||||
});
|
||||
element<HTMLButtonElement>("#prepareToken2022MetadataProfileButton").addEventListener("click", () => {
|
||||
frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", "Token-2022 Metadata profile load button clicked");
|
||||
void prepareProfile();
|
||||
});
|
||||
element<HTMLButtonElement>("#prepareSolanaProgramMetadataProfileButton").addEventListener("click", () => {
|
||||
frontendDebug("kb-app-demo-desktop.frontend.execution_metadata", "Solana Program Metadata profile load button clicked");
|
||||
void prepareProfile();
|
||||
});
|
||||
element<HTMLButtonElement>("#refreshMetadataScenariosButton").addEventListener("click", () => void loadScenarios());
|
||||
element<HTMLTextAreaElement>("#metadataExecutionLogOutput").addEventListener("app-log-clear", () => { metadataLogLines.length = 0; });
|
||||
void listen<DemoExecutionMetadataProgressPayload>("demo-execution-metadata-progress", event => appendMetadataLog(event.payload));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_execution_spl.ts
|
||||
// version: 11
|
||||
// version: 13
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -19,13 +19,23 @@ import type { DemoExecutionMemoSummaryPayload } from "./bindings/kb_app_demo_des
|
||||
import type { DemoExecutionSplTokenRequest } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoExecutionSplTokenRequest.ts";
|
||||
import type { DemoExecutionSplTokenSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoExecutionSplTokenSummaryPayload.ts";
|
||||
import type { DemoSplTokenJournalRequest } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRequest.ts";
|
||||
import type { DemoSplTokenJournalPagePayload } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalPagePayload.ts";
|
||||
import type { DemoSplTokenJournalRow } from "./bindings/kb_app_demo_desktop/demo_spl_token/DemoSplTokenJournalRow.ts";
|
||||
import type { DemoExecutionSplAtaRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaRequest.ts";
|
||||
import type { DemoExecutionSplAtaSummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoExecutionSplAtaSummaryPayload.ts";
|
||||
import type { DemoSplAtaDerivationPayload } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationPayload.ts";
|
||||
import type { DemoSplAtaDerivationRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaDerivationRequest.ts";
|
||||
import type { DemoSplAtaJournalRequest } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRequest.ts";
|
||||
import type { DemoSplAtaJournalRow } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalRow.ts";
|
||||
import type { DemoSplAtaJournalPagePayload } from "./bindings/kb_app_demo_desktop/demo_spl_ata/DemoSplAtaJournalPagePayload.ts";
|
||||
import {
|
||||
applyStoreBlockMetadata,
|
||||
createStoreBlockState,
|
||||
cursorForBlock,
|
||||
markStoreBlockFiltersDirty,
|
||||
renderStoreBlockControls,
|
||||
resetStoreBlockState,
|
||||
type StoreBlockControls,
|
||||
} from "./demo_store_block_pagination";
|
||||
import type { DemoExecutionSplToken2022Request } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoExecutionSplToken2022Request.ts";
|
||||
import type { DemoExecutionSplToken2022SummaryPayload } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoExecutionSplToken2022SummaryPayload.ts";
|
||||
import type { DemoSplToken2022FixturePayload } from "./bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoSplToken2022FixturePayload.ts";
|
||||
@@ -40,6 +50,24 @@ let running = false;
|
||||
let profileStoreReady = false;
|
||||
let profileOptions: DemoExecutionSolanaCoreProfileOption[] = [];
|
||||
let token_2022Fixture: DemoSplToken2022FixturePayload | null = null;
|
||||
const tokenJournalBlockState = createStoreBlockState();
|
||||
const ataJournalBlockState = createStoreBlockState();
|
||||
const tokenJournalBlockControls: StoreBlockControls = {
|
||||
firstSelector: "#tokenJournalFirstBlockButton",
|
||||
previousSelector: "#tokenJournalPreviousBlockButton",
|
||||
nextSelector: "#tokenJournalNextBlockButton",
|
||||
summarySelector: "#tokenJournalBlockSummary",
|
||||
};
|
||||
const ataJournalBlockControls: StoreBlockControls = {
|
||||
firstSelector: "#ataJournalFirstBlockButton",
|
||||
previousSelector: "#ataJournalPreviousBlockButton",
|
||||
nextSelector: "#ataJournalNextBlockButton",
|
||||
summarySelector: "#ataJournalBlockSummary",
|
||||
};
|
||||
type TokenJournalBaseRequest = Omit<DemoSplTokenJournalRequest, "cursor" | "blockIndex">;
|
||||
type AtaJournalBaseRequest = Omit<DemoSplAtaJournalRequest, "cursor" | "blockIndex">;
|
||||
let tokenJournalBaseRequest: TokenJournalBaseRequest | null = null;
|
||||
let ataJournalBaseRequest: AtaJournalBaseRequest | null = null;
|
||||
|
||||
function element<T extends HTMLElement>(selector: string): T {
|
||||
const value = document.querySelector<T>(selector);
|
||||
@@ -400,7 +428,7 @@ async function executeAta(submit: boolean): Promise<void> {
|
||||
element<HTMLInputElement>("#ataJournalSignatureInput").value = summary.transactionSignature;
|
||||
element<HTMLInputElement>("#ataJournalMintInput").value = request.mint;
|
||||
element<HTMLInputElement>("#ataJournalAddressInput").value = summary.associatedTokenAccount;
|
||||
await loadAtaJournal();
|
||||
await startAtaJournalLoad();
|
||||
}
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
@@ -412,27 +440,52 @@ async function executeAta(submit: boolean): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function ataJournalRequest(): DemoSplAtaJournalRequest {
|
||||
function ataJournalBaseFilters(): AtaJournalBaseRequest {
|
||||
return {
|
||||
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
|
||||
signatureContains: optionalText("#ataJournalSignatureInput"),
|
||||
mint: optionalText("#ataJournalMintInput"),
|
||||
associatedTokenAccount: optionalText("#ataJournalAddressInput"),
|
||||
operation: optionalText("#ataJournalOperationInput"),
|
||||
limit: integerValue("#ataJournalLimitInput"),
|
||||
};
|
||||
}
|
||||
|
||||
async function loadAtaJournal(): Promise<void> {
|
||||
async function startAtaJournalLoad(): Promise<void> {
|
||||
ataJournalBaseRequest = ataJournalBaseFilters();
|
||||
resetStoreBlockState(ataJournalBlockState);
|
||||
await loadAtaJournalBlock(1);
|
||||
}
|
||||
|
||||
async function loadAtaJournalBlock(blockIndex: number): Promise<void> {
|
||||
if (ataJournalBaseRequest === null) {
|
||||
markStoreBlockFiltersDirty(ataJournalBlockState, ataJournalBlockControls);
|
||||
return;
|
||||
}
|
||||
const cursor = cursorForBlock(ataJournalBlockState, blockIndex);
|
||||
if (cursor === undefined) {
|
||||
return;
|
||||
}
|
||||
const button = element<HTMLButtonElement>("#refreshAtaJournalButton");
|
||||
button.disabled = true;
|
||||
element<HTMLButtonElement>(ataJournalBlockControls.firstSelector).disabled = true;
|
||||
element<HTMLButtonElement>(ataJournalBlockControls.previousSelector).disabled = true;
|
||||
element<HTMLButtonElement>(ataJournalBlockControls.nextSelector).disabled = true;
|
||||
const request: DemoSplAtaJournalRequest = {
|
||||
...ataJournalBaseRequest,
|
||||
cursor,
|
||||
blockIndex,
|
||||
};
|
||||
try {
|
||||
const rows = await invoke<DemoSplAtaJournalRow[]>("demo_spl_ata_journal", { request: ataJournalRequest() });
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_spl_ata_journal block=${blockIndex}`);
|
||||
const payload = await invoke<DemoSplAtaJournalPagePayload>("demo_spl_ata_journal", { request });
|
||||
applyStoreBlockMetadata(ataJournalBlockState, payload.page, ataJournalBlockControls);
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_spl_ata_journal completed block=${payload.page.blockIndex}/${payload.page.totalBlocks} rows=${payload.rows.length}`);
|
||||
element<HTMLTextAreaElement>("#ataJournalOutput").value = JSON.stringify({
|
||||
title: "Journal lifecycle ATA",
|
||||
status: "success",
|
||||
count: rows.length,
|
||||
rows: rows.map(row => ({
|
||||
block: payload.page,
|
||||
count: payload.rows.length,
|
||||
rows: payload.rows.map(row => ({
|
||||
...row,
|
||||
payloadJson: parsedJson(row.payloadJson),
|
||||
})),
|
||||
@@ -445,11 +498,17 @@ async function loadAtaJournal(): Promise<void> {
|
||||
message,
|
||||
error: message,
|
||||
}, null, 2);
|
||||
renderStoreBlockControls(ataJournalBlockState, ataJournalBlockControls);
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function markAtaJournalFiltersDirty(): void {
|
||||
ataJournalBaseRequest = null;
|
||||
markStoreBlockFiltersDirty(ataJournalBlockState, ataJournalBlockControls);
|
||||
}
|
||||
|
||||
async function execute(submit: boolean): Promise<void> {
|
||||
if (running) {
|
||||
return;
|
||||
@@ -567,7 +626,7 @@ async function executeToken(submit: boolean): Promise<void> {
|
||||
});
|
||||
if (summary.transactionSignature) {
|
||||
element<HTMLInputElement>("#tokenJournalSignatureInput").value = summary.transactionSignature;
|
||||
await loadTokenJournal();
|
||||
await startTokenJournalLoad();
|
||||
}
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
@@ -579,7 +638,7 @@ async function executeToken(submit: boolean): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function tokenJournalRequest(): DemoSplTokenJournalRequest {
|
||||
function tokenJournalBaseFilters(): TokenJournalBaseRequest {
|
||||
return {
|
||||
profileName: element<HTMLSelectElement>("#executionProfileSelect").value,
|
||||
signatureContains: optionalText("#tokenJournalSignatureInput"),
|
||||
@@ -587,7 +646,6 @@ function tokenJournalRequest(): DemoSplTokenJournalRequest {
|
||||
account: optionalText("#tokenJournalAccountInput"),
|
||||
family: element<HTMLSelectElement>("#tokenJournalFamilySelect").value || null,
|
||||
operation: optionalText("#tokenJournalOperationInput"),
|
||||
limit: integerValue("#tokenJournalLimitInput"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -831,17 +889,42 @@ async function executeToken2022(submit: boolean): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTokenJournal(): Promise<void> {
|
||||
async function startTokenJournalLoad(): Promise<void> {
|
||||
tokenJournalBaseRequest = tokenJournalBaseFilters();
|
||||
resetStoreBlockState(tokenJournalBlockState);
|
||||
await loadTokenJournalBlock(1);
|
||||
}
|
||||
|
||||
async function loadTokenJournalBlock(blockIndex: number): Promise<void> {
|
||||
if (tokenJournalBaseRequest === null) {
|
||||
markStoreBlockFiltersDirty(tokenJournalBlockState, tokenJournalBlockControls);
|
||||
return;
|
||||
}
|
||||
const cursor = cursorForBlock(tokenJournalBlockState, blockIndex);
|
||||
if (cursor === undefined) {
|
||||
return;
|
||||
}
|
||||
const button = element<HTMLButtonElement>("#refreshTokenJournalButton");
|
||||
button.disabled = true;
|
||||
element<HTMLButtonElement>(tokenJournalBlockControls.firstSelector).disabled = true;
|
||||
element<HTMLButtonElement>(tokenJournalBlockControls.previousSelector).disabled = true;
|
||||
element<HTMLButtonElement>(tokenJournalBlockControls.nextSelector).disabled = true;
|
||||
const request: DemoSplTokenJournalRequest = {
|
||||
...tokenJournalBaseRequest,
|
||||
cursor,
|
||||
blockIndex,
|
||||
};
|
||||
try {
|
||||
const rows = await invoke<DemoSplTokenJournalRow[]>("demo_spl_token_journal", { request: tokenJournalRequest() });
|
||||
renderTokenJournal(rows);
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_spl_token_journal block=${blockIndex}`);
|
||||
const payload = await invoke<DemoSplTokenJournalPagePayload>("demo_spl_token_journal", { request });
|
||||
applyStoreBlockMetadata(tokenJournalBlockState, payload.page, tokenJournalBlockControls);
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_spl_token_journal completed block=${payload.page.blockIndex}/${payload.page.totalBlocks} rows=${payload.rows.length}`);
|
||||
renderTokenJournal(payload.rows);
|
||||
appendLog({
|
||||
timestamp: new Date().toISOString(),
|
||||
level: "info",
|
||||
stage: "spl_token_journal",
|
||||
message: `${rows.length} événement(s) matérialisé(s) chargé(s).`,
|
||||
message: `${payload.rows.length} événement(s) matérialisé(s) chargé(s) dans le bloc ${payload.page.blockIndex}/${payload.page.totalBlocks}.`,
|
||||
});
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
@@ -851,12 +934,18 @@ async function loadTokenJournal(): Promise<void> {
|
||||
message,
|
||||
error: message,
|
||||
}, null, 2);
|
||||
renderStoreBlockControls(tokenJournalBlockState, tokenJournalBlockControls);
|
||||
frontendError("kb-app-demo-desktop.frontend.demo_execution_spl", `Token journal loading failed: ${message}`);
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function markTokenJournalFiltersDirty(): void {
|
||||
tokenJournalBaseRequest = null;
|
||||
markStoreBlockFiltersDirty(tokenJournalBlockState, tokenJournalBlockControls);
|
||||
}
|
||||
|
||||
async function generateRecipient(): Promise<void> {
|
||||
try {
|
||||
const payload = await invoke<DemoExecutionSolanaCoreGeneratedRecipientPayload>("demo_execution_solana_core_generate_recipient");
|
||||
@@ -898,7 +987,9 @@ async function prepareSelectedProfile(): Promise<void> {
|
||||
}
|
||||
appendLog({ timestamp: new Date().toISOString(), level: "info", stage: "store_prepare", message: `Vérification du store du profil ${profile.name}.` });
|
||||
try {
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_execution_devnet_prepare_profile profile=${profile.name}`);
|
||||
const readiness = await invoke<DemoExecutionDevnetStoreReadinessPayload>("demo_execution_devnet_prepare_profile", { profileName: profile.name });
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", `Invoke demo_execution_devnet_prepare_profile completed profile=${profile.name} resources=${readiness.availableResourcesAfter}/${readiness.expectedResources}`);
|
||||
profileStoreReady = true;
|
||||
appendLog({ timestamp: new Date().toISOString(), level: "info", stage: "store_ready", message: `Store ${readiness.backend} prêt: ${readiness.availableResourcesAfter}/${readiness.expectedResources} ressources, créées=${readiness.createdResources}, autoInit=${readiness.autoInitializeSchema}.` });
|
||||
} catch (caughtError) {
|
||||
@@ -948,6 +1039,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(item => new bootstrap.Tooltip(item));
|
||||
element<HTMLSelectElement>("#executionProfileSelect").addEventListener("change", () => {
|
||||
updateProfileHelp();
|
||||
markTokenJournalFiltersDirty();
|
||||
markAtaJournalFiltersDirty();
|
||||
void prepareSelectedProfile();
|
||||
});
|
||||
element<HTMLButtonElement>("#generateRecipientButton").addEventListener("click", () => {
|
||||
@@ -982,8 +1075,27 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
void executeToken2022(true);
|
||||
});
|
||||
element<HTMLButtonElement>("#refreshTokenJournalButton").addEventListener("click", () => {
|
||||
void loadTokenJournal();
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", "Token journal load button clicked");
|
||||
void startTokenJournalLoad();
|
||||
});
|
||||
element<HTMLButtonElement>("#tokenJournalFirstBlockButton").addEventListener("click", () => {
|
||||
void loadTokenJournalBlock(1);
|
||||
});
|
||||
element<HTMLButtonElement>("#tokenJournalPreviousBlockButton").addEventListener("click", () => {
|
||||
void loadTokenJournalBlock(tokenJournalBlockState.currentBlock - 1);
|
||||
});
|
||||
element<HTMLButtonElement>("#tokenJournalNextBlockButton").addEventListener("click", () => {
|
||||
void loadTokenJournalBlock(tokenJournalBlockState.currentBlock + 1);
|
||||
});
|
||||
for (const selector of [
|
||||
"#tokenJournalSignatureInput",
|
||||
"#tokenJournalMintInput",
|
||||
"#tokenJournalAccountInput",
|
||||
"#tokenJournalOperationInput",
|
||||
]) {
|
||||
element<HTMLInputElement>(selector).addEventListener("input", markTokenJournalFiltersDirty);
|
||||
}
|
||||
element<HTMLSelectElement>("#tokenJournalFamilySelect").addEventListener("change", markTokenJournalFiltersDirty);
|
||||
element<HTMLButtonElement>("#deriveAtaButton").addEventListener("click", () => {
|
||||
void deriveAta();
|
||||
});
|
||||
@@ -994,8 +1106,26 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
void executeAta(true);
|
||||
});
|
||||
element<HTMLButtonElement>("#refreshAtaJournalButton").addEventListener("click", () => {
|
||||
void loadAtaJournal();
|
||||
frontendDebug("kb-app-demo-desktop.frontend.demo_execution_spl", "ATA journal load button clicked");
|
||||
void startAtaJournalLoad();
|
||||
});
|
||||
element<HTMLButtonElement>("#ataJournalFirstBlockButton").addEventListener("click", () => {
|
||||
void loadAtaJournalBlock(1);
|
||||
});
|
||||
element<HTMLButtonElement>("#ataJournalPreviousBlockButton").addEventListener("click", () => {
|
||||
void loadAtaJournalBlock(ataJournalBlockState.currentBlock - 1);
|
||||
});
|
||||
element<HTMLButtonElement>("#ataJournalNextBlockButton").addEventListener("click", () => {
|
||||
void loadAtaJournalBlock(ataJournalBlockState.currentBlock + 1);
|
||||
});
|
||||
for (const selector of [
|
||||
"#ataJournalSignatureInput",
|
||||
"#ataJournalMintInput",
|
||||
"#ataJournalAddressInput",
|
||||
"#ataJournalOperationInput",
|
||||
]) {
|
||||
element<HTMLInputElement>(selector).addEventListener("input", markAtaJournalFiltersDirty);
|
||||
}
|
||||
element<HTMLButtonElement>("#cancelExecutionButton").addEventListener("click", () => {
|
||||
void cancelExecution();
|
||||
});
|
||||
|
||||
114
kb-app-demo-desktop/frontend/ts/demo_store_block_pagination.ts
Normal file
114
kb-app-demo-desktop/frontend/ts/demo_store_block_pagination.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_store_block_pagination.ts
|
||||
// version: 1
|
||||
|
||||
export type StoreBlockMetadata = {
|
||||
blockSize: number;
|
||||
blockIndex: number;
|
||||
totalRows: string;
|
||||
totalBlocks: number;
|
||||
nextCursor: string | null;
|
||||
};
|
||||
|
||||
export type StoreBlockState = {
|
||||
blockSize: number;
|
||||
currentBlock: number;
|
||||
totalBlocks: number;
|
||||
totalRows: string;
|
||||
nextCursor: string | null;
|
||||
cursors: Map<number, string | null>;
|
||||
};
|
||||
|
||||
export type StoreBlockControls = {
|
||||
firstSelector: string;
|
||||
previousSelector: string;
|
||||
nextSelector: string;
|
||||
summarySelector: string;
|
||||
};
|
||||
|
||||
export function createStoreBlockState(): StoreBlockState {
|
||||
return {
|
||||
blockSize: 500,
|
||||
currentBlock: 0,
|
||||
totalBlocks: 0,
|
||||
totalRows: "0",
|
||||
nextCursor: null,
|
||||
cursors: new Map<number, string | null>([[1, null]]),
|
||||
};
|
||||
}
|
||||
|
||||
export function resetStoreBlockState(state: StoreBlockState): void {
|
||||
state.blockSize = 500;
|
||||
state.currentBlock = 0;
|
||||
state.totalBlocks = 0;
|
||||
state.totalRows = "0";
|
||||
state.nextCursor = null;
|
||||
state.cursors = new Map<number, string | null>([[1, null]]);
|
||||
}
|
||||
|
||||
export function applyStoreBlockMetadata(
|
||||
state: StoreBlockState,
|
||||
metadata: StoreBlockMetadata,
|
||||
controls: StoreBlockControls,
|
||||
): void {
|
||||
state.blockSize = metadata.blockSize;
|
||||
state.currentBlock = metadata.blockIndex;
|
||||
state.totalBlocks = metadata.totalBlocks;
|
||||
state.totalRows = metadata.totalRows;
|
||||
state.nextCursor = metadata.nextCursor;
|
||||
if (metadata.blockIndex > 0 && metadata.nextCursor !== null) {
|
||||
state.cursors.set(metadata.blockIndex + 1, metadata.nextCursor);
|
||||
}
|
||||
renderStoreBlockControls(state, controls);
|
||||
}
|
||||
|
||||
export function renderStoreBlockControls(
|
||||
state: StoreBlockState,
|
||||
controls: StoreBlockControls,
|
||||
): void {
|
||||
const firstButton = document.querySelector<HTMLButtonElement>(controls.firstSelector);
|
||||
const previousButton = document.querySelector<HTMLButtonElement>(controls.previousSelector);
|
||||
const nextButton = document.querySelector<HTMLButtonElement>(controls.nextSelector);
|
||||
const summary = document.querySelector<HTMLElement>(controls.summarySelector);
|
||||
const hasRows = state.totalBlocks > 0;
|
||||
if (firstButton !== null) {
|
||||
firstButton.disabled = !hasRows || state.currentBlock <= 1;
|
||||
}
|
||||
if (previousButton !== null) {
|
||||
previousButton.disabled = !hasRows || state.currentBlock <= 1;
|
||||
}
|
||||
if (nextButton !== null) {
|
||||
nextButton.disabled = state.nextCursor === null;
|
||||
}
|
||||
if (summary !== null) {
|
||||
summary.textContent = hasRows
|
||||
? `Bloc ${state.currentBlock} / ${state.totalBlocks} · ${state.totalRows} résultat(s) · ${state.blockSize}/bloc`
|
||||
: `Bloc 0 / 0 · 0 résultat · ${state.blockSize}/bloc`;
|
||||
}
|
||||
}
|
||||
|
||||
export function markStoreBlockFiltersDirty(
|
||||
state: StoreBlockState,
|
||||
controls: StoreBlockControls,
|
||||
): void {
|
||||
resetStoreBlockState(state);
|
||||
const firstButton = document.querySelector<HTMLButtonElement>(controls.firstSelector);
|
||||
const previousButton = document.querySelector<HTMLButtonElement>(controls.previousSelector);
|
||||
const nextButton = document.querySelector<HTMLButtonElement>(controls.nextSelector);
|
||||
const summary = document.querySelector<HTMLElement>(controls.summarySelector);
|
||||
if (firstButton !== null) {
|
||||
firstButton.disabled = true;
|
||||
}
|
||||
if (previousButton !== null) {
|
||||
previousButton.disabled = true;
|
||||
}
|
||||
if (nextButton !== null) {
|
||||
nextButton.disabled = true;
|
||||
}
|
||||
if (summary !== null) {
|
||||
summary.textContent = "Filtres modifiés — rechargez le premier bloc.";
|
||||
}
|
||||
}
|
||||
|
||||
export function cursorForBlock(state: StoreBlockState, blockIndex: number): string | null | undefined {
|
||||
return state.cursors.get(blockIndex);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_store_core.ts
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -35,7 +35,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_store_core", "Core store demo window loaded");
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||
document.querySelector<HTMLButtonElement>("#refreshSqlCoreButton")?.addEventListener("click", () => {
|
||||
document.querySelector<HTMLButtonElement>("#refreshStoreCoreButton")?.addEventListener("click", () => {
|
||||
void refreshCore();
|
||||
});
|
||||
void refreshCore();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_store_diag.ts
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -50,10 +50,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_store_diag", "Store diagnostics demo window loaded");
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||
document.querySelector<HTMLButtonElement>("#refreshSqlDiagButton")?.addEventListener("click", () => {
|
||||
document.querySelector<HTMLButtonElement>("#refreshStoreDiagButton")?.addEventListener("click", () => {
|
||||
void refreshDiagnostics();
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("#copySqlDiagButton")?.addEventListener("click", () => {
|
||||
document.querySelector<HTMLButtonElement>("#copyStoreDiagButton")?.addEventListener("click", () => {
|
||||
void copyDiagnostics();
|
||||
});
|
||||
void refreshDiagnostics();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_store_raw.ts
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -35,7 +35,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
frontendDebug("kb_app_demo_desktop.frontend.demo_store_raw", "Raw store demo window loaded");
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||
document.querySelector<HTMLButtonElement>("#refreshSqlRawButton")?.addEventListener("click", () => {
|
||||
document.querySelector<HTMLButtonElement>("#refreshStoreRawButton")?.addEventListener("click", () => {
|
||||
void refreshRaw();
|
||||
});
|
||||
void refreshRaw();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/frontend/ts/demo_store_replay_candidates.ts
|
||||
// version: 12
|
||||
// version: 14
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "simplebar";
|
||||
@@ -10,12 +10,25 @@ import "datatables.net-select-bs5";
|
||||
import "datatables.net-select-bs5/css/select.bootstrap5.css";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { frontendDebug, frontendError, installFrontendConsoleBridge } from "./frontend_log";
|
||||
import {
|
||||
applyStoreBlockMetadata,
|
||||
createStoreBlockState,
|
||||
cursorForBlock,
|
||||
markStoreBlockFiltersDirty,
|
||||
resetStoreBlockState,
|
||||
renderStoreBlockControls,
|
||||
type StoreBlockControls,
|
||||
type StoreBlockState,
|
||||
} from "./demo_store_block_pagination";
|
||||
import type { DemoStoreReplayEntityPagePayload } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayEntityPagePayload";
|
||||
import type { DemoStoreReplayEntityRequest } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayEntityRequest";
|
||||
import type { DemoStoreReplayEntityRow } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayEntityRow";
|
||||
import type { DemoStoreReplayKnownProgramOption } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayKnownProgramOption";
|
||||
import type { DemoStoreReplayOptionsPayload } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayOptionsPayload";
|
||||
import type { DemoStoreReplayProgramPagePayload } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayProgramPagePayload";
|
||||
import type { DemoStoreReplayProgramRequest } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayProgramRequest";
|
||||
import type { DemoStoreReplayProgramRow } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayProgramRow";
|
||||
import type { DemoStoreReplayTransactionPagePayload } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayTransactionPagePayload";
|
||||
import type { DemoStoreReplayTransactionRequest } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayTransactionRequest";
|
||||
import type { DemoStoreReplayTransactionRow } from "./bindings/kb_app_demo_desktop/demo_store_replay_candidates/DemoStoreReplayTransactionRow";
|
||||
|
||||
@@ -30,7 +43,7 @@ type EntityViewConfig = {
|
||||
pluralLabel: string;
|
||||
tableSelector: string;
|
||||
containsSelector: string;
|
||||
limitSelector: string;
|
||||
blockControls: StoreBlockControls;
|
||||
summarySelector: string;
|
||||
loadButtonSelector: string;
|
||||
resetButtonSelector: string;
|
||||
@@ -48,7 +61,12 @@ const entityViewConfigs: EntityViewConfig[] = [
|
||||
pluralLabel: "mints",
|
||||
tableSelector: "#mintCandidateTable",
|
||||
containsSelector: "#mintContainsInput",
|
||||
limitSelector: "#mintLimitInput",
|
||||
blockControls: {
|
||||
firstSelector: "#mintFirstBlockButton",
|
||||
previousSelector: "#mintPreviousBlockButton",
|
||||
nextSelector: "#mintNextBlockButton",
|
||||
summarySelector: "#mintBlockSummary",
|
||||
},
|
||||
summarySelector: "#mintResultSummary",
|
||||
loadButtonSelector: "#loadMintsButton",
|
||||
resetButtonSelector: "#resetMintsButton",
|
||||
@@ -63,7 +81,12 @@ const entityViewConfigs: EntityViewConfig[] = [
|
||||
pluralLabel: "owners",
|
||||
tableSelector: "#ownerCandidateTable",
|
||||
containsSelector: "#ownerContainsInput",
|
||||
limitSelector: "#ownerLimitInput",
|
||||
blockControls: {
|
||||
firstSelector: "#ownerFirstBlockButton",
|
||||
previousSelector: "#ownerPreviousBlockButton",
|
||||
nextSelector: "#ownerNextBlockButton",
|
||||
summarySelector: "#ownerBlockSummary",
|
||||
},
|
||||
summarySelector: "#ownerResultSummary",
|
||||
loadButtonSelector: "#loadOwnersButton",
|
||||
resetButtonSelector: "#resetOwnersButton",
|
||||
@@ -78,7 +101,12 @@ const entityViewConfigs: EntityViewConfig[] = [
|
||||
pluralLabel: "comptes",
|
||||
tableSelector: "#accountCandidateTable",
|
||||
containsSelector: "#accountContainsInput",
|
||||
limitSelector: "#accountLimitInput",
|
||||
blockControls: {
|
||||
firstSelector: "#accountFirstBlockButton",
|
||||
previousSelector: "#accountPreviousBlockButton",
|
||||
nextSelector: "#accountNextBlockButton",
|
||||
summarySelector: "#accountBlockSummary",
|
||||
},
|
||||
summarySelector: "#accountResultSummary",
|
||||
loadButtonSelector: "#loadAccountsButton",
|
||||
resetButtonSelector: "#resetAccountsButton",
|
||||
@@ -94,7 +122,31 @@ let mintTable: Api<DemoStoreReplayEntityRow> | null = null;
|
||||
let ownerTable: Api<DemoStoreReplayEntityRow> | null = null;
|
||||
let accountTable: Api<DemoStoreReplayEntityRow> | null = null;
|
||||
let busyOperationCount = 0;
|
||||
let maximumReplayCandidateLimit = 500;
|
||||
const transactionBlockControls: StoreBlockControls = {
|
||||
firstSelector: "#transactionFirstBlockButton",
|
||||
previousSelector: "#transactionPreviousBlockButton",
|
||||
nextSelector: "#transactionNextBlockButton",
|
||||
summarySelector: "#transactionBlockSummary",
|
||||
};
|
||||
const programBlockControls: StoreBlockControls = {
|
||||
firstSelector: "#programFirstBlockButton",
|
||||
previousSelector: "#programPreviousBlockButton",
|
||||
nextSelector: "#programNextBlockButton",
|
||||
summarySelector: "#programBlockSummary",
|
||||
};
|
||||
const transactionBlockState = createStoreBlockState();
|
||||
const programBlockState = createStoreBlockState();
|
||||
const entityBlockStates = new Map<EntityKindCode, StoreBlockState>([
|
||||
["mint", createStoreBlockState()],
|
||||
["owner", createStoreBlockState()],
|
||||
["account_key", createStoreBlockState()],
|
||||
]);
|
||||
type TransactionBaseRequest = Omit<DemoStoreReplayTransactionRequest, "cursor" | "blockIndex">;
|
||||
type ProgramBaseRequest = Omit<DemoStoreReplayProgramRequest, "cursor" | "blockIndex">;
|
||||
type EntityBaseRequest = Omit<DemoStoreReplayEntityRequest, "cursor" | "blockIndex">;
|
||||
let transactionBaseRequest: TransactionBaseRequest | null = null;
|
||||
let programBaseRequest: ProgramBaseRequest | null = null;
|
||||
const entityBaseRequests = new Map<EntityKindCode, EntityBaseRequest>();
|
||||
|
||||
function element<T extends HTMLElement>(selector: string): T {
|
||||
const value = document.querySelector<T>(selector);
|
||||
@@ -117,24 +169,6 @@ function requiredInputValue(selector: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
function positiveIntegerValue(selector: string): number {
|
||||
const value = Number.parseInt(requiredInputValue(selector), 10);
|
||||
if (!Number.isSafeInteger(value) || value <= 0) {
|
||||
throw new Error(`Valeur entière positive invalide pour ${selector}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function boundedPositiveIntegerValue(selector: string): number {
|
||||
const value = positiveIntegerValue(selector);
|
||||
if (value > maximumReplayCandidateLimit) {
|
||||
throw new Error(
|
||||
`La limite doit être comprise entre 1 et ${maximumReplayCandidateLimit} pour ${selector}`,
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function optionalNonNegativeIntegerValue(selector: string): number | null {
|
||||
const raw = optionalInputValue(selector);
|
||||
if (raw === null) {
|
||||
@@ -285,7 +319,7 @@ function createTransactionTable(): Api<DemoStoreReplayTransactionRow> {
|
||||
],
|
||||
order: [[2, "desc"]],
|
||||
pageLength: 10,
|
||||
lengthMenu: [10, 25, 50, 100, 250],
|
||||
lengthMenu: [10, 25, 50, 100, 250, 500],
|
||||
select: { style: "multi", selector: "td:first-child", headerCheckbox: "select-all" },
|
||||
language: commonLanguage(
|
||||
"Aucune transaction chargée",
|
||||
@@ -319,7 +353,7 @@ function createProgramTable(): Api<DemoStoreReplayProgramRow> {
|
||||
],
|
||||
order: [[3, "desc"]],
|
||||
pageLength: 10,
|
||||
lengthMenu: [10, 25, 50, 100, 250],
|
||||
lengthMenu: [10, 25, 50, 100, 250, 500],
|
||||
select: { style: "multi", selector: "td:first-child", headerCheckbox: "select-all" },
|
||||
language: commonLanguage(
|
||||
"Aucun programme chargé",
|
||||
@@ -352,7 +386,7 @@ function createEntityTable(config: EntityViewConfig): Api<DemoStoreReplayEntityR
|
||||
],
|
||||
order: [[2, "desc"]],
|
||||
pageLength: 10,
|
||||
lengthMenu: [10, 25, 50, 100, 250],
|
||||
lengthMenu: [10, 25, 50, 100, 250, 500],
|
||||
select: { style: "multi", selector: "td:first-child", headerCheckbox: "select-all" },
|
||||
language: commonLanguage(
|
||||
`Aucun ${config.label} chargé`,
|
||||
@@ -412,6 +446,21 @@ function selectedOrSingleFilteredRow<T>(table: Api<T>, label: string): T {
|
||||
throw new Error(`Cochez exactement un ${label}, ou réduisez le filtre local à une seule ligne.`);
|
||||
}
|
||||
|
||||
function restoreLoadedStoreBlockControls(): void {
|
||||
if (transactionBlockState.currentBlock > 0) {
|
||||
renderStoreBlockControls(transactionBlockState, transactionBlockControls);
|
||||
}
|
||||
if (programBlockState.currentBlock > 0) {
|
||||
renderStoreBlockControls(programBlockState, programBlockControls);
|
||||
}
|
||||
for (const config of entityViewConfigs) {
|
||||
const state = entityBlockState(config.kind);
|
||||
if (state.currentBlock > 0) {
|
||||
renderStoreBlockControls(state, config.blockControls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setBusy(busy: boolean, message: string): void {
|
||||
busyOperationCount = busy ? busyOperationCount + 1 : Math.max(0, busyOperationCount - 1);
|
||||
const operationInProgress = busyOperationCount > 0;
|
||||
@@ -419,13 +468,20 @@ function setBusy(busy: boolean, message: string): void {
|
||||
badge.textContent = operationInProgress ? (busy ? message : "Chargement en cours") : "Prêt";
|
||||
badge.className = operationInProgress ? "badge text-bg-warning" : "badge text-bg-success";
|
||||
document.querySelectorAll<HTMLButtonElement>("button").forEach(button => {
|
||||
if (button.id !== "openCoreExtractionButton") {
|
||||
button.disabled = operationInProgress;
|
||||
if (button.id === "openCoreExtractionButton") {
|
||||
return;
|
||||
}
|
||||
if (!operationInProgress && button.dataset.storeBlockNavigation === "true") {
|
||||
return;
|
||||
}
|
||||
button.disabled = operationInProgress;
|
||||
});
|
||||
if (!operationInProgress) {
|
||||
restoreLoadedStoreBlockControls();
|
||||
}
|
||||
}
|
||||
|
||||
function buildTransactionRequest(): DemoStoreReplayTransactionRequest {
|
||||
function buildTransactionBaseRequest(): TransactionBaseRequest {
|
||||
const entityKind = optionalInputValue("#transactionEntityKindSelect");
|
||||
const entityValue = optionalInputValue("#transactionEntityValueInput");
|
||||
if ((entityKind === null) !== (entityValue === null)) {
|
||||
@@ -441,11 +497,31 @@ function buildTransactionRequest(): DemoStoreReplayTransactionRequest {
|
||||
programScope: requiredInputValue("#transactionProgramScopeSelect"),
|
||||
entityKind,
|
||||
entityValue,
|
||||
limit: boundedPositiveIntegerValue("#transactionLimitInput"),
|
||||
newestFirst: element<HTMLInputElement>("#transactionNewestFirstInput").checked,
|
||||
};
|
||||
}
|
||||
|
||||
function buildProgramBaseRequest(): ProgramBaseRequest {
|
||||
return {
|
||||
programIdContains: optionalInputValue("#programContainsInput"),
|
||||
};
|
||||
}
|
||||
|
||||
function buildEntityBaseRequest(config: EntityViewConfig): EntityBaseRequest {
|
||||
return {
|
||||
entityKind: config.kind,
|
||||
entityValueContains: optionalInputValue(config.containsSelector),
|
||||
};
|
||||
}
|
||||
|
||||
function entityBlockState(kind: EntityKindCode): StoreBlockState {
|
||||
const state = entityBlockStates.get(kind);
|
||||
if (state === undefined) {
|
||||
throw new Error(`État de pagination Store manquant pour ${kind}.`);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
async function resetTransactionFilters(): Promise<void> {
|
||||
element<HTMLInputElement>("#transactionSignatureContainsInput").value = "";
|
||||
element<HTMLInputElement>("#transactionMinSlotInput").value = "";
|
||||
@@ -456,95 +532,190 @@ async function resetTransactionFilters(): Promise<void> {
|
||||
element<HTMLSelectElement>("#transactionProgramScopeSelect").value = "any";
|
||||
element<HTMLSelectElement>("#transactionEntityKindSelect").value = "";
|
||||
element<HTMLInputElement>("#transactionEntityValueInput").value = "";
|
||||
element<HTMLInputElement>("#transactionLimitInput").value = "500";
|
||||
element<HTMLInputElement>("#transactionNewestFirstInput").checked = true;
|
||||
resetTableState(transactionTable);
|
||||
await loadTransactions();
|
||||
await startTransactionLoad();
|
||||
}
|
||||
|
||||
async function resetProgramFilters(): Promise<void> {
|
||||
element<HTMLSelectElement>("#knownProgramSelect").value = "";
|
||||
element<HTMLInputElement>("#programContainsInput").value = "";
|
||||
element<HTMLInputElement>("#programLimitInput").value = "500";
|
||||
resetTableState(programTable);
|
||||
await loadPrograms();
|
||||
await startProgramLoad();
|
||||
}
|
||||
|
||||
async function resetEntityFilters(config: EntityViewConfig): Promise<void> {
|
||||
element<HTMLInputElement>(config.containsSelector).value = "";
|
||||
element<HTMLInputElement>(config.limitSelector).value = "500";
|
||||
resetTableState(entityTable(config.kind));
|
||||
await loadEntity(config);
|
||||
await startEntityLoad(config);
|
||||
}
|
||||
|
||||
async function loadTransactions(): Promise<void> {
|
||||
async function startTransactionLoad(): Promise<void> {
|
||||
transactionBaseRequest = buildTransactionBaseRequest();
|
||||
resetStoreBlockState(transactionBlockState);
|
||||
await loadTransactionBlock(1, null);
|
||||
}
|
||||
|
||||
async function loadTransactionBlock(blockIndex: number, cursor: string | null): Promise<void> {
|
||||
if (transactionTable === null) {
|
||||
return;
|
||||
}
|
||||
if (transactionBaseRequest === null) {
|
||||
throw new Error("Rechargez les transactions pour initialiser les filtres Store.");
|
||||
}
|
||||
try {
|
||||
setBusy(true, "Chargement transactions");
|
||||
const request = buildTransactionRequest();
|
||||
const rows = await invoke<DemoStoreReplayTransactionRow[]>("load_demo_store_replay_transactions", { request });
|
||||
replaceRows(transactionTable, rows);
|
||||
element<HTMLElement>("#transactionResultSummary").textContent = `${rows.length} transaction(s) chargée(s) depuis le store actif.`;
|
||||
frontendDebug(tracingTarget, `loaded ${rows.length} replay transaction candidates`);
|
||||
const request: DemoStoreReplayTransactionRequest = {
|
||||
...transactionBaseRequest,
|
||||
cursor,
|
||||
blockIndex,
|
||||
};
|
||||
const payload = await invoke<DemoStoreReplayTransactionPagePayload>(
|
||||
"load_demo_store_replay_transactions",
|
||||
{ request },
|
||||
);
|
||||
replaceRows(transactionTable, payload.rows);
|
||||
applyStoreBlockMetadata(transactionBlockState, payload.page, transactionBlockControls);
|
||||
element<HTMLElement>("#transactionResultSummary").textContent = `${payload.rows.length} transaction(s) dans le bloc Store courant.`;
|
||||
frontendDebug(tracingTarget, `loaded replay transaction block ${payload.page.blockIndex}/${payload.page.totalBlocks} with ${payload.rows.length} rows`);
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
element<HTMLElement>("#transactionResultSummary").textContent = `Erreur : ${message}`;
|
||||
renderStoreBlockControls(transactionBlockState, transactionBlockControls);
|
||||
frontendError(tracingTarget, `transaction candidate loading failed: ${message}`);
|
||||
} finally {
|
||||
setBusy(false, "Prêt");
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPrograms(): Promise<void> {
|
||||
async function startProgramLoad(): Promise<void> {
|
||||
programBaseRequest = buildProgramBaseRequest();
|
||||
resetStoreBlockState(programBlockState);
|
||||
await loadProgramBlock(1, null);
|
||||
}
|
||||
|
||||
async function loadProgramBlock(blockIndex: number, cursor: string | null): Promise<void> {
|
||||
if (programTable === null) {
|
||||
return;
|
||||
}
|
||||
if (programBaseRequest === null) {
|
||||
throw new Error("Rechargez les programmes pour initialiser les filtres Store.");
|
||||
}
|
||||
try {
|
||||
setBusy(true, "Chargement programmes");
|
||||
const request: DemoStoreReplayProgramRequest = {
|
||||
programIdContains: optionalInputValue("#programContainsInput"),
|
||||
limit: boundedPositiveIntegerValue("#programLimitInput"),
|
||||
...programBaseRequest,
|
||||
cursor,
|
||||
blockIndex,
|
||||
};
|
||||
const rows = await invoke<DemoStoreReplayProgramRow[]>("load_demo_store_replay_programs", { request });
|
||||
replaceRows(programTable, rows);
|
||||
element<HTMLElement>("#programResultSummary").textContent = `${rows.length} programme(s) chargé(s), avec occurrences top-level, inner et logs liés.`;
|
||||
frontendDebug(tracingTarget, `loaded ${rows.length} replay program summaries`);
|
||||
const payload = await invoke<DemoStoreReplayProgramPagePayload>(
|
||||
"load_demo_store_replay_programs",
|
||||
{ request },
|
||||
);
|
||||
replaceRows(programTable, payload.rows);
|
||||
applyStoreBlockMetadata(programBlockState, payload.page, programBlockControls);
|
||||
element<HTMLElement>("#programResultSummary").textContent = `${payload.rows.length} programme(s) dans le bloc Store courant, avec occurrences top-level, inner et logs liés.`;
|
||||
frontendDebug(tracingTarget, `loaded replay program block ${payload.page.blockIndex}/${payload.page.totalBlocks} with ${payload.rows.length} rows`);
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
element<HTMLElement>("#programResultSummary").textContent = `Erreur : ${message}`;
|
||||
renderStoreBlockControls(programBlockState, programBlockControls);
|
||||
frontendError(tracingTarget, `program summary loading failed: ${message}`);
|
||||
} finally {
|
||||
setBusy(false, "Prêt");
|
||||
}
|
||||
}
|
||||
|
||||
async function loadEntity(config: EntityViewConfig): Promise<void> {
|
||||
async function startEntityLoad(config: EntityViewConfig): Promise<void> {
|
||||
entityBaseRequests.set(config.kind, buildEntityBaseRequest(config));
|
||||
resetStoreBlockState(entityBlockState(config.kind));
|
||||
await loadEntityBlock(config, 1, null);
|
||||
}
|
||||
|
||||
async function loadEntityBlock(config: EntityViewConfig, blockIndex: number, cursor: string | null): Promise<void> {
|
||||
const table = entityTable(config.kind);
|
||||
if (table === null) {
|
||||
return;
|
||||
}
|
||||
const baseRequest = entityBaseRequests.get(config.kind);
|
||||
if (baseRequest === undefined) {
|
||||
throw new Error(`Rechargez les ${config.pluralLabel} pour initialiser les filtres Store.`);
|
||||
}
|
||||
try {
|
||||
setBusy(true, `Chargement ${config.pluralLabel}`);
|
||||
const request: DemoStoreReplayEntityRequest = {
|
||||
entityKind: config.kind,
|
||||
entityValueContains: optionalInputValue(config.containsSelector),
|
||||
limit: boundedPositiveIntegerValue(config.limitSelector),
|
||||
...baseRequest,
|
||||
cursor,
|
||||
blockIndex,
|
||||
};
|
||||
const rows = await invoke<DemoStoreReplayEntityRow[]>("load_demo_store_replay_entities", { request });
|
||||
replaceRows(table, rows);
|
||||
element<HTMLElement>(config.summarySelector).textContent = `${rows.length} ${config.pluralLabel} chargé(s) depuis le store actif.`;
|
||||
frontendDebug(tracingTarget, `loaded ${rows.length} replay ${config.kind} summaries`);
|
||||
const payload = await invoke<DemoStoreReplayEntityPagePayload>(
|
||||
"load_demo_store_replay_entities",
|
||||
{ request },
|
||||
);
|
||||
replaceRows(table, payload.rows);
|
||||
applyStoreBlockMetadata(entityBlockState(config.kind), payload.page, config.blockControls);
|
||||
element<HTMLElement>(config.summarySelector).textContent = `${payload.rows.length} ${config.pluralLabel} dans le bloc Store courant.`;
|
||||
frontendDebug(tracingTarget, `loaded replay ${config.kind} block ${payload.page.blockIndex}/${payload.page.totalBlocks} with ${payload.rows.length} rows`);
|
||||
} catch (caughtError) {
|
||||
const message = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
||||
element<HTMLElement>(config.summarySelector).textContent = `Erreur : ${message}`;
|
||||
renderStoreBlockControls(entityBlockState(config.kind), config.blockControls);
|
||||
frontendError(tracingTarget, `${config.kind} summary loading failed: ${message}`);
|
||||
} finally {
|
||||
setBusy(false, "Prêt");
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPreviousTransactionBlock(): Promise<void> {
|
||||
const targetBlock = transactionBlockState.currentBlock - 1;
|
||||
const cursor = cursorForBlock(transactionBlockState, targetBlock);
|
||||
if (targetBlock < 1 || cursor === undefined) {
|
||||
return;
|
||||
}
|
||||
await loadTransactionBlock(targetBlock, cursor);
|
||||
}
|
||||
|
||||
async function loadNextTransactionBlock(): Promise<void> {
|
||||
if (transactionBlockState.nextCursor === null) {
|
||||
return;
|
||||
}
|
||||
await loadTransactionBlock(transactionBlockState.currentBlock + 1, transactionBlockState.nextCursor);
|
||||
}
|
||||
|
||||
async function loadPreviousProgramBlock(): Promise<void> {
|
||||
const targetBlock = programBlockState.currentBlock - 1;
|
||||
const cursor = cursorForBlock(programBlockState, targetBlock);
|
||||
if (targetBlock < 1 || cursor === undefined) {
|
||||
return;
|
||||
}
|
||||
await loadProgramBlock(targetBlock, cursor);
|
||||
}
|
||||
|
||||
async function loadNextProgramBlock(): Promise<void> {
|
||||
if (programBlockState.nextCursor === null) {
|
||||
return;
|
||||
}
|
||||
await loadProgramBlock(programBlockState.currentBlock + 1, programBlockState.nextCursor);
|
||||
}
|
||||
|
||||
async function loadPreviousEntityBlock(config: EntityViewConfig): Promise<void> {
|
||||
const state = entityBlockState(config.kind);
|
||||
const targetBlock = state.currentBlock - 1;
|
||||
const cursor = cursorForBlock(state, targetBlock);
|
||||
if (targetBlock < 1 || cursor === undefined) {
|
||||
return;
|
||||
}
|
||||
await loadEntityBlock(config, targetBlock, cursor);
|
||||
}
|
||||
|
||||
async function loadNextEntityBlock(config: EntityViewConfig): Promise<void> {
|
||||
const state = entityBlockState(config.kind);
|
||||
if (state.nextCursor === null) {
|
||||
return;
|
||||
}
|
||||
await loadEntityBlock(config, state.currentBlock + 1, state.nextCursor);
|
||||
}
|
||||
|
||||
async function copyLines(lines: string[], label: string): Promise<void> {
|
||||
if (lines.length === 0) {
|
||||
throw new Error(`Aucune ${label} à copier.`);
|
||||
@@ -635,7 +806,7 @@ async function useSelectedProgram(): Promise<void> {
|
||||
element<HTMLInputElement>("#transactionProgramIdInput").value = row.programId;
|
||||
element<HTMLSelectElement>("#transactionProgramScopeSelect").value = "any";
|
||||
showTransactionsTab();
|
||||
await loadTransactions();
|
||||
await startTransactionLoad();
|
||||
}
|
||||
|
||||
async function useSelectedEntity(config: EntityViewConfig): Promise<void> {
|
||||
@@ -647,7 +818,7 @@ async function useSelectedEntity(config: EntityViewConfig): Promise<void> {
|
||||
element<HTMLSelectElement>("#transactionEntityKindSelect").value = config.kind;
|
||||
element<HTMLInputElement>("#transactionEntityValueInput").value = row.entityValue;
|
||||
showTransactionsTab();
|
||||
await loadTransactions();
|
||||
await startTransactionLoad();
|
||||
}
|
||||
|
||||
async function openCoreExtraction(): Promise<void> {
|
||||
@@ -661,16 +832,12 @@ async function openCoreExtraction(): Promise<void> {
|
||||
|
||||
async function loadOptions(): Promise<void> {
|
||||
const options = await invoke<DemoStoreReplayOptionsPayload>("demo_store_replay_options");
|
||||
if (!Number.isSafeInteger(options.maximumLimit) || options.maximumLimit <= 0) {
|
||||
throw new Error("La limite maximale des candidats replay retournée par le backend est invalide.");
|
||||
if (!Number.isSafeInteger(options.blockSize) || options.blockSize !== 500) {
|
||||
throw new Error("La taille de bloc Store retournée par le backend est invalide.");
|
||||
}
|
||||
maximumReplayCandidateLimit = options.maximumLimit;
|
||||
element<HTMLElement>("#replayCandidateProfileBadge").textContent = `Profil ${options.activeProfileName}`;
|
||||
element<HTMLElement>("#replayCandidateDsn").textContent = options.connectionDescriptor;
|
||||
element<HTMLElement>("#replayCandidateMaximumLimit").textContent = String(options.maximumLimit);
|
||||
document.querySelectorAll<HTMLInputElement>("input[type=number][max]").forEach(input => {
|
||||
input.max = String(options.maximumLimit);
|
||||
});
|
||||
element<HTMLElement>("#replayCandidateBlockSize").textContent = `${options.blockSize} lignes`;
|
||||
populateKnownPrograms(options.knownPrograms);
|
||||
}
|
||||
|
||||
@@ -729,14 +896,20 @@ function reportUiError(action: string, caughtError: unknown): void {
|
||||
}
|
||||
|
||||
function installHandlers(): void {
|
||||
element<HTMLButtonElement>("#loadTransactionsButton").addEventListener("click", () => void loadTransactions());
|
||||
element<HTMLButtonElement>("#loadTransactionsButton").addEventListener("click", () => void startTransactionLoad());
|
||||
element<HTMLButtonElement>("#resetTransactionsButton").addEventListener("click", () => {
|
||||
void resetTransactionFilters().catch(error => reportUiError("reset transaction filters", error));
|
||||
});
|
||||
element<HTMLButtonElement>("#loadProgramsButton").addEventListener("click", () => void loadPrograms());
|
||||
element<HTMLButtonElement>(transactionBlockControls.firstSelector).addEventListener("click", () => void loadTransactionBlock(1, null));
|
||||
element<HTMLButtonElement>(transactionBlockControls.previousSelector).addEventListener("click", () => void loadPreviousTransactionBlock());
|
||||
element<HTMLButtonElement>(transactionBlockControls.nextSelector).addEventListener("click", () => void loadNextTransactionBlock());
|
||||
element<HTMLButtonElement>("#loadProgramsButton").addEventListener("click", () => void startProgramLoad());
|
||||
element<HTMLButtonElement>("#resetProgramsButton").addEventListener("click", () => {
|
||||
void resetProgramFilters().catch(error => reportUiError("reset program filters", error));
|
||||
});
|
||||
element<HTMLButtonElement>(programBlockControls.firstSelector).addEventListener("click", () => void loadProgramBlock(1, null));
|
||||
element<HTMLButtonElement>(programBlockControls.previousSelector).addEventListener("click", () => void loadPreviousProgramBlock());
|
||||
element<HTMLButtonElement>(programBlockControls.nextSelector).addEventListener("click", () => void loadNextProgramBlock());
|
||||
element<HTMLButtonElement>("#openCoreExtractionButton").addEventListener("click", () => void openCoreExtraction());
|
||||
element<HTMLButtonElement>("#copySignaturesButton").addEventListener("click", () => {
|
||||
void copyLines(transactionRowsForExport().map(row => row.signature), "signature").catch(error => reportUiError("copy signatures", error));
|
||||
@@ -754,10 +927,13 @@ function installHandlers(): void {
|
||||
void useSelectedProgram().catch(error => reportUiError("use selected program", error));
|
||||
});
|
||||
for (const config of entityViewConfigs) {
|
||||
element<HTMLButtonElement>(config.loadButtonSelector).addEventListener("click", () => void loadEntity(config));
|
||||
element<HTMLButtonElement>(config.loadButtonSelector).addEventListener("click", () => void startEntityLoad(config));
|
||||
element<HTMLButtonElement>(config.resetButtonSelector).addEventListener("click", () => {
|
||||
void resetEntityFilters(config).catch(error => reportUiError(`reset ${config.pluralLabel} filters`, error));
|
||||
});
|
||||
element<HTMLButtonElement>(config.blockControls.firstSelector).addEventListener("click", () => void loadEntityBlock(config, 1, null));
|
||||
element<HTMLButtonElement>(config.blockControls.previousSelector).addEventListener("click", () => void loadPreviousEntityBlock(config));
|
||||
element<HTMLButtonElement>(config.blockControls.nextSelector).addEventListener("click", () => void loadNextEntityBlock(config));
|
||||
element<HTMLButtonElement>(config.copyButtonSelector).addEventListener("click", () => {
|
||||
void copyLines(entityRowsForExport(config.kind).map(row => row.entityValue), config.label).catch(error => reportUiError(`copy ${config.pluralLabel}`, error));
|
||||
});
|
||||
@@ -767,16 +943,49 @@ function installHandlers(): void {
|
||||
element<HTMLButtonElement>(config.useButtonSelector).addEventListener("click", () => {
|
||||
void useSelectedEntity(config).catch(error => reportUiError(`use selected ${config.label}`, error));
|
||||
});
|
||||
element<HTMLInputElement>(config.containsSelector).addEventListener("input", () => {
|
||||
entityBaseRequests.delete(config.kind);
|
||||
markStoreBlockFiltersDirty(entityBlockState(config.kind), config.blockControls);
|
||||
});
|
||||
}
|
||||
const transactionFilterSelectors = [
|
||||
"#transactionSignatureContainsInput",
|
||||
"#transactionMinSlotInput",
|
||||
"#transactionMaxSlotInput",
|
||||
"#transactionRawStateSelect",
|
||||
"#transactionLedgerStatusSelect",
|
||||
"#transactionProgramIdInput",
|
||||
"#transactionProgramScopeSelect",
|
||||
"#transactionEntityKindSelect",
|
||||
"#transactionEntityValueInput",
|
||||
"#transactionNewestFirstInput",
|
||||
];
|
||||
for (const selector of transactionFilterSelectors) {
|
||||
element<HTMLInputElement | HTMLSelectElement>(selector).addEventListener("change", () => {
|
||||
transactionBaseRequest = null;
|
||||
markStoreBlockFiltersDirty(transactionBlockState, transactionBlockControls);
|
||||
});
|
||||
element<HTMLInputElement | HTMLSelectElement>(selector).addEventListener("input", () => {
|
||||
transactionBaseRequest = null;
|
||||
markStoreBlockFiltersDirty(transactionBlockState, transactionBlockControls);
|
||||
});
|
||||
}
|
||||
element<HTMLInputElement>("#programContainsInput").addEventListener("input", () => {
|
||||
programBaseRequest = null;
|
||||
markStoreBlockFiltersDirty(programBlockState, programBlockControls);
|
||||
});
|
||||
element<HTMLSelectElement>("#knownProgramSelect").addEventListener("change", event => {
|
||||
const target = event.currentTarget as HTMLSelectElement;
|
||||
element<HTMLInputElement>("#programContainsInput").value = target.value;
|
||||
programBaseRequest = null;
|
||||
markStoreBlockFiltersDirty(programBlockState, programBlockControls);
|
||||
});
|
||||
document.querySelectorAll<HTMLButtonElement>('[data-bs-toggle="tab"]').forEach(button => {
|
||||
button.addEventListener("shown.bs.tab", adjustVisibleTables);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
installFrontendConsoleBridge(tracingTarget);
|
||||
frontendDebug(tracingTarget, "Store replay candidate browser loaded");
|
||||
@@ -790,11 +999,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
installInlineCopyHandler();
|
||||
void loadOptions()
|
||||
.then(() => Promise.all([
|
||||
loadTransactions(),
|
||||
loadPrograms(),
|
||||
loadEntity(entityViewConfigs[0]),
|
||||
loadEntity(entityViewConfigs[1]),
|
||||
loadEntity(entityViewConfigs[2]),
|
||||
startTransactionLoad(),
|
||||
startProgramLoad(),
|
||||
startEntityLoad(entityViewConfigs[0]),
|
||||
startEntityLoad(entityViewConfigs[1]),
|
||||
startEntityLoad(entityViewConfigs[2]),
|
||||
]))
|
||||
.catch(caughtError => reportUiError("initial loading", caughtError));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user