diff options
Diffstat (limited to 'src_js/vt.js')
| -rw-r--r-- | src_js/vt.js | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/src_js/vt.js b/src_js/vt.js new file mode 100644 index 0000000..1dc467e --- /dev/null +++ b/src_js/vt.js @@ -0,0 +1,469 @@ +import r from '@hat-open/renderer'; +import * as u from '@hat-open/util'; + +import * as common from './common'; +import * as dragger from './dragger'; +import * as states from './states'; + + +export function main() { + return ['div.main', + leftPanel(), + leftPanelResizer(), + centerPanel(), + rightPanelResizer(), + rightPanel() + ]; +} + + +function leftPanelResizer() { + return ['div.panel-resizer', { + on: { + mousedown: dragger.mouseDownHandler(evt => { + const panel = evt.target.parentNode.querySelector('.left-panel'); + const width = panel.clientWidth; + return (_, dx, __) => { + panel.style.width = `${width + dx}px`; + }; + }) + } + }]; +} + + +function rightPanelResizer() { + return ['div.panel-resizer', { + on: { + mousedown: dragger.mouseDownHandler(evt => { + const panel = evt.target.parentNode.querySelector('.right-panel'); + const width = panel.clientWidth; + return (_, dx, __) => { + panel.style.width = `${width - dx}px`; + }; + }) + } + }]; +} + + +function leftPanel() { + return ['div.left-panel', + ['div.header', + ['span.title', 'OPCUT'], + ['a.github', { + props: { + title: 'GitHub', + href: 'https://github.com/bozokopic/opcut' + }}, + ['span.fa.fa-github'] + ] + ], + ['div.group', + ['label', 'Method'], + selectInput(r.get('form', 'method'), + ['forward_greedy', 'greedy'], + val => r.set(['form', 'method'], val)) + ], + ['div.group', + ['label', 'Cut width'], + numberInput(r.get('form', 'cut_width'), + val => r.set(['form', 'cut_width'], val)) + ], + ['div.content', + leftPanelPanels(), + leftPanelItems() + ], + ['button.calculate', { + on: { + click: common.calculate + }}, + 'Calculate' + ] + ]; +} + + +function leftPanelPanels() { + const panelsPath = ['form', 'panels']; + + return ['div', + ['table', + ['thead', + ['tr', + ['th.col-name', 'Panel name'], + ['th.col-quantity', 'Quantity'], + ['th.col-height', 'Height'], + ['th.col-width', 'Width'], + ['th.col-delete'] + ] + ], + ['tbody', + r.get(panelsPath).map((panel, index) => ['tr', + ['td.col-name', + ['div', + textInput(panel.name, + val => r.set([panelsPath, index, 'name'], val)) + ] + ], + ['td.col-quantity', + ['div', + numberInput(panel.quantity, + val => r.set([panelsPath, index, 'quantity'], val)) + ] + ], + ['td.col-height', + ['div', + numberInput(panel.height, + val => r.set([panelsPath, index, 'height'], val)) + ] + ], + ['td.col-width', + ['div', + numberInput(panel.width, + val => r.set([panelsPath, index, 'width'], val)) + ] + ], + ['td.col-delete', + ['button', { + on: { + click: _ => r.change(panelsPath, u.omit(index)) + }}, + ['span.fa.fa-minus'] + ] + ] + ]) + ], + ['tfoot', + ['tr', + ['td', { + props: { + colSpan: 5 + }}, + ['div', + ['button', { + on: { + click: common.addPanel + }}, + ['span.fa.fa-plus'], + ' Add' + ], + ['span.spacer'], + ['button', { + on: { + click: common.csvImportPanels + }}, + ['span.fa.fa-download'], + ' CSV Import' + ], + ['button', { + on: { + click: common.csvExportPanels + }}, + ['span.fa.fa-upload'], + ' CSV Export' + ] + ] + ] + ] + ] + ] + ]; +} + + +function leftPanelItems() { + const itemsPath = ['form', 'items']; + + return ['div', + ['table', + ['thead', + ['tr', + ['th.col-name', 'Item name'], + ['th.col-quantity', 'Quantity'], + ['th.col-height', 'Height'], + ['th.col-width', 'Width'], + ['th.col-rotate', 'Rotate'], + ['th.col-delete'] + ] + ], + ['tbody', + r.get(itemsPath).map((item, index) => ['tr', + ['td.col-name', + ['div', + textInput(item.name, + val => r.set([itemsPath, index, 'name'], val)) + ] + ], + ['td.col-quantity', + ['div', + numberInput(item.quantity, + val => r.set([itemsPath, index, 'quantity'], val)) + ] + ], + ['td.col-height', + ['div', + numberInput(item.height, + val => r.set([itemsPath, index, 'height'], val)) + ] + ], + ['td.col-width', + ['div', + numberInput(item.width, + val => r.set([itemsPath, index, 'width'], val)) + ] + ], + ['td.col-rotate', + ['div', + checkboxInput(item.can_rotate, + val => r.set([itemsPath, index, 'can_rotate'], val)) + ] + ], + ['td.col-delete', + ['button', { + on: { + click: _ => r.change(itemsPath, u.omit(index)) + }}, + ['span.fa.fa-minus'] + ] + ] + ]) + ], + ['tfoot', + ['tr', + ['td', { + props: { + colSpan: 6 + }}, + ['div', + ['button', { + on: { + click: common.addItem + }}, + ['span.fa.fa-plus'], + ' Add' + ], + ['span.spacer'], + ['button', { + on: { + click: common.csvImportItems + }}, + ['span.fa.fa-download'], + ' CSV Import' + ], + ['button', { + on: { + click: common.csvExportItems + }}, + ['span.fa.fa-upload'], + ' CSV Export' + ] + ] + ] + ] + ] + ] + ]; +} + + +function rightPanel() { + const result = r.get('result'); + return ['div.right-panel', (!result ? + [] : + [ + ['div.toolbar', + ['button', { + on: { + click: common.generateOutput + }}, + ['span.fa.fa-file-pdf-o'], + ' PDF' + ] + ], + Object.keys(result.params.panels).map(rightPanelPanel) + ]) + ]; +} + + +function rightPanelPanel(panel) { + const isSelected = item => u.equals(r.get('selected'), {panel: panel, item: item}); + + return ['div.panel', + ['div.panel-name', { + class: { + selected: isSelected(null) + }, + on: { + click: () => r.set('selected', {panel: panel, item: null}) + }}, + panel + ], + u.filter(used => used.panel == panel)(r.get('result', 'used')).map(used => + ['div.item', { + class: { + selected: isSelected(used.item) + }, + on: { + click: () => r.set('selected', {panel: panel, item: used.item}) + }}, + ['div.item-name', used.item], + (used.rotate ? ['span.item-rotate.fa.fa-refresh'] : []), + ['div.item-x', + 'X:', + String(Math.round(used.x * 100) / 100) + ], + ['div.item-y', + 'Y:', + String(Math.round(used.y * 100) / 100) + ] + ]) + ]; +} + + +function centerPanel() { + const result = r.get('result'); + const selected = r.get('selected'); + if (!result || !selected.panel) + return ['div.center-panel']; + + const panel = result.params.panels[selected.panel]; + const used = u.filter(i => i.panel == selected.panel, result.used); + const unused = u.filter(i => i.panel == selected.panel, result.unused); + const panelColor = 'rgb(100,100,100)'; + const itemColor = 'rgb(250,250,250)'; + const selectedItemColor = 'rgb(200,140,140)'; + const unusedColor = 'rgb(238,238,238)'; + const fontSize = String(Math.max(panel.height, panel.width) * 0.02); + + return ['div.center-panel', + ['svg', { + attrs: { + width: '100%', + height: '100%', + viewBox: [0, 0, panel.width, panel.height].join(' '), + preserveAspectRatio: 'xMidYMid' + }}, + ['rect', { + attrs: { + x: '0', + y: '0', + width: String(panel.width), + height: String(panel.height), + 'stroke-width': '0', + fill: panelColor + }} + ], + used.map(used => { + const item = result.params.items[used.item]; + const width = (used.rotate ? item.height : item.width); + const height = (used.rotate ? item.width : item.height); + return ['rect', { + props: { + style: 'cursor: pointer' + }, + attrs: { + x: String(used.x), + y: String(used.y), + width: String(width), + height: String(height), + 'stroke-width': '0', + fill: (used.item == selected.item ? selectedItemColor : itemColor) + }, + on: { + click: () => r.set(['selected', 'item'], used.item) + }} + ]; + }), + unused.map(unused => { + return ['rect', { + attrs: { + x: String(unused.x), + y: String(unused.y), + width: String(unused.width), + height: String(unused.height), + 'stroke-width': '0', + fill: unusedColor + }} + ]; + }), + used.map(used => { + const item = result.params.items[used.item]; + const width = (used.rotate ? item.height : item.width); + const height = (used.rotate ? item.width : item.height); + return ['text', { + props: { + style: 'cursor: pointer' + }, + attrs: { + x: String(used.x + width / 2), + y: String(used.y + height / 2), + 'alignment-baseline': 'middle', + 'text-anchor': 'middle', + 'font-size': fontSize + }, + on: { + click: () => r.set(['selected', 'item'], used.item) + }}, + used.item + (used.rotate ? ' \u293E' : '') + ]; + }) + ] + ]; +} + + +function textInput(value, onChange) { + return ['input', { + props: { + type: 'text', + value: value + }, + on: { + change: evt => onChange(evt.target.value) + } + }]; +} + + +function numberInput(value, onChange) { + return ['input', { + props: { + type: 'number', + value: value + }, + on: { + change: evt => onChange(evt.target.valueAsNumber) + } + }]; +} + + +function checkboxInput(value, onChange) { + return ['input', { + props: { + type: 'checkbox', + checked: value + }, + on: { + change: evt => onChange(evt.target.checked) + } + }]; +} + + +function selectInput(selected, values, onChange) { + return ['select', { + on: { + change: evt => onChange(evt.target.value) + }}, + values.map(i => ['option', { + props: { + selected: i == selected + }}, + i + ]) + ]; +} |
