ENH - rebuild calculator with scientific history

This commit is contained in:
smx.pusha
2026-08-13 14:23:22 +02:00
parent bc4b439eb6
commit 223cb4a600
7 changed files with 912 additions and 109 deletions
+149 -2
View File
@@ -1,25 +1,67 @@
import { defineStore } from 'pinia'
import {
applyCalculatorUnary,
calculatorPercent,
chooseCalculatorOperator,
clearCalculator,
formatCalculatorNumber,
inputDecimal,
inputDigit,
resolveCalculator,
toggleCalculatorSign,
type CalculatorOperator,
type CalculatorState,
type CalculatorUnaryOperation,
} from '@/utils/calculator'
export type CalculatorHistoryEntry = {
id: string
expression: string
result: string
createdAt: number
}
type CalculatorParentFrame = Pick<
CalculatorState,
'accumulator' | 'calculation' | 'pendingOperator' | 'waitingForOperand'
>
const HISTORY_KEY = 'sky_phone_calculator_history_v1'
function loadHistory(): CalculatorHistoryEntry[] {
if (typeof localStorage === 'undefined') return []
const stored = localStorage.getItem(HISTORY_KEY)
if (!stored) return []
try {
const parsed = JSON.parse(stored) as CalculatorHistoryEntry[]
return Array.isArray(parsed) ? parsed.slice(0, 50) : []
} catch {
console.warn('[Calculator] Ignored invalid persisted history.')
return []
}
}
export const useCalculatorStore = defineStore('calculator', {
state: (): CalculatorState => clearCalculator(),
state: () => ({
...clearCalculator(),
angleUnit: 'radians' as 'degrees' | 'radians',
history: loadHistory(),
memory: 0,
parentFrames: [] as CalculatorParentFrame[],
second: false,
}),
actions: {
backspace(): void {
if (this.error || this.waitingForOperand) return
this.display = this.display.length > 1 ? this.display.slice(0, -1) : '0'
},
chooseOperator(operator: CalculatorOperator): void {
Object.assign(this, chooseCalculatorOperator(this.$state, operator))
},
clear(): void {
Object.assign(this, clearCalculator())
this.parentFrames = []
},
decimal(): void {
Object.assign(this, inputDecimal(this.$state))
@@ -28,7 +70,18 @@ export const useCalculatorStore = defineStore('calculator', {
Object.assign(this, inputDigit(this.$state, value))
},
equals(): void {
Object.assign(this, resolveCalculator(this.$state))
const expression = this.calculation
const next = resolveCalculator(this.$state)
Object.assign(this, next)
if (!expression || next.error || next.calculation === expression) return
this.history.unshift({
id: `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
expression: next.calculation.replace(/\s*=\s*$/, ''),
result: next.display,
createdAt: Date.now(),
})
this.history = this.history.slice(0, 50)
this.persistHistory()
},
percent(): void {
Object.assign(this, calculatorPercent(this.$state))
@@ -36,5 +89,99 @@ export const useCalculatorStore = defineStore('calculator', {
toggleSign(): void {
Object.assign(this, toggleCalculatorSign(this.$state))
},
unary(operation: CalculatorUnaryOperation): void {
const result = applyCalculatorUnary(
Number(this.display),
operation,
this.angleUnit,
)
if (result === null) {
Object.assign(this, {
...clearCalculator(),
display: 'Error',
error: true,
})
return
}
this.display = formatCalculatorNumber(result)
this.waitingForOperand = true
},
constant(value: number): void {
this.display = formatCalculatorNumber(value)
this.error = false
this.waitingForOperand = true
},
random(): void {
this.display = formatCalculatorNumber(Math.random())
this.error = false
this.waitingForOperand = true
},
memoryClear(): void {
this.memory = 0
},
memoryAdd(): void {
this.memory += Number(this.display) || 0
},
memorySubtract(): void {
this.memory -= Number(this.display) || 0
},
memoryRecall(): void {
this.constant(this.memory)
},
openParenthesis(): void {
this.parentFrames.push({
accumulator: this.accumulator,
calculation: this.calculation,
pendingOperator: this.pendingOperator,
waitingForOperand: this.waitingForOperand,
})
Object.assign(this, clearCalculator())
},
closeParenthesis(): void {
const frame = this.parentFrames.pop()
if (!frame) return
const inner = this.pendingOperator
? resolveCalculator(this.$state)
: { ...this.$state }
const innerExpression = (inner.calculation || inner.display).replace(
/\s*=\s*$/,
'',
)
Object.assign(this, {
...frame,
display: inner.display,
calculation: frame.calculation
? `${frame.calculation} (${innerExpression})`
: `(${innerExpression})`,
error: inner.error,
waitingForOperand: false,
})
},
toggleAngleUnit(): void {
this.angleUnit = this.angleUnit === 'radians' ? 'degrees' : 'radians'
},
toggleSecond(): void {
this.second = !this.second
},
removeHistory(id: string): void {
this.history = this.history.filter((entry) => entry.id !== id)
this.persistHistory()
},
clearHistory(): void {
this.history = []
this.persistHistory()
},
useHistory(entry: CalculatorHistoryEntry): void {
Object.assign(this, {
...clearCalculator(),
display: entry.result,
calculation: entry.expression,
waitingForOperand: true,
})
},
persistHistory(): void {
if (typeof localStorage === 'undefined') return
localStorage.setItem(HISTORY_KEY, JSON.stringify(this.history))
},
},
})
+14 -1
View File
@@ -2403,7 +2403,20 @@ const defaultLocales: LocaleTree = {
default: 'The housing request failed.',
},
},
calculator: { name: 'Calculator' },
calculator: {
name: 'Calculator',
history: 'Calculation history',
changeMode: 'Change calculator mode',
scientificFunctions: 'Scientific functions',
edit: 'Edit',
done: 'Done',
closeHistory: 'Close history',
today: 'Today',
yesterday: 'Yesterday',
earlier: 'Earlier',
noHistory: 'No calculations yet',
clearHistory: 'Clear History',
},
snake: {
name: 'Snake',
backToMenu: 'Back to game menu',