Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions api/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ exports.calculate = function(req, res) {
'subtract': function(a, b) { return a - b },
'multiply': function(a, b) { return a * b },
'divide': function(a, b) { return a / b },
'sin': function(a) { return Math.sin(a) },
'cos': function(a) { return Math.cos(a) },
'tan': function(a) { return Math.tan(a) },
'sqrt': function(a) { return Math.sqrt(a) },
'power': function(a, b) { return Math.pow(a, b) },
'log': function(a) { return Math.log10(a) },
};

if (!req.query.operation) {
Expand All @@ -29,16 +35,23 @@ exports.calculate = function(req, res) {
}

if (!req.query.operand1 ||
!req.query.operand1.match(/^(-)?[0-9\.]+(e(-)?[0-9]+)?$/) ||
!req.query.operand1.match(/^(-)?[0-9.]+(e(-)?[0-9]+)?$/) ||
req.query.operand1.replace(/[-0-9e]/g, '').length > 1) {
throw new Error("Invalid operand1: " + req.query.operand1);
}

if (!req.query.operand2 ||
!req.query.operand2.match(/^(-)?[0-9\.]+(e(-)?[0-9]+)?$/) ||
req.query.operand2.replace(/[-0-9e]/g, '').length > 1) {
throw new Error("Invalid operand2: " + req.query.operand2);
}
// Check if operation requires two operands
var singleOperandOps = ['sin', 'cos', 'tan', 'sqrt', 'log'];
var requiresTwoOperands = singleOperandOps.indexOf(req.query.operation) === -1;

res.json({ result: operation(req.query.operand1, req.query.operand2) });
if (requiresTwoOperands) {
if (!req.query.operand2 ||
!req.query.operand2.match(/^(-)?[0-9.]+(e(-)?[0-9]+)?$/) ||
req.query.operand2.replace(/[-0-9e]/g, '').length > 1) {
throw new Error("Invalid operand2: " + req.query.operand2);
}
res.json({ result: operation(req.query.operand1, req.query.operand2) });
} else {
res.json({ result: operation(req.query.operand1) });
}
};
55 changes: 54 additions & 1 deletion public/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,28 @@ function calculate(operand1, operand2, operation) {
case '/':
uri += "?operation=divide";
break;
case 'power':
uri += "?operation=power";
break;
case 'sin':
case 'cos':
case 'tan':
case 'sqrt':
case 'log':
uri += "?operation=" + operation;
break;
default:
setError();
return;
}

uri += "&operand1=" + encodeURIComponent(operand1);
uri += "&operand2=" + encodeURIComponent(operand2);

// Only add operand2 for operations that need it
var singleOperandOps = ['sin', 'cos', 'tan', 'sqrt', 'log'];
if (singleOperandOps.indexOf(operation) === -1) {
uri += "&operand2=" + encodeURIComponent(operand2);
}

setLoading(true);

Expand Down Expand Up @@ -132,6 +147,44 @@ function equalPressed() {
calculate(operand1, operand2, operation);
}

function scientificOperation(op) {
// Scientific operations work on the current value immediately
var currentValue = getValue();

// Save state for potential chaining
operand1 = currentValue;
operation = op;
state = states.complete;

// Calculate immediately (operand2 not needed for these operations)
calculate(currentValue, null, op);
}

function toggleTheme() {
var body = document.body;
var themeIcon = document.querySelector('.theme-icon');

if (body.classList.contains('dark-theme')) {
body.classList.remove('dark-theme');
body.classList.add('light-theme');
themeIcon.textContent = '🌙';
} else if (body.classList.contains('light-theme')) {
body.classList.remove('light-theme');
body.classList.add('dark-theme');
themeIcon.textContent = '☀️';
} else {
// First time toggle - check system preference
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (prefersDark) {
body.classList.add('light-theme');
themeIcon.textContent = '🌙';
} else {
body.classList.add('dark-theme');
themeIcon.textContent = '☀️';
}
}
}

// Enhanced keyboard support
document.addEventListener('keydown', (event) => {
// Handle keydown for special keys
Expand Down
78 changes: 78 additions & 0 deletions public/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
--button-operator: #007bff;
--button-operator-hover: #0056b3;
--button-operator-active: #004085;
--button-scientific: #6f42c1;
--button-scientific-hover: #5a32a3;
--button-scientific-active: #4c2a8a;
--button-equals: #28a745;
--button-equals-hover: #1e7e34;
--button-equals-active: #155724;
Expand Down Expand Up @@ -42,6 +45,32 @@
}
}

/* Light theme override */
body.light-theme {
--primary-bg: #f0f2f5;
--calculator-bg: #ffffff;
--display-bg: #1e1e1e;
--display-text: #ffffff;
--button-bg: #f8f9fa;
--button-hover: #e9ecef;
--button-active: #dee2e6;
--text-primary: #212529;
--text-secondary: #6c757d;
}

/* Dark theme override */
body.dark-theme {
--primary-bg: #121212;
--calculator-bg: #1e1e1e;
--display-bg: #000000;
--display-text: #ffffff;
--button-bg: #2d2d2d;
--button-hover: #3d3d3d;
--button-active: #4d4d4d;
--text-primary: #ffffff;
--text-secondary: #adb5bd;
}

