Skip to content

Commit

Permalink
labsite: Rework JS/CSS for mobile 3 col layout
Browse files Browse the repository at this point in the history
Rework JS/CSS for mobile 3 col layout by adding a notes button in the
top right corner on labs only (not playsite).

Fix bug for
	#editor=none&content=...
	#editor=none&source=....

where the display on playsite and labsite were broken for URL fragment
parameter hiding the editor.

Update mobile primary button for Code/Run/Stop to have label "Notes"
when on output screen and there is only Notes and no Editor visible,
e.g. the very first #welcome screen.

Remove editor and notes for lab#bounce-show for testing purposes.

This commit has been tested and should be tested hand by hand in anger
on mobile or with browser dev tools active and small mobile view, where
only one column (notes/editor/output) are displayed.
  • Loading branch information
juliaogris committed Sep 8, 2024
1 parent a1dc3ce commit fcd2547
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 48 deletions.
5 changes: 5 additions & 0 deletions frontend/css/icons.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
SVG source files are located at /img/icon/*.svg.
Encoding tool: https://yoksel.github.io/url-encoder/
*/
.icon-book {
mask: url("data:image/svg+xml,%3Csvg fill='currentColor' stroke='none' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M0 3.75A.75.75 0 0 1 .75 3h7.497c1.566 0 2.945.8 3.751 2.014A4.495 4.495 0 0 1 15.75 3h7.5a.75.75 0 0 1 .75.75v15.063a.752.752 0 0 1-.755.75l-7.682-.052a3 3 0 0 0-2.142.878l-.89.891a.75.75 0 0 1-1.061 0l-.902-.901a2.996 2.996 0 0 0-2.121-.879H.75a.75.75 0 0 1-.75-.75Zm12.75 15.232a4.503 4.503 0 0 1 2.823-.971l6.927.047V4.5h-6.75a3 3 0 0 0-3 3ZM11.247 7.497a3 3 0 0 0-3-2.997H1.5V18h6.947c1.018 0 2.006.346 2.803.98Z'%3E%3C/path%3E%3C/svg%3E");
}

.icon-close {
mask: url("data:image/svg+xml,%3Csvg fill='none' stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg stroke-linecap='round' stroke-width='2'%3E%3Cpath d='M4,4 L20,20' /%3E%3Cpath d='M4,20 L20,4' /%3E%3C/g%3E%3C/svg%3E%0A");
}
Expand Down Expand Up @@ -43,6 +47,7 @@
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 142.5 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cg stroke-linecap='round' stroke-width='25' fill='none'%3E%3Cpath d='M12.5,12.5 h0' stroke='hsl(49deg 100%25 50%25) ' /%3E%3Cpath d='M50,12.5 h80' stroke='hsl(336deg 78%25 64%25)' /%3E%3Cpath d='M12.5,50 h0' stroke='hsl(7deg 66%25 56%25)' /%3E%3Cpath d='M50,50 h42.5' stroke='hsl(201deg 100%25 63%25)' /%3E%3Cpath d='M12.5,87.5 h0' stroke='hsl(150deg 50%25 47%25) ' /%3E%3Cpath d='M50,87.5 h72.5' stroke='hsl(234deg 48%25 51%25)' /%3E%3C/g%3E%3C/svg%3E");
}

.icon-book,
.icon-chevron,
.icon-close,
.icon-copy,
Expand Down
42 changes: 39 additions & 3 deletions frontend/lab/css/overrides.css
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,16 @@ a.youtube {
}
}

#hamburger div {
width: 1.5rem;
height: 1.5rem;
color: var(--color);
}

