|
Server : Apache/2.4.62 System : FreeBSD fbsdweb2.web.rcn.net 14.1-RELEASE FreeBSD 14.1-RELEASE releng/14.1-n267679-10e31f0946d8 GENERIC amd64 User : www ( 80) PHP Version : 8.3.8 Disable Function : NONE Directory : /domains/drsuper/CalculusMechanics/images/ |
Upload File : |
/* calc-popup-original-plus-move.js */
/* Based on your original JS, with only one behavior added:
- drag the calculator by the header
It still opens in the same top-right place as before.
*/
document.addEventListener("DOMContentLoaded", () => {
if (document.getElementById("calcFab") || document.getElementById("calcOverlay")) return;
const wrapper = document.createElement("div");
wrapper.innerHTML = `
<button id="calcFab" type="button" aria-label="Open calculator">🧮</button>
<div id="calcOverlay" class="calc-overlay" role="dialog" aria-modal="true" aria-labelledby="calcTitle">
<div class="calc-modal" role="document">
<div class="calc-header">
<div>
<div id="calcTitle" class="calc-title">Calculator</div>
<div class="calc-sub">Esc to close • Enter to =</div>
</div>
<button id="calcClose" type="button" class="calc-x" aria-label="Close">✕</button>
</div>
<input id="calcDisplay" class="calc-display" inputmode="decimal" autocomplete="off"
placeholder="Type: (3+2)*5 or sin(0.5)" />
<div class="calc-grid" aria-label="Calculator buttons">
<button class="cb" type="button" data-a="7">7</button>
<button class="cb" type="button" data-a="8">8</button>
<button class="cb" type="button" data-a="9">9</button>
<button class="cb op" type="button" data-a="/">÷</button>
<button class="cb fn" type="button" data-a="(">(</button>
<button class="cb fn" type="button" data-a=")">)</button>
<button class="cb" type="button" data-a="4">4</button>
<button class="cb" type="button" data-a="5">5</button>
<button class="cb" type="button" data-a="6">6</button>
<button class="cb op" type="button" data-a="*">×</button>
<button class="cb fn" type="button" data-a="^">^</button>
<button class="cb fn" type="button" data-a="pi">Ï€</button>
<button class="cb" type="button" data-a="1">1</button>
<button class="cb" type="button" data-a="2">2</button>
<button class="cb" type="button" data-a="3">3</button>
<button class="cb op" type="button" data-a="-">−</button>
<button class="cb fn" type="button" data-a="sqrt(">√</button>
<button class="cb fn" type="button" data-a="abs(">abs</button>
<button class="cb" type="button" data-a="0">0</button>
<button class="cb" type="button" data-a=".">.</button>
<button class="cb fn" type="button" data-a="%">%</button>
<button class="cb op" type="button" data-a="+">+</button>
<button class="cb danger" type="button" data-a="back">⌫</button>
<button class="cb danger" type="button" data-a="clear">C</button>
<button class="cb fn" type="button" data-a="sin(">sin</button>
<button class="cb fn" type="button" data-a="cos(">cos</button>
<button class="cb fn" type="button" data-a="tan(">tan</button>
<button class="cb fn" type="button" data-a="ln(">ln</button>
<button class="cb fn" type="button" data-a="log10(">log</button>
<button class="cb eq" type="button" data-a="=">=</button>
</div>
<div class="calc-footer">
<button id="calcCopy" type="button" class="calc-copy">Copy result</button>
<span id="calcMsg" class="calc-msg" aria-live="polite"></span>
</div>
</div>
</div>
`;
document.body.appendChild(wrapper);
const fab = document.getElementById("calcFab");
const overlay = document.getElementById("calcOverlay");
const modal = overlay.querySelector(".calc-modal");
const header = overlay.querySelector(".calc-header");
const closeBtn = document.getElementById("calcClose");
const display = document.getElementById("calcDisplay");
const copyBtn = document.getElementById("calcCopy");
const msg = document.getElementById("calcMsg");
const showMsg = (t) => { msg.textContent = t; setTimeout(() => (msg.textContent = ""), 1400); };
function resetToDefaultOpenSpot(){
modal.style.position = "";
modal.style.left = "";
modal.style.top = "";
modal.style.marginTop = "52px";
modal.style.marginRight = "8px";
}
const setOpen = (open) => {
overlay.classList.toggle("is-open", !!open);
if (open) {
resetToDefaultOpenSpot();
setTimeout(() => display.focus(), 0);
}
};
const toggleOpen = () => setOpen(!overlay.classList.contains("is-open"));
function evaluateExpr(input){
let s = (input || "").trim();
if (!s) return "";
s = s.replaceAll("×","*").replaceAll("÷","/").replaceAll("−","-");
if (!/^[0-9+\-*/().,^% \tA-Za-z]+$/.test(s)) {
throw new Error("Invalid characters");
}
s = s
.replace(/\bpi\b/gi, "Math.PI")
.replace(/\be\b/g, "Math.E")
.replace(/\^/g, "**")
.replace(/\babs\s*\(/gi, "Math.abs(")
.replace(/\bsqrt\s*\(/gi, "Math.sqrt(")
.replace(/\bsin\s*\(/gi, "Math.sin(")
.replace(/\bcos\s*\(/gi, "Math.cos(")
.replace(/\btan\s*\(/gi, "Math.tan(")
.replace(/\bln\s*\(/gi, "Math.log(")
.replace(/\blog10\s*\(/gi, "Math.log10(");
s = s.replace(/(\d+(?:\.\d+)?)\s*%/g, "($1/100)");
const f = new Function(`"use strict"; return (${s});`);
const result = f();
if (typeof result !== "number" || !isFinite(result)) throw new Error("Bad result");
return result;
}
function doEquals(){
try{
const r = evaluateExpr(display.value);
if (r === "") return;
display.value = String(r);
showMsg("OK");
} catch (e){
showMsg("Error");
}
}
fab.addEventListener("click", toggleOpen);
closeBtn.addEventListener("click", () => setOpen(false));
document.addEventListener("keydown", (e) => {
if (!overlay.classList.contains("is-open")) return;
if (e.key === "Escape") setOpen(false);
if (e.key === "Enter") { e.preventDefault(); doEquals(); }
});
document.querySelectorAll(".cb").forEach(btn => {
btn.addEventListener("click", () => {
const a = btn.getAttribute("data-a");
if (a === "clear") { display.value = ""; display.focus(); return; }
if (a === "back") { display.value = display.value.slice(0, -1); display.focus(); return; }
if (a === "=") { doEquals(); return; }
if (a === "pi") { display.value += "pi"; display.focus(); return; }
display.value += a;
display.focus();
});
});
copyBtn.addEventListener("click", async () => {
try{
await navigator.clipboard.writeText(display.value || "");
showMsg("Copied");
} catch(e){
showMsg("Copy failed");
}
});
/* --- Dragging: only by header, and starts from the same open spot --- */
let dragging = false;
let startX = 0;
let startY = 0;
let originLeft = 0;
let originTop = 0;
function startDrag(clientX, clientY){
const rect = modal.getBoundingClientRect();
modal.style.position = "fixed";
modal.style.left = rect.left + "px";
modal.style.top = rect.top + "px";
modal.style.marginTop = "0";
modal.style.marginRight = "0";
startX = clientX;
startY = clientY;
originLeft = rect.left;
originTop = rect.top;
dragging = true;
}
header.addEventListener("mousedown", (e) => {
if (e.target === closeBtn) return;
e.preventDefault();
startDrag(e.clientX, e.clientY);
});
document.addEventListener("mousemove", (e) => {
if (!dragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
modal.style.left = (originLeft + dx) + "px";
modal.style.top = (originTop + dy) + "px";
});
document.addEventListener("mouseup", () => {
dragging = false;
});
header.addEventListener("touchstart", (e) => {
if (e.target === closeBtn) return;
const t = e.touches[0];
if (!t) return;
startDrag(t.clientX, t.clientY);
}, {passive: true});
document.addEventListener("touchmove", (e) => {
if (!dragging) return;
const t = e.touches[0];
if (!t) return;
const dx = t.clientX - startX;
const dy = t.clientY - startY;
modal.style.left = (originLeft + dx) + "px";
modal.style.top = (originTop + dy) + "px";
}, {passive: true});
document.addEventListener("touchend", () => {
dragging = false;
});
setOpen(false);
});