BODY {
height: 100vh;
width: 100vw;
Expand Down Expand Up @@ -108,6 +137,36 @@ BODY {
min-height: auto;
}

/* Theme toggle button */
.theme-toggle {
position: absolute;
top: 1rem;
right: 1rem;
background: var(--button-bg);
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
font-size: 1.5rem;
cursor: pointer;
box-shadow: var(--shadow-md);
transition: all var(--transition-duration) ease;
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}

.theme-toggle:hover {
background: var(--button-hover);
transform: scale(1.1);
box-shadow: var(--shadow-lg);
}

.theme-toggle:active {
transform: scale(0.95);
}

#results {
margin-bottom: 1.5rem;
width: 100%;
Expand Down Expand Up @@ -266,6 +325,21 @@ BODY {
background: var(--button-clear-active);
}

/* Scientific button styling */
.btn-scientific {
background: var(--button-scientific);
color: white;
font-size: 1rem;
}

.btn-scientific:hover {
background: var(--button-scientific-hover);
}

.btn-scientific:active {
background: var(--button-scientific-active);
}

.calculator {
background: var(--calculator-bg);
border-radius: var(--border-radius);
Expand Down Expand Up @@ -306,6 +380,10 @@ BODY {
background: var(--button-clear-active);
}

.btn-scientific.pressed {
background: var(--button-scientific-active);
}

/* Improved mobile touch targets */
@media (max-width: 767px) {
.btn {
Expand Down
11 changes: 10 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
</head>
<body onLoad="setValue(0)">
<div class="container">
<button class="theme-toggle" onClick="toggleTheme()" aria-label="Toggle dark/light mode">
<span class="theme-icon">🌙</span>
</button>
<main class="calculator" role="application" aria-label="Calculator">
<div id="results">
<div id="result" role="textbox" aria-label="Calculator display" aria-live="polite" aria-readonly="true" tabindex="0"><span class="digit0">0</span></div>
Expand All @@ -16,7 +19,13 @@
<div id="loading" style="visibility:hidden" aria-hidden="true"></div>

<div class="calculator-buttons" role="grid" aria-label="Calculator buttons">
<button class="btn span-2" aria-label="Empty button" disabled></button>
<button class="btn btn-scientific" onClick="scientificOperation('sin')" aria-label="Sine">sin</button>
<button class="btn btn-scientific" onClick="scientificOperation('cos')" aria-label="Cosine">cos</button>
<button class="btn btn-scientific" onClick="scientificOperation('tan')" aria-label="Tangent">tan</button>
<button class="btn btn-scientific" onClick="scientificOperation('sqrt')" aria-label="Square root">√</button>

<button class="btn btn-scientific" onClick="scientificOperation('log')" aria-label="Logarithm">log</button>
<button class="btn btn-scientific" onClick="operationPressed('power')" aria-label="Power">x^y</button>
<button class="btn" onClick="clearPressed()" aria-label="Clear all">C</button>
<button class="btn" onClick="clearPressed()" aria-label="Clear entry">CE</button>

Expand Down
84 changes: 84 additions & 0 deletions test/arithmetic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,88 @@ describe('Arithmetic', function () {
});
});
});

describe('Scientific Operations', function () {
describe('Sine', function () {
it('calculates sine of 0', function (done) {
request.get('/arithmetic?operation=sin&operand1=0')
.expect(200)
.end(function (err, res) {
expect(res.body).to.eql({ result: 0 });
done();
});
});
});

describe('Cosine', function () {
it('calculates cosine of 0', function (done) {
request.get('/arithmetic?operation=cos&operand1=0')
.expect(200)
.end(function (err, res) {
expect(res.body).to.eql({ result: 1 });
done();
});
});
});

describe('Square Root', function () {
it('calculates square root of 9', function (done) {
request.get('/arithmetic?operation=sqrt&operand1=9')
.expect(200)
.end(function (err, res) {
expect(res.body).to.eql({ result: 3 });
done();
});
});

it('calculates square root of 0', function (done) {
request.get('/arithmetic?operation=sqrt&operand1=0')
.expect(200)
.end(function (err, res) {
expect(res.body).to.eql({ result: 0 });
done();
});
});
});

describe('Power', function () {
it('calculates 2 to the power of 3', function (done) {
request.get('/arithmetic?operation=power&operand1=2&operand2=3')
.expect(200)
.end(function (err, res) {
expect(res.body).to.eql({ result: 8 });
done();
});
});

it('calculates 10 to the power of 2', function (done) {
request.get('/arithmetic?operation=power&operand1=10&operand2=2')
.expect(200)
.end(function (err, res) {
expect(res.body).to.eql({ result: 100 });
done();
});
});
});

describe('Logarithm', function () {
it('calculates log of 10', function (done) {
request.get('/arithmetic?operation=log&operand1=10')
.expect(200)
.end(function (err, res) {
expect(res.body).to.eql({ result: 1 });
done();
});
});

it('calculates log of 100', function (done) {
request.get('/arithmetic?operation=log&operand1=100')
.expect(200)
.end(function (err, res) {
expect(res.body).to.eql({ result: 2 });
done();
});
});
});
});
});