aboutsummaryrefslogtreecommitdiff
path: root/src_js/vt.js
diff options
context:
space:
mode:
authorbozo.kopic <bozo@kopic.xyz>2021-11-07 15:52:44 +0100
committerbozo.kopic <bozo@kopic.xyz>2021-12-18 02:38:50 +0100
commit0702d13263bf501c1db074ce1544e60b95161210 (patch)
treeebca76946cead0ffcc742a64c15dd6f5e79958fa /src_js/vt.js
parent56a75fcb8f5a9e4c05ccec8eb4a3345a115da441 (diff)
major rewritev0.3.0
Diffstat (limited to 'src_js/vt.js')
-rw-r--r--src_js/vt.js469
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
+ ])
+ ];
+}