import '@fortawesome/fontawesome-free/css/all.css'; import '../css/layout.css'; import '@picocss/pico/css/pico.min.css'; import { computeDifferent, computeMinMax, numberCheckPredicate, stringOrNumberCheckPredicate } from './util'; import { listRef, ref } from './rendering'; import { CSVRecord } from './types'; import { readCSV } from './csv'; // ┌ ┐ // │ Define refs │ // └ ┘ const tableHeaderElement = document.getElementById( 'table-header' )!; const dataList = listRef( document.getElementById( 'table-body' )!, [], 'table-body', { 'type': 'tr', 'cssClasses': [], 'children': [] } ); const headerList = listRef( tableHeaderElement, [], 'table-header', { 'type': 'td', 'cssClasses': [], 'children': [] } ); const columnEntriesElement = document.getElementById( 'column-entries' )!; const columnMaxElement = document.getElementById( 'column-max' )!; const columnMinElement = document.getElementById( 'column-min' )!; const filter = ref( [], '' ); const filterInput = document.getElementById( 'filter' )! as HTMLInputElement; const filename = ref( [ document.getElementById( 'data-filename' )! ], '' ); const filetype = ref( [ document.getElementById( 'data-filetype' )! ], '' ); const filesize = ref( [ document.getElementById( 'data-filesize' )! ], '' ); const rowCount = ref( [ document.getElementById( 'data-rowcount' )! ], '' ); const columnName = ref( [ document.getElementById( 'column-selected' )! ], '' ); const columnDatatype = ref( [ document.getElementById( 'column-datatype' )! ], '' ); const columnEntries = ref( [ columnEntriesElement ], 0 ); const columnMax = ref( [ columnMaxElement ], 0 ); const columnMin = ref( [ columnMinElement ], 0 ); const fileInput = document.getElementById( 'file-input' )! as HTMLInputElement; const ascendingSort = ref( [], true ); let selectedColumn = ''; filterInput.disabled = true; filterInput.value = ''; // ┌ ┐ // │ conditional rendering of some elements │ // └ ┘ columnDatatype.addConditionalElementBind( columnMinElement, numberCheckPredicate ); columnDatatype.addConditionalElementBind( columnMaxElement, numberCheckPredicate ); columnDatatype.addConditionalElementBind( document.getElementById( 'title-column-min' )!, numberCheckPredicate ); columnDatatype.addConditionalElementBind( document.getElementById( 'title-column-max' )!, numberCheckPredicate ); columnDatatype.addConditionalElementBind( columnEntriesElement, stringOrNumberCheckPredicate ); columnDatatype.addConditionalElementBind( document.getElementById( 'title-column-entries' )!, stringOrNumberCheckPredicate ); // ┌ ┐ // │ Bind to file input event │ // └ ┘ fileInput.addEventListener( 'change', event => { loadFile( event ); } ); const loadFile = ( event: Event ) => { if ( fileInput.files && fileInput.files.length > 0 ) { const file = fileInput.files[0]!; filename.set( file.name ); filetype.set( file.type ); filesize.set( String( file.size ) + 'B' ); // TODO: KB / MB conversion stuff? readCSV( event ) .then( data => { // Row count rowCount.set( String( data.length ) ); // Header will be the keyset of any row const header = Object.keys( data[0]! ); headerList.set( header ); // Initialize sorting for ( let i = 0; i < header.length; i++ ) { const column = header[ i ]!; document.getElementById( 'table-header--' + i )!.addEventListener( 'click', () => { // TODO: Decide on sorting cycling // TODO: Add indicator as well if ( selectedColumn === column ) { ascendingSort.set( !ascendingSort.get() ); } else { // This column will now be the active column selectedColumn = column; ascendingSort.set( true ); const dtype = typeof dataList.get()[0]![ column ]; columnDatatype.set( dtype ); columnName.set( column ); if ( dtype === 'string' ) filterInput.disabled = false; else filterInput.disabled = true; } } ); } // ── Generate list. Need to first generate the correct template ─── // Reset, to not trigger expensive rerender dataList.set( [] ); dataList.setTemplate( { 'type': 'tr', 'cssClasses': [], 'children': header.map( val => { return { 'type': 'td', 'cssClasses': [], 'children': [], 'attribute': val }; } ) } ); dataList.set( data ); } ) .catch( e => { console.warn( e ); alert( 'Failed to read CSV' ); } ); } else { alert( 'No file selected' ); } }; // ┌ ┐ // │ Sorting │ // └ ┘ const doSort = () => { filter.set( '' ); if ( columnDatatype.get() === 'string' ) { columnEntries.set( computeDifferent( dataList.get(), selectedColumn ) ); if ( ascendingSort.get() ) { dataList.sort( ( a, b ) => { return ( a[ selectedColumn ] as string ).localeCompare( b[ selectedColumn ] as string ); } ); } else { dataList.sort( ( a, b ) => { return ( b[ selectedColumn ] as string ).localeCompare( a[ selectedColumn ] as string ); } ); } } else if ( columnDatatype.get() === 'number' ) { const stats = computeMinMax( dataList.get(), selectedColumn ); columnMin.set( stats[ 0 ] ); columnMax.set( stats[ 1 ] ); columnEntries.set( stats[ 2 ] ); if ( ascendingSort.get() ) { dataList.sort( ( a, b ) => { return ( a[ selectedColumn ] as number ) - ( b[ selectedColumn ] as number ); } ); } else { dataList.sort( ( a, b ) => { return ( b[ selectedColumn ] as number ) - ( a[ selectedColumn ] as number ); } ); } } }; columnName.onChange( doSort ); ascendingSort.onChange( doSort ); // ┌ ┐ // │ Filtering │ // └ ┘ // Bind filter ref to element filter.bind( filterInput, val => val ); // Add listener to change of filter value. filter.onChange( () => { if ( columnDatatype.get() === 'string' ) { dataList.filter( a => { return ( a[ selectedColumn ] as string ).includes( filter.get() ); } ); } else { dataList.filter( () => true ); } } );