tease 0.5.0

Rapidly prototype interfaces for your machine learning models
Documentation
"use strict";
(() => {
    function parseCSV(text) {
        text = text.replace(/^\uFEFF/, "");
        const rows = []; let row = [], cell = "", quoted = false, closed = false;
        function field() { row.push(cell); cell = ""; closed = false; }
        function line() { field(); if (row.length > 1 || row[0] !== "") rows.push(row); row = []; }
        for (let i = 0; i < text.length; i++) {
            const c = text[i];
            if (quoted) {
                if (c === '"' && text[i+1] === '"') { cell += '"'; i++; }
                else if (c === '"') { quoted = false; closed = true; }
                else cell += c;
            } else if (c === ',' ) field();
            else if (c === '\n' || c === '\r') { line(); if (c === '\r' && text[i+1] === '\n') i++; }
            else if (c === '"' && !cell && !closed) quoted = true;
            else { if (closed || c === '"') throw new Error('Invalid CSV quoting.'); cell += c; }
            if (rows.length > 1001 || row.length > 64 || cell.length > 8192) throw new Error('CSV exceeds its row, column, or cell limits.');
        }
        if (quoted) throw new Error('CSV has an unclosed quoted field.');
        if (cell || row.length || closed) line();
        const columns = rows.shift() || [];
        if (!columns.length || columns.length > 64 || columns.some(c => !c.trim()) || new Set(columns).size !== columns.length || rows.some(r => r.length !== columns.length) || rows.length > 1000) throw new Error('CSV needs unique headers and rows of matching width (up to 1,000 rows).');
        return {columns, rows};
    }
    function apply(input, raw, preview, name = 'Preset file', invalidate = true) {
        if (invalidate) input._ticket = (input._ticket || 0) + 1; input._reading = false;
        input._payload = ''; input._fileName = name;
        preview.replaceChildren();
        if (!raw) return;
        let data;
        if (input.dataset.fileKind === 'image') {
            const image = JSON.parse(raw);
            if (!['image/png','image/jpeg'].includes(image.mime)) throw new Error('Select a PNG or JPEG image.');
            data = {kind:'image',image};
        } else { data = {kind:'table',table:parseCSV(raw)}; }
        window.teaseView.render(preview, data);
        window.teaseView.node('p', name, preview).className = 'help';
        input._payload = raw;
    }
    function read(file, kind) {
        if (!file) return Promise.resolve('');
        if (file.size > (kind === 'image' ? 8 : 2) * 1024 * 1024) return Promise.reject(new Error('File exceeds the size limit.'));
        return new Promise((resolve,reject) => {
            const reader = new FileReader();
            reader.onerror = () => reject(new Error('Could not read the selected file.'));
            reader.onload = () => {
                try {
                    if (kind === 'table') resolve(String(reader.result));
                    else {
                        const base64 = String(reader.result).split(',')[1] || '';
                        const mime = base64.startsWith('iVBORw0KGgo') ? 'image/png' : base64.startsWith('/9j/') ? 'image/jpeg' : '';
                        if (!mime) throw new Error('Select a PNG or JPEG image.');
                        resolve(JSON.stringify({mime,base64}));
                    }
                } catch (error) { reject(error); }
            };
            if (kind === 'image') reader.readAsDataURL(file); else reader.readAsText(file,'UTF-8');
        });
    }
    window.teaseFiles = {apply,read,parseCSV};
})();