Made an application for supporting sustainable local businesses in San Pancho.
Never really got completed, but it has some useful Svelte components for maps that we can reuse.
http://greenspots.dctrl.space
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
359 lines
10 KiB
359 lines
10 KiB
docReady(() => { |
|
if (!EVALEX_TRUSTED) { |
|
initPinBox(); |
|
} |
|
// if we are in console mode, show the console. |
|
if (CONSOLE_MODE && EVALEX) { |
|
createInteractiveConsole(); |
|
} |
|
|
|
const frames = document.querySelectorAll("div.traceback div.frame"); |
|
if (EVALEX) { |
|
addConsoleIconToFrames(frames); |
|
} |
|
addEventListenersToElements(document.querySelectorAll("div.detail"), "click", () => |
|
document.querySelector("div.traceback").scrollIntoView(false) |
|
); |
|
addToggleFrameTraceback(frames); |
|
addToggleTraceTypesOnClick(document.querySelectorAll("h2.traceback")); |
|
addInfoPrompt(document.querySelectorAll("span.nojavascript")); |
|
wrapPlainTraceback(); |
|
}); |
|
|
|
function addToggleFrameTraceback(frames) { |
|
frames.forEach((frame) => { |
|
frame.addEventListener("click", () => { |
|
frame.getElementsByTagName("pre")[0].parentElement.classList.toggle("expanded"); |
|
}); |
|
}) |
|
} |
|
|
|
|
|
function wrapPlainTraceback() { |
|
const plainTraceback = document.querySelector("div.plain textarea"); |
|
const wrapper = document.createElement("pre"); |
|
const textNode = document.createTextNode(plainTraceback.textContent); |
|
wrapper.appendChild(textNode); |
|
plainTraceback.replaceWith(wrapper); |
|
} |
|
|
|
function initPinBox() { |
|
document.querySelector(".pin-prompt form").addEventListener( |
|
"submit", |
|
function (event) { |
|
event.preventDefault(); |
|
const pin = encodeURIComponent(this.pin.value); |
|
const encodedSecret = encodeURIComponent(SECRET); |
|
const btn = this.btn; |
|
btn.disabled = true; |
|
|
|
fetch( |
|
`${document.location.pathname}?__debugger__=yes&cmd=pinauth&pin=${pin}&s=${encodedSecret}` |
|
) |
|
.then((res) => res.json()) |
|
.then(({auth, exhausted}) => { |
|
if (auth) { |
|
EVALEX_TRUSTED = true; |
|
fadeOut(document.getElementsByClassName("pin-prompt")[0]); |
|
} else { |
|
alert( |
|
`Error: ${ |
|
exhausted |
|
? "too many attempts. Restart server to retry." |
|
: "incorrect pin" |
|
}` |
|
); |
|
} |
|
}) |
|
.catch((err) => { |
|
alert("Error: Could not verify PIN. Network error?"); |
|
console.error(err); |
|
}) |
|
.finally(() => (btn.disabled = false)); |
|
}, |
|
false |
|
); |
|
} |
|
|
|
function promptForPin() { |
|
if (!EVALEX_TRUSTED) { |
|
const encodedSecret = encodeURIComponent(SECRET); |
|
fetch( |
|
`${document.location.pathname}?__debugger__=yes&cmd=printpin&s=${encodedSecret}` |
|
); |
|
const pinPrompt = document.getElementsByClassName("pin-prompt")[0]; |
|
fadeIn(pinPrompt); |
|
document.querySelector('.pin-prompt input[name="pin"]').focus(); |
|
} |
|
} |
|
|
|
/** |
|
* Helper function for shell initialization |
|
*/ |
|
function openShell(consoleNode, target, frameID) { |
|
promptForPin(); |
|
if (consoleNode) { |
|
slideToggle(consoleNode); |
|
return consoleNode; |
|
} |
|
let historyPos = 0; |
|
const history = [""]; |
|
const consoleElement = createConsole(); |
|
const output = createConsoleOutput(); |
|
const form = createConsoleInputForm(); |
|
const command = createConsoleInput(); |
|
|
|
target.parentNode.appendChild(consoleElement); |
|
consoleElement.append(output); |
|
consoleElement.append(form); |
|
form.append(command); |
|
command.focus(); |
|
slideToggle(consoleElement); |
|
|
|
form.addEventListener("submit", (e) => { |
|
handleConsoleSubmit(e, command, frameID).then((consoleOutput) => { |
|
output.append(consoleOutput); |
|
command.focus(); |
|
consoleElement.scrollTo(0, consoleElement.scrollHeight); |
|
const old = history.pop(); |
|
history.push(command.value); |
|
if (typeof old !== "undefined") { |
|
history.push(old); |
|
} |
|
historyPos = history.length - 1; |
|
command.value = ""; |
|
}); |
|
}); |
|
|
|
command.addEventListener("keydown", (e) => { |
|
if (e.key === "l" && e.ctrlKey) { |
|
output.innerText = "--- screen cleared ---"; |
|
} else if (e.key === "ArrowUp" || e.key === "ArrowDown") { |
|
// Handle up arrow and down arrow. |
|
if (e.key === "ArrowUp" && historyPos > 0) { |
|
e.preventDefault(); |
|
historyPos--; |
|
} else if (e.key === "ArrowDown" && historyPos < history.length - 1) { |
|
historyPos++; |
|
} |
|
command.value = history[historyPos]; |
|
} |
|
return false; |
|
}); |
|
|
|
return consoleElement; |
|
} |
|
|
|
function addEventListenersToElements(elements, event, listener) { |
|
elements.forEach((el) => el.addEventListener(event, listener)); |
|
} |
|
|
|
/** |
|
* Add extra info |
|
*/ |
|
function addInfoPrompt(elements) { |
|
for (let i = 0; i < elements.length; i++) { |
|
elements[i].innerHTML = |
|
"<p>To switch between the interactive traceback and the plaintext " + |
|
'one, you can click on the "Traceback" headline. From the text ' + |
|
"traceback you can also create a paste of it. " + |
|
(!EVALEX |
|
? "" |
|
: "For code execution mouse-over the frame you want to debug and " + |
|
"click on the console icon on the right side." + |
|
"<p>You can execute arbitrary Python code in the stack frames and " + |
|
"there are some extra helpers available for introspection:" + |
|
"<ul><li><code>dump()</code> shows all variables in the frame" + |
|
"<li><code>dump(obj)</code> dumps all that's known about the object</ul>"); |
|
elements[i].classList.remove("nojavascript"); |
|
} |
|
} |
|
|
|
function addConsoleIconToFrames(frames) { |
|
for (let i = 0; i < frames.length; i++) { |
|
let consoleNode = null; |
|
const target = frames[i]; |
|
const frameID = frames[i].id.substring(6); |
|
|
|
for (let j = 0; j < target.getElementsByTagName("pre").length; j++) { |
|
const img = createIconForConsole(); |
|
img.addEventListener("click", (e) => { |
|
e.stopPropagation(); |
|
consoleNode = openShell(consoleNode, target, frameID); |
|
return false; |
|
}); |
|
target.getElementsByTagName("pre")[j].append(img); |
|
} |
|
} |
|
} |
|
|
|
function slideToggle(target) { |
|
target.classList.toggle("active"); |
|
} |
|
|
|
/** |
|
* toggle traceback types on click. |
|
*/ |
|
function addToggleTraceTypesOnClick(elements) { |
|
for (let i = 0; i < elements.length; i++) { |
|
elements[i].addEventListener("click", () => { |
|
document.querySelector("div.traceback").classList.toggle("hidden"); |
|
document.querySelector("div.plain").classList.toggle("hidden"); |
|
}); |
|
elements[i].style.cursor = "pointer"; |
|
document.querySelector("div.plain").classList.toggle("hidden"); |
|
} |
|
} |
|
|
|
function createConsole() { |
|
const consoleNode = document.createElement("pre"); |
|
consoleNode.classList.add("console"); |
|
consoleNode.classList.add("active"); |
|
return consoleNode; |
|
} |
|
|
|
function createConsoleOutput() { |
|
const output = document.createElement("div"); |
|
output.classList.add("output"); |
|
output.innerHTML = "[console ready]"; |
|
return output; |
|
} |
|
|
|
function createConsoleInputForm() { |
|
const form = document.createElement("form"); |
|
form.innerHTML = ">>> "; |
|
return form; |
|
} |
|
|
|
function createConsoleInput() { |
|
const command = document.createElement("input"); |
|
command.type = "text"; |
|
command.setAttribute("autocomplete", "off"); |
|
command.setAttribute("spellcheck", false); |
|
command.setAttribute("autocapitalize", "off"); |
|
command.setAttribute("autocorrect", "off"); |
|
return command; |
|
} |
|
|
|
function createIconForConsole() { |
|
const img = document.createElement("img"); |
|
img.setAttribute("src", "?__debugger__=yes&cmd=resource&f=console.png"); |
|
img.setAttribute("title", "Open an interactive python shell in this frame"); |
|
return img; |
|
} |
|
|
|
function createExpansionButtonForConsole() { |
|
const expansionButton = document.createElement("a"); |
|
expansionButton.setAttribute("href", "#"); |
|
expansionButton.setAttribute("class", "toggle"); |
|
expansionButton.innerHTML = " "; |
|
return expansionButton; |
|
} |
|
|
|
function createInteractiveConsole() { |
|
const target = document.querySelector("div.console div.inner"); |
|
while (target.firstChild) { |
|
target.removeChild(target.firstChild); |
|
} |
|
openShell(null, target, 0); |
|
} |
|
|
|
function handleConsoleSubmit(e, command, frameID) { |
|
// Prevent page from refreshing. |
|
e.preventDefault(); |
|
|
|
return new Promise((resolve) => { |
|
// Get input command. |
|
const cmd = command.value; |
|
|
|
// Setup GET request. |
|
const urlPath = ""; |
|
const params = { |
|
__debugger__: "yes", |
|
cmd: cmd, |
|
frm: frameID, |
|
s: SECRET, |
|
}; |
|
const paramString = Object.keys(params) |
|
.map((key) => { |
|
return "&" + encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); |
|
}) |
|
.join(""); |
|
|
|
fetch(urlPath + "?" + paramString) |
|
.then((res) => { |
|
return res.text(); |
|
}) |
|
.then((data) => { |
|
const tmp = document.createElement("div"); |
|
tmp.innerHTML = data; |
|
resolve(tmp); |
|
|
|
// Handle expandable span for long list outputs. |
|
// Example to test: list(range(13)) |
|
let wrapperAdded = false; |
|
const wrapperSpan = document.createElement("span"); |
|
const expansionButton = createExpansionButtonForConsole(); |
|
|
|
tmp.querySelectorAll("span.extended").forEach((spanToWrap) => { |
|
const parentDiv = spanToWrap.parentNode; |
|
if (!wrapperAdded) { |
|
parentDiv.insertBefore(wrapperSpan, spanToWrap); |
|
wrapperAdded = true; |
|
} |
|
parentDiv.removeChild(spanToWrap); |
|
wrapperSpan.append(spanToWrap); |
|
spanToWrap.hidden = true; |
|
|
|
expansionButton.addEventListener("click", () => { |
|
spanToWrap.hidden = !spanToWrap.hidden; |
|
expansionButton.classList.toggle("open"); |
|
return false; |
|
}); |
|
}); |
|
|
|
// Add expansion button at end of wrapper. |
|
if (wrapperAdded) { |
|
wrapperSpan.append(expansionButton); |
|
} |
|
}) |
|
.catch((err) => { |
|
console.error(err); |
|
}); |
|
return false; |
|
}); |
|
} |
|
|
|
function fadeOut(element) { |
|
element.style.opacity = 1; |
|
|
|
(function fade() { |
|
element.style.opacity -= 0.1; |
|
if (element.style.opacity < 0) { |
|
element.style.display = "none"; |
|
} else { |
|
requestAnimationFrame(fade); |
|
} |
|
})(); |
|
} |
|
|
|
function fadeIn(element, display) { |
|
element.style.opacity = 0; |
|
element.style.display = display || "block"; |
|
|
|
(function fade() { |
|
let val = parseFloat(element.style.opacity) + 0.1; |
|
if (val <= 1) { |
|
element.style.opacity = val; |
|
requestAnimationFrame(fade); |
|
} |
|
})(); |
|
} |
|
|
|
function docReady(fn) { |
|
if (document.readyState === "complete" || document.readyState === "interactive") { |
|
setTimeout(fn, 1); |
|
} else { |
|
document.addEventListener("DOMContentLoaded", fn); |
|
} |
|
}
|
|
|