From dc7cc767859a82a087e258eead7d59e177b30d0f Mon Sep 17 00:00:00 2001 From: bozokopic Date: Fri, 13 Apr 2018 19:07:18 +0200 Subject: WIP web frontend --- src_js/opcut/fs.js | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ src_js/opcut/grid.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++-- src_js/opcut/vt.js | 4 +-- src_web/style/main.scss | 16 ++++++----- 4 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 src_js/opcut/fs.js diff --git a/src_js/opcut/fs.js b/src_js/opcut/fs.js new file mode 100644 index 0000000..b1d464e --- /dev/null +++ b/src_js/opcut/fs.js @@ -0,0 +1,71 @@ +import h from 'hyperscript'; +import FileSaver from 'file-saver'; + +import * as ev from 'opcut/ev'; + + +export function loadText(ext) { + const el = h('input', { + style: 'display: none', + type: 'file', + accept: ext}); + const promise = new Promise(resolve => { + ev.on(el, 'change', evt => { + const file = u.get(['files', 0], evt.target); + if (!file) + return; + const fileReader = new FileReader(); + fileReader.onload = () => { + const data = fileReader.result; + resolve(data); + }; + fileReader.readAsText(file); + }); + el.click(); + }); + return promise; +} + + +export function saveText(text, fileName) { + const blob = stringToBlob(text); + FileSaver.saveAs(blob, fileName); +} + + +export function saveB64Data(b64Data, fileName) { + const blob = b64ToBlob(b64Data); + FileSaver.saveAs(blob, fileName); +} + + +function stringToBlob(strData, contentType) { + contentType = contentType || ''; + return new Blob([strData], {type: contentType}); +} + + +// http://stackoverflow.com/a/16245768 +function b64ToBlob(b64Data, contentType, sliceSize) { + contentType = contentType || ''; + sliceSize = sliceSize || 512; + + var byteCharacters = atob(b64Data); + var byteArrays = []; + + for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { + var slice = byteCharacters.slice(offset, offset + sliceSize); + + var byteNumbers = new Array(slice.length); + for (var i = 0; i < slice.length; i++) { + byteNumbers[i] = slice.charCodeAt(i); + } + + var byteArray = new Uint8Array(byteNumbers); + + byteArrays.push(byteArray); + } + + var blob = new Blob(byteArrays, {type: contentType}); + return blob; +} diff --git a/src_js/opcut/grid.js b/src_js/opcut/grid.js index b72e0b0..972341d 100644 --- a/src_js/opcut/grid.js +++ b/src_js/opcut/grid.js @@ -1,3 +1,5 @@ +import Papa from 'papaparse'; + import r from 'opcut/renderer'; import * as u from 'opcut/util'; import * as ev from 'opcut/ev'; @@ -68,7 +70,7 @@ export function tbody(gridPath, columns, validators) { } -export function tfoot(gridPath, colspan, newItem) { +export function tfoot(gridPath, colspan, newItem, csvColumns) { const itemsPath = [gridPath, 'items']; return ['tfoot', ['tr', @@ -85,7 +87,27 @@ export function tfoot(gridPath, colspan, newItem) { 'ev-click': () => r.set(itemsPath, [])}, ['span.fa.fa-trash-o'], ' Remove all' - ] + ], + (!csvColumns ? + [] : + [ + ['button', { + 'ev-click': () => { + const items = importCsv(csvColumns, newItem); + if (!items) + return; + r.change(itemsPath, state => state.concat(items)); + }}, + ['span.fa.fa-download'], + ' Import from CSV' + ], + ['button', { + 'ev-click': () => exportCsv(r.get(itemsPath), csvColumns)}, + ['span.fa.fa-upload'], + ' Export to CSV' + ] + ] + ) ] ] ] @@ -93,6 +115,17 @@ export function tfoot(gridPath, colspan, newItem) { } +export function createStringCsvColumns(...columns) { + return u.pipe( + u.map(i => [i, { + toString: u.get(i), + toItem: u.set(i) + }]), + u.fromPairs + )(columns); +} + + export function deleteColumn(gridPath, showUpDown, onDeleteCb) { const itemsPath = [gridPath, 'items']; return (i, index) => [ @@ -173,3 +206,36 @@ export function selectColumn(gridPath, column, values) { ]; }; } + + +function importCsv(csvColumns, newItem) { + fs.loadText('csv').then(csvData => { + const result = Papa.parse(csvData, { + delimiter: ';', + skipEmptyLines: true, + header: true + }); + + const items = []; + for (let i of result.data) { + if (!Object.keys(i).every(k => u.contains(k, Object.keys(csvColumns)))) + continue; + const item = u.reduce( + (acc, [name, column]) => column.toItem(i[name], acc), + newItem, + u.toPairs(csvColumns)); + items.push(item); + } + return items; + }); +} + + +function exportCsv(items, csvColumns) { + const csvData = Papa.unparse( + items.map(item => u.map(column => column.toString(item))(csvColumns)), { + delimiter: ';', + skipEmptyLines: true, + header: true}); + fs.saveText(csvData, 'data.csv'); +} diff --git a/src_js/opcut/vt.js b/src_js/opcut/vt.js index 39f6188..c13d35a 100644 --- a/src_js/opcut/vt.js +++ b/src_js/opcut/vt.js @@ -23,7 +23,7 @@ function leftPanel() { ['span.fa.fa-github'] ] ], - ['div', + ['div.group', ['label', 'Method'], ['select', ['FORWARD_GREEDY', 'GREEDY'].map(method => @@ -36,7 +36,7 @@ function leftPanel() { ]) ] ], - ['div', + ['div.group', ['label', 'Cut width'], ['input', { props: { diff --git a/src_web/style/main.scss b/src_web/style/main.scss index 21a6c79..6b848b4 100644 --- a/src_web/style/main.scss +++ b/src_web/style/main.scss @@ -25,28 +25,30 @@ html, body { flex-direction: column; background-color: $color-grey-100; - & > * { + .group { + flex-shrink: 0; display: flex; flex-direction: column; margin: 10px; margin-bottom: 0px; - flex-shrink: 0; } .header { - margin-bottom: 10px; - color: $color-grey-800; - flex-direction: row; + flex-shrink: 0; + margin: 10px; + display: flex; align-items: center; .title { flex-grow: 1; font-size: 18pt; font-weight: 500; + color: $color-grey-800; } a { font-size: 24pt; + color: $color-grey-800; &:visited { color: $color-grey-800; @@ -62,7 +64,8 @@ html, body { flex-grow: 1; display: flex; flex-direction: column; - flex-shrink: 1; + margin: 10px; + margin-bottom: 0px; & > * { flex-shrink: 0; @@ -80,6 +83,7 @@ html, body { } .submit { + flex-shrink: 0; margin: 10px; padding: 10px; } -- cgit v1.2.3-70-g09d2