UNPKG

oda-framework

Version:

It's an ES Progressive Framework based on the technology of Web Components and designed especially for creating custom UI/UX of any complexity for web and cross-platform PWA mobile applications.

211 lines (210 loc) 8.36 kB
ODA({is: 'oda-calculator', imports: '@oda/button', template: /*html*/ ` <style> :host { @apply --vertical; padding: 16px; @apply --shadow; height: fit-content; } .header { @apply --header } .layout { @apply --layout } oda-button { margin: 0px 2px; } </style> <div class="border vertical" style="margin-bottom: 16px; text-align: right"> <span style="font-size: small" class="dimmed">{{operation}}</span> <span style="font-size: large">{{error || expression || value || 0}} <span disabled>{{hint}}</span> </span> </div> <div class="horizontal" style="margin: 0px 8px 0px auto" ~style="{height: buttonHeight + 'px'}"> <oda-button class="raised flex" @tap="chooseAccuracy">Acc <span disabled>{{Acc}}</span></oda-button> </div> <div class="horizontal" ::data="calc.data"> <div class="vertical flex" ~for="data?.cols" style="margin: 0px 8px" > <div ~for="$for.item?.rows" class="horizontal flex" style="margin-top: 8px;" ~props="$$for.item.props" ~style="{height: buttonHeight + 'px'}"> <oda-button class="raised flex" ~for="$$for.item.buttons" ~html="$$$for.item?.key || $$$for.key" @tap="tap" :item="$$$for.item" ~props="$$$for.item.props" ~style="{width: 100/Object.keys($$for.item.buttons).length + '%'}"></oda-button> </div> </div> </div> `, data: {}, error: undefined, hints: [], // unclosed brackets stack: [], timerClear: '', // a variable containing a timer to clear the monitor operation: '0', // the value of the previous expression result: 0, answer: '0', $public: { accuracy: { $def: 2, $label: 'Точность', $save: true, $list: [0, 1, 2, 3, 4, 5], }, buttonHeight: { $def: 32, $label: 'Высота кнопки', $save: true, } }, hostAttributes: { tabindex: 1 }, get hint() { return this.hints.map(i => i?.hint).join(''); }, get expression() { return this.stack.map(i => (i?.name || i?.key)).join('').replace(/\B(?=(\d{3})+(?!\d))/g, " "); }, get calcExpression() { return ([...this.stack]).map(i => { return (i.expr || i.name || i.key); }).join(''); }, get Acc() { // bit width of the result return ` = ${this.accuracy}` }, get value() { return this.result.toFixed(this.Acc.match(/\d$/)) }, $keyBindings: { Enter() { this.calc(); }, Backspace() { this.back(); }, Escape() { this.clear(); }, }, attached() { this.listen('keydown', '_onKeyDown', { target: document }); }, detached() { this.unlisten('keydown', '_onKeyDown', { target: document }); }, _onKeyDown(e) { e.key.match(/[0-9().*/+-]/) ? this.tap(e.key) : false; }, tap(e) { this.error = undefined; // on any input, the error is cleared this.operation = `Ans = ${this.value}`; // for any input, the result is formed according to a given template const model = e.currentTarget?.item ? e.currentTarget.item : { key: e }; // determine if a calculator button or keyboard key was pressed if (model?.command && this[model.command]) // if the button has a function, then it is executed return this[model.command]() if (this.hints[0]?.hint === (model?.key || model?.name)) // checking closing brackets this.hints.shift(); this.expression == 0 && this.stack.length === 1 ? this.stack.splice(0, 1, model) : this.stack.push(model); // if there is only zero in the expression, replace it with the entered character if (model?.hint) this.hints.unshift({ key: model?.hint, hint: model?.hint }); if (/^[a-zA-Z(]/.test(model?.expr || model?.name || model?.key) && /[0-9)]$/.test(this.stack[this.stack.length - 2]?.key)) { // if immediately after the number certain symbols follow, you must substitute multiplication this.stack.splice(this.stack.length - 1, 1, { key: model.key, name: model?.name, expr: `*${model?.expr}`, hint: model?.hint }); } if (/^\d/.test(model?.key || model?.expr) && /[a-zA-Z)]$/.test(this.stack[this.stack.length - 2]?.expr || this.stack[this.stack.length - 2]?.key || 0)) { // if the number is written immediately after certain characters, it is necessary to insert multiplication this.stack.splice(this.stack.length - 1, 1, { key: model.key, expr: `*${model.key}` }) } this.expression = undefined; this.hint = undefined; try { (new Function([], `with (this) {return ${this.calcExpression.replace(/[a-zA-Z]\.|[a-zA-Z()]/g, '') + 0}}`)).call(this); // remove all letters, brackets and dots in operations and test this.expression = undefined; this.hint = undefined; } catch (e) { this.error = e; this.stack[this.stack.length - 1]?.hint ? this.hints.pop() : false; this.stack.pop(model); } }, calc() { this.stack.push(...this.hints); // close all brackets this.expression = undefined; this.answer = this.calcExpression; try { this.result = (new Function([], `with (this) {return ${this.calcExpression}}`)).call(this); this.value = this.result.toFixed(this.Acc.match(/\d$/)); // rounding to the specified value this.operation = this.expression + ' ='; } catch (e) { this.error = 'Error'; } this.stack = [{ key: this.value, result: true }]; this.hints = []; }, clear() { this.stack = [{ key: 0 }]; this.hints = []; this.operation = '0'; }, back() { if (this.stack[this.stack.length - 1]?.hint === this.hints[this.hints.length - 1]?.hint) { this.hints.shift(); this.hint = undefined; } if (this.stack.length === 1) { this.stack.splice(-1, 1, { key: 0 }); this.expression = undefined; } else { this.stack.pop(); this.expression = undefined; this.hint = undefined; } }, getAnswer() { if (this.expression == 0 && this.stack.length === 1) { this.stack.splice(0, 1, { name: 'Ans', expr: `(${this.answer})` }); } else { try { this.stack.push({ name: 'Ans', expr: `(${this.answer})` }); (new Function([], `with (this) {return ${this.calcExpression}}`)).call(this) } catch (e) { this.stack.pop(); this.answer = '0'; } } this.expression = undefined; this.calcExpression = undefined; }, calcFactorial() { const factorial = (num = this.stack[this.stack.length - 1].key - 1) => { return (num !== 1) ? num * factorial(num - 1) : 1 } this.stack.push({ name: '!', expr: `*${factorial()}` }); this.expression = undefined; }, // choose what bit depth the number will be chooseAccuracy() { switch (this.accuracy) { case 0: this.accuracy = 1; break; case 1: this.accuracy = 2; break; case 2: this.accuracy = 3; break; case 3: this.accuracy = 4; break; case 4: this.accuracy = 5; break; case 5: this.accuracy = 0; break; } this.stack[this.stack.length - 1]?.result ? this.stack.splice(-1, 1, { key: (+this.value).toFixed(this.accuracy), result: true }) : 0; this.expression = undefined; }, })