oda-framework
Version:
211 lines (210 loc) • 8.36 kB
JavaScript
ODA({is: 'oda-calculator', imports: '@oda/button',
template: /*html*/ `
<style>
:host {
--vertical;
padding: 16px;
--shadow;
height: fit-content;
}
.header {
--header
}
.layout {
--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" ="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" :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;
},
})