Task 80% complete

This commit is contained in:
2025-10-20 15:09:24 +02:00
parent b23ef80a5f
commit 6dedd2f75e
5 changed files with 213 additions and 13 deletions

View File

@@ -59,11 +59,11 @@
<p id="column-selected"></p> <p id="column-selected"></p>
<h4>Data type</h4> <h4>Data type</h4>
<p id="column-datatype"></p> <p id="column-datatype"></p>
<h4>Different entries</h4> <h4 id="title-column-entries">Different entries</h4>
<p id="column-entries"></p> <p id="column-entries"></p>
<h4>Min</h4> <h4 id="title-column-min">Min</h4>
<p id="column-min"></p> <p id="column-min"></p>
<h4>Max</h4> <h4 id="title-column-max">Max</h4>
<p id="column-max"></p> <p id="column-max"></p>
</div> </div>
</article> </article>
@@ -72,8 +72,8 @@
<header> <header>
<h2>Filter active column</h2> <h2>Filter active column</h2>
</header> </header>
<label for="filter">Filter column:</label><input type="search" id="filter" name="filter" <label for="filter">Filter column:</label>
placeholder="Filter" disabled> <input type="search" id="filter" name="filter" placeholder="Filter" disabled>
</article> </article>
<article style="width: 100%"> <article style="width: 100%">
@@ -83,7 +83,6 @@
<table id="table-content"> <table id="table-content">
<thead id="table-header"></thead> <thead id="table-header"></thead>
<tbody id="table-body"> <tbody id="table-body">
</tbody> </tbody>
</table> </table>
</article> </article>

View File

