@@ -81,6 +81,7 @@ import {
8181 AACPage ,
8282 AACButton
8383} from 'aac-processors' ;
84+ import { validateFileOrBuffer , type ValidationResult } from 'aac-processors/validation' ;
8485
8586import sqlWasmUrl from 'sql.js/dist/sql-wasm.wasm?url' ;
8687
@@ -92,6 +93,7 @@ configureSqlJs({
9293const dropArea = document . getElementById ( 'dropArea' ) as HTMLElement ;
9394const fileInput = document . getElementById ( 'fileInput' ) as HTMLInputElement ;
9495const processBtn = document . getElementById ( 'processBtn' ) as HTMLButtonElement ;
96+ const validateBtn = document . getElementById ( 'validateBtn' ) as HTMLButtonElement ;
9597const runTestsBtn = document . getElementById ( 'runTestsBtn' ) as HTMLButtonElement ;
9698const clearBtn = document . getElementById ( 'clearBtn' ) as HTMLButtonElement ;
9799const fileInfo = document . getElementById ( 'fileInfo' ) as HTMLElement ;
@@ -102,6 +104,9 @@ const results = document.getElementById('results') as HTMLElement;
102104const logPanel = document . getElementById ( 'logPanel' ) as HTMLElement ;
103105const testResults = document . getElementById ( 'testResults' ) as HTMLElement ;
104106const testList = document . getElementById ( 'testList' ) as HTMLElement ;
107+ const validationPanel = document . getElementById ( 'validationPanel' ) as HTMLElement ;
108+ const validationSummary = document . getElementById ( 'validationSummary' ) as HTMLElement ;
109+ const validationList = document . getElementById ( 'validationList' ) as HTMLElement ;
105110const tabButtons = document . querySelectorAll ( '.tab-btn' ) as NodeListOf < HTMLButtonElement > ;
106111const inspectTab = document . getElementById ( 'inspectTab' ) as HTMLElement ;
107112const pagesetTab = document . getElementById ( 'pagesetTab' ) as HTMLElement ;
@@ -218,6 +223,7 @@ function handleFile(file: File) {
218223 fileDetails . textContent = extension ;
219224 fileInfo . style . display = 'block' ;
220225 processBtn . disabled = true ;
226+ validateBtn . disabled = true ;
221227 return ;
222228 }
223229
@@ -228,12 +234,14 @@ function handleFile(file: File) {
228234 fileDetails . textContent = `${ file . name } • ${ formatFileSize ( file . size ) } ` ;
229235 fileInfo . style . display = 'block' ;
230236 processBtn . disabled = false ;
237+ validateBtn . disabled = false ;
231238 currentSourceLabel = file . name ;
232239
233240 log ( `Using processor: ${ currentProcessor . constructor . name } ` , 'success' ) ;
234241 } catch ( error ) {
235242 log ( `Error getting processor: ${ ( error as Error ) . message } ` , 'error' ) ;
236243 processBtn . disabled = true ;
244+ validateBtn . disabled = true ;
237245 }
238246}
239247
@@ -314,6 +322,79 @@ processBtn.addEventListener('click', async () => {
314322 }
315323} ) ;
316324
325+ function collectValidationMessages (
326+ result : ValidationResult ,
327+ prefix = ''
328+ ) : Array < { type : 'error' | 'warn' ; message : string } > {
329+ const messages : Array < { type : 'error' | 'warn' ; message : string } > = [ ] ;
330+ const label = prefix ? `${ prefix } : ` : '' ;
331+ result . results . forEach ( ( check ) => {
332+ if ( ! check . valid && check . error ) {
333+ messages . push ( { type : 'error' , message : `${ label } ${ check . description } : ${ check . error } ` } ) ;
334+ }
335+ if ( check . warnings ?. length ) {
336+ check . warnings . forEach ( ( warning ) => {
337+ messages . push ( { type : 'warn' , message : `${ label } ${ check . description } : ${ warning } ` } ) ;
338+ } ) ;
339+ }
340+ } ) ;
341+ result . sub_results ?. forEach ( ( sub ) => {
342+ const nextPrefix = `${ label } ${ sub . filename || sub . format } ` ;
343+ messages . push ( ...collectValidationMessages ( sub , nextPrefix ) ) ;
344+ } ) ;
345+ return messages ;
346+ }
347+
348+ function renderValidationResult ( result : ValidationResult ) {
349+ validationPanel . style . display = 'block' ;
350+ validationSummary . classList . remove ( 'success' , 'error' ) ;
351+ validationSummary . classList . add ( result . valid ? 'success' : 'error' ) ;
352+ validationSummary . textContent = `${ result . valid ? '✅ Valid' : '❌ Invalid' } • ${ result . format . toUpperCase ( ) } • ${ result . errors } errors, ${ result . warnings } warnings` ;
353+
354+ validationList . innerHTML = '' ;
355+ const messages = collectValidationMessages ( result ) . slice ( 0 , 30 ) ;
356+ if ( messages . length === 0 ) {
357+ const empty = document . createElement ( 'div' ) ;
358+ empty . className = 'validation-item' ;
359+ empty . textContent = 'No issues reported.' ;
360+ validationList . appendChild ( empty ) ;
361+ return ;
362+ }
363+
364+ messages . forEach ( ( entry ) => {
365+ const item = document . createElement ( 'div' ) ;
366+ item . className = `validation-item ${ entry . type } ` ;
367+ item . textContent = entry . message ;
368+ validationList . appendChild ( item ) ;
369+ } ) ;
370+ }
371+
372+ validateBtn . addEventListener ( 'click' , async ( ) => {
373+ if ( ! currentFile ) return ;
374+ log ( 'Validating file...' , 'info' ) ;
375+
376+ try {
377+ validateBtn . disabled = true ;
378+ const arrayBuffer = await currentFile . arrayBuffer ( ) ;
379+ const result = await validateFileOrBuffer ( new Uint8Array ( arrayBuffer ) , currentFile . name ) ;
380+ renderValidationResult ( result ) ;
381+ log (
382+ `${ result . valid ? '✅' : '❌' } Validation complete: ${ result . errors } errors, ${ result . warnings } warnings` ,
383+ result . valid ? 'success' : 'warn'
384+ ) ;
385+ } catch ( error ) {
386+ const errorMsg = ( error as Error ) . message ;
387+ validationPanel . style . display = 'block' ;
388+ validationSummary . classList . remove ( 'success' ) ;
389+ validationSummary . classList . add ( 'error' ) ;
390+ validationSummary . textContent = `❌ Validation failed: ${ errorMsg } ` ;
391+ validationList . innerHTML = '' ;
392+ log ( `❌ Validation failed: ${ errorMsg } ` , 'error' ) ;
393+ } finally {
394+ validateBtn . disabled = ! currentFile ;
395+ }
396+ } ) ;
397+
317398// Display results
318399function displayResults ( tree : AACTree ) {
319400 results . innerHTML = '' ;
@@ -423,6 +504,9 @@ clearBtn.addEventListener('click', () => {
423504 stats . style . display = 'none' ;
424505 results . innerHTML = '<p style="color: #999; text-align: center; padding: 40px;">Load a file to see its contents here</p>' ;
425506 testResults . style . display = 'none' ;
507+ validationPanel . style . display = 'none' ;
508+ validationSummary . textContent = '' ;
509+ validationList . innerHTML = '' ;
426510 logPanel . innerHTML = '<div class="log-entry log-info">Cleared. Ready to process files...</div>' ;
427511 pagesetOutput . textContent = 'Generate or convert a pageset to preview the output JSON.' ;
428512 updateConvertButtons ( ) ;
0 commit comments