feat(calculator): Scientific-Modus implementieren
Neuer Taschenrechner-Modus mit 6 wissenschaftlichen Buttons (√, x², xⁿ, π, e, ±), Formel-Helfer (Kreisfläche, Kreisumfang, °C↔°F, Pythagoras, Prozent) und erweiterter Tastaturunterstützung (p → Pi, ^ → Potenz). - calc-scientific.js: IIFE, registriert 'scientific' Mode via Calculator.registerMode() - newtab.html: Script-Tag nach calculator.js eingefügt (Load-Order eingehalten) - i18n.js: 15 neue Keys pro Sprache (DE + EN) - main.css: Styles für .calc-sci-buttons und .calc-formula-* Komponenten
This commit is contained in:
@@ -507,6 +507,7 @@
|
|||||||
<script src="src/js/widgets.js"></script>
|
<script src="src/js/widgets.js"></script>
|
||||||
<script src="src/js/notes.js"></script>
|
<script src="src/js/notes.js"></script>
|
||||||
<script src="src/js/calculator.js"></script>
|
<script src="src/js/calculator.js"></script>
|
||||||
|
<script src="src/js/calc-scientific.js"></script>
|
||||||
<script src="src/js/timer.js"></script>
|
<script src="src/js/timer.js"></script>
|
||||||
<script src="src/js/image-ref.js"></script>
|
<script src="src/js/image-ref.js"></script>
|
||||||
<script src="src/js/bookmark-import.js"></script>
|
<script src="src/js/bookmark-import.js"></script>
|
||||||
|
|||||||
@@ -1332,6 +1332,65 @@ body.show-desc .bm-desc { display: block; }
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Calculator Scientific Mode */
|
||||||
|
.calc-sci-buttons {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.calc-formula-helper {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
padding-top: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.calc-formula-label {
|
||||||
|
font-size: 9px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.calc-formula-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 4px 6px;
|
||||||
|
background: rgba(0,0,0,0.3);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: 'Rajdhani', sans-serif;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.calc-formula-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.calc-formula-row label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
.calc-formula-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 4px 6px;
|
||||||
|
background: rgba(0,0,0,0.3);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: 'Rajdhani', sans-serif;
|
||||||
|
}
|
||||||
|
.calc-formula-result {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: 'Rajdhani', monospace;
|
||||||
|
text-align: right;
|
||||||
|
min-height: 20px;
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
TIMER WIDGET
|
TIMER WIDGET
|
||||||
============================================ */
|
============================================ */
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
/* =============================================
|
||||||
|
HELLION NEWTAB — calc-scientific.js
|
||||||
|
Scientific-Modus für Calculator Widget
|
||||||
|
============================================= */
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const FORMULAS = [
|
||||||
|
{
|
||||||
|
key: 'circle_area',
|
||||||
|
fields: [{ key: 'radius', default: '' }],
|
||||||
|
calc: (vals) => Math.PI * vals.radius * vals.radius
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'circle_circumference',
|
||||||
|
fields: [{ key: 'radius', default: '' }],
|
||||||
|
calc: (vals) => 2 * Math.PI * vals.radius
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'celsius_to_fahrenheit',
|
||||||
|
fields: [{ key: 'temp', default: '' }],
|
||||||
|
calc: (vals) => (vals.temp * 9 / 5) + 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'fahrenheit_to_celsius',
|
||||||
|
fields: [{ key: 'temp', default: '' }],
|
||||||
|
calc: (vals) => (vals.temp - 32) * 5 / 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'pythagoras',
|
||||||
|
fields: [{ key: 'a', default: '' }, { key: 'b', default: '' }],
|
||||||
|
calc: (vals) => Math.sqrt(vals.a * vals.a + vals.b * vals.b)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'percentage',
|
||||||
|
fields: [{ key: 'value', default: '' }, { key: 'percent', default: '' }],
|
||||||
|
calc: (vals) => vals.value * vals.percent / 100
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let _keyboardExtHandler = null;
|
||||||
|
|
||||||
|
function renderSciButtons(container) {
|
||||||
|
const grid = document.createElement('div');
|
||||||
|
grid.className = 'calc-buttons calc-sci-buttons';
|
||||||
|
|
||||||
|
const buttons = [
|
||||||
|
['√', 'sqrt', 'operator'],
|
||||||
|
['x²', 'square', 'operator'],
|
||||||
|
['xⁿ', 'power', 'operator'],
|
||||||
|
['π', 'pi', 'operator'],
|
||||||
|
['e', 'euler', 'operator'],
|
||||||
|
['±', 'negate', 'operator']
|
||||||
|
];
|
||||||
|
|
||||||
|
buttons.forEach(([label, value, cls]) => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'calc-btn' + (cls ? ' ' + cls : '');
|
||||||
|
btn.textContent = label;
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.addEventListener('click', () => handleSciKey(value));
|
||||||
|
grid.appendChild(btn);
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSciKey(key) {
|
||||||
|
switch (key) {
|
||||||
|
case 'sqrt':
|
||||||
|
Calculator._currentExpr += 'sqrt(';
|
||||||
|
Calculator._updateDisplay();
|
||||||
|
break;
|
||||||
|
case 'square':
|
||||||
|
Calculator._currentExpr += '^2';
|
||||||
|
Calculator._updateDisplay();
|
||||||
|
break;
|
||||||
|
case 'power':
|
||||||
|
Calculator._handleKey('^');
|
||||||
|
break;
|
||||||
|
case 'pi':
|
||||||
|
Calculator._currentExpr += '3.14159265359';
|
||||||
|
Calculator._updateDisplay();
|
||||||
|
break;
|
||||||
|
case 'euler':
|
||||||
|
Calculator._currentExpr += '2.71828182846';
|
||||||
|
Calculator._updateDisplay();
|
||||||
|
break;
|
||||||
|
case 'negate':
|
||||||
|
handleNegate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNegate() {
|
||||||
|
const expr = Calculator._currentExpr;
|
||||||
|
if (!expr && Calculator._lastResult) {
|
||||||
|
const num = parseFloat(Calculator._lastResult);
|
||||||
|
if (!isNaN(num)) {
|
||||||
|
Calculator._currentExpr = String(-num);
|
||||||
|
Calculator._lastResult = '';
|
||||||
|
Calculator._updateDisplay();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const match = expr.match(/(-?\d+\.?\d*)$/);
|
||||||
|
if (match) {
|
||||||
|
const num = parseFloat(match[1]);
|
||||||
|
const negated = String(-num);
|
||||||
|
Calculator._currentExpr = expr.slice(0, expr.length - match[1].length) + negated;
|
||||||
|
Calculator._updateDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFormulaHelper(container) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'calc-formula-helper';
|
||||||
|
|
||||||
|
const label = document.createElement('div');
|
||||||
|
label.className = 'calc-formula-label';
|
||||||
|
label.textContent = t('calculator.sci.formulas');
|
||||||
|
|
||||||
|
const select = document.createElement('select');
|
||||||
|
select.className = 'calc-formula-select';
|
||||||
|
|
||||||
|
const emptyOpt = document.createElement('option');
|
||||||
|
emptyOpt.value = '';
|
||||||
|
emptyOpt.textContent = t('calculator.sci.select_formula');
|
||||||
|
select.appendChild(emptyOpt);
|
||||||
|
|
||||||
|
FORMULAS.forEach((f, i) => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = String(i);
|
||||||
|
opt.textContent = t('calculator.sci.formula.' + f.key);
|
||||||
|
select.appendChild(opt);
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputsContainer = document.createElement('div');
|
||||||
|
inputsContainer.className = 'calc-formula-inputs';
|
||||||
|
|
||||||
|
const resultContainer = document.createElement('div');
|
||||||
|
resultContainer.className = 'calc-formula-result';
|
||||||
|
|
||||||
|
select.addEventListener('change', () => {
|
||||||
|
while (inputsContainer.firstChild) {
|
||||||
|
inputsContainer.removeChild(inputsContainer.firstChild);
|
||||||
|
}
|
||||||
|
resultContainer.textContent = '';
|
||||||
|
|
||||||
|
const idx = parseInt(select.value, 10);
|
||||||
|
if (isNaN(idx)) return;
|
||||||
|
|
||||||
|
const formula = FORMULAS[idx];
|
||||||
|
renderFormulaInputs(formula, inputsContainer, resultContainer);
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.append(label, select, inputsContainer, resultContainer);
|
||||||
|
container.appendChild(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFormulaInputs(formula, inputsEl, resultEl) {
|
||||||
|
const inputs = {};
|
||||||
|
|
||||||
|
formula.fields.forEach(field => {
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'calc-formula-row';
|
||||||
|
|
||||||
|
const lbl = document.createElement('label');
|
||||||
|
lbl.textContent = t('calculator.sci.field.' + field.key);
|
||||||
|
|
||||||
|
const inp = document.createElement('input');
|
||||||
|
inp.type = 'number';
|
||||||
|
inp.className = 'calc-formula-input';
|
||||||
|
inp.placeholder = '0';
|
||||||
|
inp.step = 'any';
|
||||||
|
inputs[field.key] = inp;
|
||||||
|
|
||||||
|
inp.addEventListener('input', () => {
|
||||||
|
recalcFormula(formula, inputs, resultEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
row.append(lbl, inp);
|
||||||
|
inputsEl.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function recalcFormula(formula, inputs, resultEl) {
|
||||||
|
const vals = {};
|
||||||
|
let allValid = true;
|
||||||
|
|
||||||
|
for (const field of formula.fields) {
|
||||||
|
const v = parseFloat(inputs[field.key].value);
|
||||||
|
if (isNaN(v)) { allValid = false; break; }
|
||||||
|
vals[field.key] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allValid) {
|
||||||
|
resultEl.textContent = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = formula.calc(vals);
|
||||||
|
if (result === null || !isFinite(result)) {
|
||||||
|
resultEl.textContent = t('calculator.error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultEl.textContent = '= ' + Calculator._formatResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindSciKeyboard(widgetEl) {
|
||||||
|
_keyboardExtHandler = (e) => {
|
||||||
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||||||
|
if (e.target.contentEditable === 'true') return;
|
||||||
|
|
||||||
|
if (e.key === 'p') {
|
||||||
|
handleSciKey('pi');
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
} else if (e.key === '^') {
|
||||||
|
handleSciKey('power');
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
widgetEl.addEventListener('keydown', _keyboardExtHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
Calculator.registerMode('scientific', {
|
||||||
|
label: '📐',
|
||||||
|
shortName: 'Sci',
|
||||||
|
titleKey: 'calculator.tab.scientific',
|
||||||
|
|
||||||
|
render(bodyEl) {
|
||||||
|
bodyEl.style.padding = '8px';
|
||||||
|
bodyEl.style.display = 'flex';
|
||||||
|
bodyEl.style.flexDirection = 'column';
|
||||||
|
bodyEl.style.flex = '1';
|
||||||
|
bodyEl.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
const display = document.createElement('div');
|
||||||
|
display.className = 'calc-display';
|
||||||
|
|
||||||
|
const exprEl = document.createElement('div');
|
||||||
|
exprEl.className = 'calc-expression';
|
||||||
|
Calculator._displayExprEl = exprEl;
|
||||||
|
|
||||||
|
const resultEl = document.createElement('div');
|
||||||
|
resultEl.className = 'calc-result';
|
||||||
|
resultEl.textContent = Calculator._lastResult || '0';
|
||||||
|
Calculator._displayResultEl = resultEl;
|
||||||
|
|
||||||
|
display.append(exprEl, resultEl);
|
||||||
|
|
||||||
|
const sciSection = document.createElement('div');
|
||||||
|
renderSciButtons(sciSection);
|
||||||
|
|
||||||
|
const stdButtons = Calculator._createButtons();
|
||||||
|
const historyEl = Calculator._createHistoryPanel();
|
||||||
|
|
||||||
|
const formulaSection = document.createElement('div');
|
||||||
|
renderFormulaHelper(formulaSection);
|
||||||
|
|
||||||
|
bodyEl.append(display, sciSection, stdButtons, historyEl, formulaSection);
|
||||||
|
Calculator._updateDisplay();
|
||||||
|
|
||||||
|
const entry = WidgetManager._widgets.get(Calculator.WIDGET_ID);
|
||||||
|
if (entry) bindSciKeyboard(entry.el);
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (_keyboardExtHandler) {
|
||||||
|
const entry = WidgetManager._widgets.get(Calculator.WIDGET_ID);
|
||||||
|
if (entry) {
|
||||||
|
entry.el.removeEventListener('keydown', _keyboardExtHandler);
|
||||||
|
}
|
||||||
|
_keyboardExtHandler = null;
|
||||||
|
}
|
||||||
|
Calculator._displayExprEl = null;
|
||||||
|
Calculator._displayResultEl = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -84,6 +84,21 @@ const STRINGS = {
|
|||||||
'calculator.history': 'History',
|
'calculator.history': 'History',
|
||||||
'calculator.error': 'Fehler',
|
'calculator.error': 'Fehler',
|
||||||
'calculator.tab.standard': 'Standard',
|
'calculator.tab.standard': 'Standard',
|
||||||
|
'calculator.tab.scientific': 'Wissenschaftlich',
|
||||||
|
'calculator.sci.formulas': 'Formel-Helfer',
|
||||||
|
'calculator.sci.select_formula': 'Formel wählen…',
|
||||||
|
'calculator.sci.formula.circle_area': 'Kreisfläche (π×r²)',
|
||||||
|
'calculator.sci.formula.circle_circumference':'Kreisumfang (2πr)',
|
||||||
|
'calculator.sci.formula.celsius_to_fahrenheit':'°C → °F',
|
||||||
|
'calculator.sci.formula.fahrenheit_to_celsius':'°F → °C',
|
||||||
|
'calculator.sci.formula.pythagoras': 'Pythagoras (√(a²+b²))',
|
||||||
|
'calculator.sci.formula.percentage': 'Prozentwert',
|
||||||
|
'calculator.sci.field.radius': 'Radius',
|
||||||
|
'calculator.sci.field.temp': 'Temperatur',
|
||||||
|
'calculator.sci.field.a': 'Seite a',
|
||||||
|
'calculator.sci.field.b': 'Seite b',
|
||||||
|
'calculator.sci.field.value': 'Wert',
|
||||||
|
'calculator.sci.field.percent': 'Prozent',
|
||||||
|
|
||||||
// Timer
|
// Timer
|
||||||
'timer.title': 'Timer',
|
'timer.title': 'Timer',
|
||||||
@@ -393,6 +408,21 @@ const STRINGS = {
|
|||||||
'calculator.history': 'History',
|
'calculator.history': 'History',
|
||||||
'calculator.error': 'Error',
|
'calculator.error': 'Error',
|
||||||
'calculator.tab.standard': 'Standard',
|
'calculator.tab.standard': 'Standard',
|
||||||
|
'calculator.tab.scientific': 'Scientific',
|
||||||
|
'calculator.sci.formulas': 'Formula Helper',
|
||||||
|
'calculator.sci.select_formula': 'Choose formula…',
|
||||||
|
'calculator.sci.formula.circle_area': 'Circle Area (π×r²)',
|
||||||
|
'calculator.sci.formula.circle_circumference':'Circle Circumference (2πr)',
|
||||||
|
'calculator.sci.formula.celsius_to_fahrenheit':'°C → °F',
|
||||||
|
'calculator.sci.formula.fahrenheit_to_celsius':'°F → °C',
|
||||||
|
'calculator.sci.formula.pythagoras': 'Pythagoras (√(a²+b²))',
|
||||||
|
'calculator.sci.formula.percentage': 'Percentage',
|
||||||
|
'calculator.sci.field.radius': 'Radius',
|
||||||
|
'calculator.sci.field.temp': 'Temperature',
|
||||||
|
'calculator.sci.field.a': 'Side a',
|
||||||
|
'calculator.sci.field.b': 'Side b',
|
||||||
|
'calculator.sci.field.value': 'Value',
|
||||||
|
'calculator.sci.field.percent': 'Percent',
|
||||||
|
|
||||||
// Timer
|
// Timer
|
||||||
'timer.title': 'Timer',
|
'timer.title': 'Timer',
|
||||||
|
|||||||
Reference in New Issue
Block a user