@@ -1,6 +1,11 @@
import '@fortawesome/fontawesome-free/css/all.css'; import '@fortawesome/fontawesome-free/css/all.css';
import '../css/layout.css'; import '../css/layout.css';
import '@picocss/pico/css/pico.min.css'; import '@picocss/pico/css/pico.min.css';
import {
computeDifferent,
computeMinMax,
numberCheckPredicate, stringOrNumberCheckPredicate
} from './util';
import { import {
listRef, ref listRef, ref
} from './rendering'; } from './rendering';
@@ -11,6 +16,11 @@ import {
readCSV readCSV
} from './csv'; } from './csv';
// ┌ ┐
// │ Define refs │
// └ ┘
const tableHeaderElement = document.getElementById( 'table-header' )!;
const dataList = listRef<CSVRecord>( const dataList = listRef<CSVRecord>(
document.getElementById( 'table-body' )!, document.getElementById( 'table-body' )!,
[], [],
@@ -22,7 +32,7 @@ const dataList = listRef<CSVRecord>(
} }
); );
const headerList = listRef<string>( const headerList = listRef<string>(
document.getElementById( 'table-header' )!, tableHeaderElement,
[], [],
'table-header', 'table-header',
{ {
@@ -31,31 +41,56 @@ const headerList = listRef<string>(
'children': [] 'children': []
} }
); );
const filter = ref<string>( [ document.getElementById( 'filter' )! ], '' ); const columnEntriesElement = document.getElementById( 'column-entries' )!;
const columnMaxElement = document.getElementById( 'column-max' )!;
const columnMinElement = document.getElementById( 'column-min' )!;
const filter = ref<string>( [], '' );
const filterInput = document.getElementById( 'filter' )! as HTMLInputElement;
const filename = ref<string>( [ document.getElementById( 'data-filename' )! ], '' ); const filename = ref<string>( [ document.getElementById( 'data-filename' )! ], '' );
const filetype = ref<string>( [ document.getElementById( 'data-filetype' )! ], '' ); const filetype = ref<string>( [ document.getElementById( 'data-filetype' )! ], '' );
const filesize = ref<string>( [ document.getElementById( 'data-filesize' )! ], '' ); const filesize = ref<string>( [ document.getElementById( 'data-filesize' )! ], '' );
const rowCount = ref<string>( [ document.getElementById( 'data-rowcount' )! ], '' ); const rowCount = ref<string>( [ document.getElementById( 'data-rowcount' )! ], '' );
const columnName = ref<string>( [ document.getElementById( 'column-selected' )! ], '' ); const columnName = ref<string>( [ document.getElementById( 'column-selected' )! ], '' );
const columnDatatype = ref<string>( [ document.getElementById( 'column-datatype' )! ], '' ); const columnDatatype = ref<string>( [ document.getElementById( 'column-datatype' )! ], '' );
const columnEntries = ref<string>( [ document.getElementById( 'column-entries' )! ], '' ); const columnEntries = ref<number>( [ columnEntriesElement ], 0 );
const columnMax = ref<string>( [ document.getElementById( 'column-max' )! ], '' ); const columnMax = ref<number>( [ columnMaxElement ], 0 );
const columnMin = ref<string>( [ document.getElementById( 'column-min' )! ], '' ); const columnMin = ref<number>( [ columnMinElement ], 0 );
const fileInput = document.getElementById( 'file-input' )! as HTMLInputElement; const fileInput = document.getElementById( 'file-input' )! as HTMLInputElement;
const ascendingSort = ref<boolean>( [], true );
let selectedColumn = '';
filterInput.disabled = true;
filterInput.value = '';
// Bind to file input event // ┌ ┐
// │ 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 => { fileInput.addEventListener( 'change', event => {
loadFile( event ); loadFile( event );
} ); } );
const loadFile = ( event: Event ) => { const loadFile = ( event: Event ) => {
if ( fileInput.files && fileInput.files.length > 0 ) { if ( fileInput.files && fileInput.files.length > 0 ) {
const file = fileInput.files[0]!; const file = fileInput.files[0]!;
filename.set( file.name ); filename.set( file.name );
filetype.set( file.type ); filetype.set( file.type );
filesize.set( String( file.size ) + 'B' ); // TODO: KB / MB conversion stuff filesize.set( String( file.size ) + 'B' ); // TODO: KB / MB conversion stuff?
readCSV( event ) readCSV( event )
.then( data => { .then( data => {
// Row count // Row count
@@ -66,6 +101,33 @@ const loadFile = ( event: Event ) => {
headerList.set( header ); 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 ─── // ── Generate list. Need to first generate the correct template ───
// Reset, to not trigger expensive rerender // Reset, to not trigger expensive rerender
dataList.set( [] ); dataList.set( [] );
@@ -92,3 +154,61 @@ const loadFile = ( event: Event ) => {
alert( 'No file selected' ); 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 );
}
} );

View File

@@ -0,0 +1,24 @@
import {
PersistanceConfig
} from './types';
// Using localStorage for persistance
const persistanceStore: PersistanceConfig = JSON.parse( localStorage.getItem( 'persistance' ) ?? '{}' );
export const store = (
filename: string,
size: number,
sorted: string,
active: string
) => {
persistanceStore[ `${ filename }-${ size }` ] = {
'active': active,
'sorted': sorted
};
localStorage.setItem( 'persistance', JSON.stringify( persistanceStore ) );
};
export const get = ( filename: string, size: number ) => {
return persistanceStore[ `${ filename }-${ size }` ];
};

View File

@@ -2,3 +2,12 @@
export type CSVRecord = Record<string, unknown>; export type CSVRecord = Record<string, unknown>;
export type CSV_Data = CSVRecord[]; export type CSV_Data = CSVRecord[];
export interface PersistanceConfigEntry {
'sorted': string;
'active': string;
}
export interface PersistanceConfig {
[filename: `${ string }-${ number }`]: PersistanceConfigEntry
}

48
task_2_ts/ts/util.ts Normal file
View File

@@ -0,0 +1,48 @@
import {
CSVRecord
} from './types';
export const numberCheckPredicate = ( value: string ) => {
return value === 'number';
};
export const stringOrNumberCheckPredicate = ( value: string ) => {
return value === 'string' || value === 'number';
};
export const computeMinMax = ( list: CSVRecord[], selectedColumn: string ): [number, number, number] => {
const containsList: number[] = [];
let min = Number.MAX_VALUE;
let max = Number.MIN_VALUE;
for ( let i = 0; i < list.length; i++ ) {
const el = list[i]!;
const curr = el[ selectedColumn ]! as number;
if ( curr < min ) min = curr;
else if ( curr > max ) max = curr;
if ( !containsList.includes( curr ) ) containsList.push( curr );
}
return [
min,
max,
containsList.length
];
};
export const computeDifferent = ( list: CSVRecord[], selectedColumn: string ): number => {
const containsList: string[] = [];
for ( let i = 0; i < list.length; i++ ) {
const el = list[i]!;
const curr = el[ selectedColumn ]! as string;
if ( !containsList.includes( curr ) ) containsList.push( curr );
}
return containsList.length;
};