229 lines
7.6 KiB
JavaScript
229 lines
7.6 KiB
JavaScript
"use strict";
|
||
(() => {
|
||
if (typeof document === "undefined") return;
|
||
const $ = (s) => document.querySelector(s);
|
||
|
||
const addr = $("#addr");
|
||
const display = $("#display");
|
||
const exprEcho = $("#exprEcho");
|
||
const errorline = $("#errorline");
|
||
const suggestions = $("#suggestions");
|
||
const historyList = $("#historyList");
|
||
const statusVars = $("#statusVars");
|
||
const statusText = $("#statusText");
|
||
const library = $("#library");
|
||
const helpVeil = $("#helpVeil");
|
||
|
||
let ans = 0;
|
||
const history = [];
|
||
let navIdx = -1;
|
||
|
||
/* ---------- рендер ---------- */
|
||
|
||
function renderHistory() {
|
||
historyList.innerHTML = "";
|
||
if (!history.length) {
|
||
const li = document.createElement("li");
|
||
li.className = "history-empty";
|
||
li.textContent = "История пуста";
|
||
historyList.appendChild(li);
|
||
return;
|
||
}
|
||
for (let i = history.length - 1; i >= 0; i--) {
|
||
const h = history[i];
|
||
const li = document.createElement("li");
|
||
li.className = "history-item" + (h.ok ? "" : " bad");
|
||
const main = document.createElement("span");
|
||
main.className = "h-main";
|
||
main.textContent = h.expr;
|
||
const res = document.createElement("span");
|
||
res.className = "h-res";
|
||
res.textContent = h.ok ? "= " + h.result : "⚠";
|
||
li.append(main, res);
|
||
if (h.ok) li.addEventListener("click", () => { addr.value = h.expr; addr.focus(); });
|
||
historyList.appendChild(li);
|
||
}
|
||
}
|
||
|
||
function okResult(expr, value) {
|
||
exprEcho.textContent = expr + " =";
|
||
display.textContent = value;
|
||
display.classList.remove("is-err");
|
||
errorline.hidden = true;
|
||
statusVars.textContent = "ans = " + value;
|
||
statusText.textContent = "Готово";
|
||
}
|
||
|
||
function errResult(expr, msg) {
|
||
exprEcho.textContent = expr;
|
||
display.classList.add("is-err");
|
||
errorline.textContent = "Ошибка: " + msg;
|
||
errorline.hidden = false;
|
||
statusText.textContent = "Ошибка вычисления";
|
||
}
|
||
|
||
function resetSession() {
|
||
ans = 0;
|
||
history.length = 0;
|
||
navIdx = -1;
|
||
renderHistory();
|
||
exprEcho.innerHTML = " ";
|
||
display.textContent = "0";
|
||
display.classList.remove("is-err");
|
||
errorline.hidden = true;
|
||
statusVars.textContent = "ans = 0";
|
||
statusText.textContent = "Сессия сброшена (ans = 0, история пуста)";
|
||
}
|
||
|
||
/* ---------- вычисление ---------- */
|
||
|
||
function evaluateNow() {
|
||
const raw = addr.value.trim();
|
||
if (!raw) { addr.focus(); return; }
|
||
const low = raw.toLowerCase();
|
||
if (low === "exit" || low === "выход") {
|
||
statusText.textContent = "«Выход» — просто закройте вкладку 🙂";
|
||
addr.value = "";
|
||
return;
|
||
}
|
||
if (low === "reset") { resetSession(); addr.value = ""; return; }
|
||
try {
|
||
const v = Calc.evaluate(raw, { ans });
|
||
ans = v;
|
||
const text = Calc.format(v);
|
||
history.push({ expr: raw, result: text, ok: true });
|
||
okResult(raw, text);
|
||
} catch (e) {
|
||
history.push({ expr: raw, result: null, ok: false });
|
||
errResult(raw, e.message || String(e));
|
||
}
|
||
renderHistory();
|
||
addr.value = "";
|
||
navIdx = -1;
|
||
hideSuggestions();
|
||
addr.focus();
|
||
}
|
||
|
||
/* ---------- awesome bar ---------- */
|
||
|
||
function showSuggestions() {
|
||
const q = addr.value.trim().toLowerCase();
|
||
if (!q) { hideSuggestions(); return; }
|
||
const items = history
|
||
.filter((h) => h.expr.toLowerCase().includes(q))
|
||
.slice(-7)
|
||
.reverse();
|
||
if (!items.length) { hideSuggestions(); return; }
|
||
suggestions.innerHTML = "";
|
||
items.forEach((h) => {
|
||
const row = document.createElement("div");
|
||
row.className = "sugg";
|
||
const icon = document.createElement("span");
|
||
icon.className = "sugg-icon";
|
||
icon.textContent = h.ok ? "=" : "⚠";
|
||
const expr = document.createElement("span");
|
||
expr.className = "sugg-expr";
|
||
expr.textContent = h.expr;
|
||
const res = document.createElement("span");
|
||
res.className = "sugg-res";
|
||
res.textContent = h.ok ? "= " + h.result : "ошибка";
|
||
row.append(icon, expr, res);
|
||
row.addEventListener("mousedown", (ev) => {
|
||
ev.preventDefault();
|
||
addr.value = h.expr;
|
||
hideSuggestions();
|
||
addr.focus();
|
||
});
|
||
suggestions.appendChild(row);
|
||
});
|
||
suggestions.hidden = false;
|
||
}
|
||
|
||
function hideSuggestions() { suggestions.hidden = true; }
|
||
|
||
/* ---------- вставка с клавиатуры ---------- */
|
||
|
||
function insert(text) {
|
||
if (text === "back") {
|
||
const s = addr.selectionStart, e = addr.selectionEnd;
|
||
if (s !== e) {
|
||
addr.value = addr.value.slice(0, s) + addr.value.slice(e);
|
||
addr.selectionStart = addr.selectionEnd = s;
|
||
} else if (s > 0) {
|
||
addr.value = addr.value.slice(0, s - 1) + addr.value.slice(s);
|
||
addr.selectionStart = addr.selectionEnd = s - 1;
|
||
}
|
||
addr.focus();
|
||
showSuggestions();
|
||
return;
|
||
}
|
||
if (text === "C") { addr.value = ""; addr.focus(); hideSuggestions(); return; }
|
||
if (text === "CE") { resetSession(); return; }
|
||
const s = addr.selectionStart == null ? addr.value.length : addr.selectionStart;
|
||
const e = addr.selectionEnd == null ? s : addr.selectionEnd;
|
||
addr.value = addr.value.slice(0, s) + text + addr.value.slice(e);
|
||
addr.selectionStart = addr.selectionEnd = s + text.length;
|
||
addr.focus();
|
||
}
|
||
|
||
/* ---------- привязки ---------- */
|
||
|
||
document.querySelectorAll("[data-insert]").forEach((b) =>
|
||
b.addEventListener("click", () => insert(b.dataset.insert)));
|
||
document.querySelectorAll("[data-eval]").forEach((b) =>
|
||
b.addEventListener("click", evaluateNow));
|
||
|
||
$("#evalForm").addEventListener("submit", (ev) => { ev.preventDefault(); evaluateNow(); });
|
||
addr.addEventListener("input", showSuggestions);
|
||
addr.addEventListener("blur", () => setTimeout(hideSuggestions, 120));
|
||
|
||
addr.addEventListener("keydown", (ev) => {
|
||
if (ev.key === "Escape") { hideSuggestions(); return; }
|
||
const okList = history.filter((h) => h.ok);
|
||
if (ev.key === "ArrowUp" && okList.length) {
|
||
ev.preventDefault();
|
||
navIdx = Math.min(navIdx + 1, okList.length - 1);
|
||
addr.value = okList[okList.length - 1 - navIdx].expr;
|
||
return;
|
||
}
|
||
if (ev.key === "ArrowDown" && okList.length && navIdx >= 0) {
|
||
ev.preventDefault();
|
||
navIdx = Math.max(navIdx - 1, -1);
|
||
addr.value = navIdx < 0 ? "" : okList[okList.length - 1 - navIdx].expr;
|
||
return;
|
||
}
|
||
});
|
||
|
||
$("#btnReset").addEventListener("click", resetSession);
|
||
$("#btnHome").addEventListener("click", () => {
|
||
addr.value = "sqrt(3**2 + 4**2)";
|
||
evaluateNow();
|
||
});
|
||
|
||
/* ---------- библиотека (сайдбар) ---------- */
|
||
|
||
$("#btnLibrary").addEventListener("click", () => {
|
||
library.classList.toggle("hidden");
|
||
$("#btnLibrary").classList.toggle("active");
|
||
});
|
||
document.querySelectorAll(".lib-tab").forEach((tab) =>
|
||
tab.addEventListener("click", () => {
|
||
document.querySelectorAll(".lib-tab").forEach((t) => t.classList.remove("active"));
|
||
tab.classList.add("active");
|
||
$("#libHistory").hidden = tab.dataset.libtab !== "history";
|
||
$("#libFuncs").hidden = tab.dataset.libtab !== "funcs";
|
||
}));
|
||
|
||
/* ---------- справка ---------- */
|
||
|
||
$("#btnHelp").addEventListener("click", () => { helpVeil.hidden = false; });
|
||
$("#helpClose").addEventListener("click", () => { helpVeil.hidden = true; });
|
||
helpVeil.addEventListener("click", (ev) => { if (ev.target === helpVeil) helpVeil.hidden = true; });
|
||
document.addEventListener("keydown", (ev) => {
|
||
if (ev.key === "Escape") helpVeil.hidden = true;
|
||
});
|
||
|
||
renderHistory();
|
||
addr.focus();
|
||
})();
|