v0.5.3-pre.005-fix010
This commit is contained in:
@@ -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