#show-notes[disabled] {
cursor: none;
color: var(--color-dimmed);
}
/* --- Notes responsive tweaks ---------------------------------- */
@media (hover: hover) {
.notes summary,
Expand All @@ -217,10 +227,36 @@ a.youtube {

@media (max-width: 767px) {
.notes {
display: none;
padding-top: 16px;
width: var(--editor-width);
&:has(+ .editor-wrap.hidden) {
display: block;
}
/* 3 column layout */
.main:not(:has(> .hidden)) {
width: 300vw;
&.view-notes {
translate: 0;
}
&.view-code {
translate: -100vw;
}
&.view-output {
translate: -200vw;
}
}
/* 2 column layout */
.main:has(> .hidden) {
&.view-notes,
&.view-code {
translate: 0;
}
&.view-output {
translate: -100vw;
}
}
/* 1 column layout */
.main:has(.notes.hidden, .editorWrap.hidden) {
&.view-output {
translate: 0;
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions frontend/lab/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@
<body>
<header class="topnav">
<div class="left">
<button id="hamburger" class="icon-hamburger"></button>
<button id="hamburger">
<div class="mobile logo-small"></div>
<div class="desktop icon-hamburger"></div>
</button>
<a href="/" class="desktop">
<img alt="Evy logo" class="logo" />
</a>
Expand All @@ -62,16 +65,16 @@
</button>
</div>
<div class="right">
<button class="mobile icon-book" id="show-notes" disabled></button>
<button class="desktop share" id="share">
<div class="icon-share"></div>
<span>Share</span>
</button>
<a href="/" class="mobile logo-small"></a>
</div>
</header>

<div class="max-width-wrapper">
<main class="main">
<main class="main view-notes">
<div class="notes" id="notes">
<h1>👋 Welcome!</h1>
<p>Programming is for everyone!</p>
Expand Down
2 changes: 1 addition & 1 deletion frontend/lab/samples/samples.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
{
"id": "bounce-show",
"title": "🏓📺 Bouncing ball",
"notes": true,
"notes": false,
"unlisted": true,
"editor": "none"
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/play/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ button.share > div {
}

@media (max-width: 767px) {
.main.view-output {
.main.view-output:not(:has(.editor-wrap.hidden)) {
translate: -100vw;
}
.main {
Expand Down
2 changes: 1 addition & 1 deletion frontend/play/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
</header>

<div class="max-width-wrapper">
<main class="main">
<main class="main view-code">
<div class="editor-wrap noscrollbar">
<div class="editor language-evy" style="padding-left: calc(2ch + 1.5rem)">
<!-- These editor sample contents are replaced by JS, once evy toolchain and editor are ready. -->
Expand Down
135 changes: 96 additions & 39 deletions frontend/play/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async function initWasm() {
const runButtonMob = document.querySelector("#run-mobile")
runButton.onclick = handleRun
runButton.classList.remove("loading")
runButtonMob.onclick = handleMobRun
runButtonMob.onclick = handlePrimaryClick
runButtonMob.classList.remove("loading")
}

Expand Down Expand Up @@ -232,28 +232,52 @@ async function handleRun() {
stopped ? start() : stop()
}

// handleMobRun handles three states for mobile devices:
// run -> stop -> code
async function handleMobRun() {
// handlePrimaryClick handles view states (mobile, code, output) on mobile.
async function handlePrimaryClick() {
// single column layout: run <-> stop
if (editorHidden && notesHidden) {
handleRun()
return
}
if (onCodeScreen()) {
const view = getView()
const showNotesBtn = document.querySelector("#show-notes")
if (view == "view-notes" && !editorHidden) {
await slide("view-code")
if (showNotesBtn) showNotesBtn.disabled = false
return
}
if (view === "view-notes" || view === "view-code") {
// we need to wait for the slide transition to finish otherwise
// el.focus() in jsRead() messes up the layout
await slide()
await slide("view-output")
if (showNotesBtn) showNotesBtn.disabled = false
start()
return
}
// on output screen
if (stopped) {
const runButtonMob = document.querySelector("#run-mobile")
runButtonMob.innerText = "Run"
slide()
// on output view, running
if (!stopped) {
stop()
return
}
stop()
// on output view, stopped
document.querySelector("#run-mobile").innerText = "Run"
const nextScreen = editorHidden ? "view-notes" : "view-code"
slide(nextScreen)
}

function getView() {
const cl = document.querySelector("main.main").classList
if (cl.contains("view-output")) return "view-output"
if (cl.contains("view-code")) return "view-code"
if (cl.contains("view-notes")) return "view-notes"
}

function showNotes() {
if (notesHidden) return
if (!stopped) stop()
slide("view-notes")
const showNotesBtn = document.querySelector("#show-notes")
if (showNotesBtn) showNotesBtn.disabled = true
}

// start calls evy wasm/go main(). It parses, formats and evaluates evy
Expand Down Expand Up @@ -301,36 +325,48 @@ function afterStop() {
wasmInst = undefined

const runButton = document.querySelector("#run")
const runButtonMob = document.querySelector("#run-mobile")
runButton.classList.remove("running")
runButton.innerText = "Run"
runButtonMob.classList.remove("running")
runButtonMob.innerText = onCodeScreen() ? "Run" : "Code"
updateMobilePrimaryButton()

const readEl = document.querySelector("#read")
document.activeElement === readEl && readEl.blur()
}

function onCodeScreen() {
return !document.querySelector("main").classList.contains("view-output")
function updateMobilePrimaryButton() {
const classList = document.querySelector("#run-mobile")
classList.classList.remove("running")
classList.innerText = mobilePrimaryButtonText()
}

function mobilePrimaryButtonText() {
if (editorHidden && notesHidden) return "Run"
const view = getView()
if (view === "view-notes" && !editorHidden) return "Code"
if (view === "view-notes" && editorHidden) return "Run"
if (view === "view-code") return "Run"
// output screen
if (editorHidden) return "Notes"
return "Code"
}

async function slide() {
const el = document.querySelector("main")
async function slide(view) {
const el = document.querySelector("main.main")
const cl = el.classList
return new Promise((resolve) => {
el.ontransitionend = () => setTimeout(resolve, 100)
el.onanimationend = () => cl.remove("animate")
cl.add("animate")
onCodeScreen() ? cl.add("view-output") : cl.remove("view-output")
setView(view)
})
}

async function stopAndSlide() {
if (!onCodeScreen()) {
await slide()
}
stop()
function setView(view) {
const cl = document.querySelector("main.main").classList
const viewClasses = ["view-code", "view-notes", "view-output"]
viewClasses.map((c) => cl.remove(c))
cl.add(view)
updateMobilePrimaryButton()
}

function clearOutput() {
Expand All @@ -346,10 +382,13 @@ async function initUI() {
document.addEventListener("keydown", ctrlEnterListener)
window.addEventListener("hashchange", handleHashChange)
document.querySelector("#modal-close").onclick = hideModal
document.querySelector("#share").onclick = share
document.querySelector("#sidebar-about").onclick = showAbout
document.querySelector("#sidebar-share").onclick = share
document.querySelector("#sidebar-icon-share").onclick = share
const shareBtn = document.querySelector("#share")
if (shareBtn) shareBtn.onclick = share
const showNotesBtn = document.querySelector("#show-notes")
if (showNotesBtn) showNotesBtn.onclick = showNotes
await fetchSamples()
await handleHashChangeNoFormat() // Evy wasm for formatting might not be ready yet
initModal()
Expand Down Expand Up @@ -409,15 +448,11 @@ async function handleHashChange() {

async function handleHashChangeNoFormat() {
hideModal()
await stopAndSlide() // go to code screen for new code
await stop() // go to code screen for new code
let opts = parseHash()
if (!opts.source && !opts.sample && !opts.content) {
if (hasEditorSession()) {
currentSample = "<UNSET>"
!editor && initEditor()
editor.loadSession()
loadNotes()
toggleEditorVisibility(true)
if (sessionStorage.getItem("evy-editor") !== null) {
loadSession()
return
}
const sample = "welcome"
Expand All @@ -428,9 +463,35 @@ async function handleHashChangeNoFormat() {
updateNotes(notes)
updateEditor(source, opts)
updateSampleTitle()
resetView()
clearOutput()
}

function loadSession() {
currentSample = "<UNSET>"
!editor && initEditor()
editor.loadSession()
loadNotes()
toggleEditorVisibility(true)
resetView() // on mobile go to Notes view or Code view if no notes available.
}

// resetView resets the view based on content availability, on mobile only.
// If notes are available, navigates to the notes view.
// If no notes but the editor is present, switches to the code view.
// Otherwise, defaults to the output view
function resetView() {
const showNotesBtn = document.querySelector("#show-notes")
if (showNotesBtn) showNotesBtn.disabled = true
if (!notesHidden) {
setView("view-notes")
} else if (!editorHidden) {
setView("view-code")
} else {
setView("view-output")
}
}

function updateNotes(notes) {
hasNotes(notes) ? addNotes(notes) : removeNotes()
}
Expand Down Expand Up @@ -918,10 +979,6 @@ function initEditor() {
document.querySelector(".editor-wrap").classList.remove("noscrollbar")
}

function hasEditorSession() {
return !!sessionStorage.getItem("evy-editor")
}

// --- eventHandlers, evy `on` -----------------------------------------

// registerEventHandler is exported to evy go/wasm
Expand All @@ -937,7 +994,7 @@ function registerEventHandler(ptr, len) {
c.onpointermove = (e) => exp.onMove(logicalX(e), logicalY(e))
c.onmouseleave = (e) => exp.onMove(...leaveXY(e)) // pointer can leave in middle of canvas
} else if (s === "key") {
unfocusRunBotton()
unfocusRunButton()
document.addEventListener("keydown", keydownListener)
} else if (s === "input") {
addInputHandlers()
Expand All @@ -948,7 +1005,7 @@ function registerEventHandler(ptr, len) {
}
}

function unfocusRunBotton() {
function unfocusRunButton() {
const runButton = document.querySelector("#run")
const runButtonMob = document.querySelector("#run-mobile")
document.activeElement === runButton && runButton.blur()
Expand Down

0 comments on commit fcd2547

Please sign in to comment.