Skip to content

Commit a0e5551

Browse files
committed
labsite: Rework JS/CSS for mobile 3 col layout
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.
1 parent 5b99ae5 commit a0e5551

File tree

7 files changed

+149
-48
lines changed

7 files changed

+149
-48
lines changed

frontend/css/icons.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
SVG source files are located at /img/icon/*.svg.
44
Encoding tool: https://yoksel.github.io/url-encoder/
55
*/
6+
.icon-book {
7+
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");
8+
}
9+
610
.icon-close {
711
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");
812
}
@@ -43,6 +47,7 @@
4347
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");
4448
}
4549

50+
.icon-book,
4651
.icon-chevron,
4752
.icon-close,
4853
.icon-copy,

frontend/lab/css/overrides.css

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,16 @@ a.youtube {
202202
}
203203
}
204204

205+
#hamburger div {
206+
width: 1.5rem;
207+
height: 1.5rem;
208+
color: var(--color);
209+
}
210+
211+
#show-notes[disabled] {
212+
cursor: none;
213+
color: var(--color-dimmed);
214+
}
205215
/* --- Notes responsive tweaks ---------------------------------- */
206216
@media (hover: hover) {
207217
.notes summary,
@@ -217,10 +227,36 @@ a.youtube {
217227

218228
@media (max-width: 767px) {
219229
.notes {
220-
display: none;
230+
padding-top: 16px;
221231
width: var(--editor-width);
222-
&:has(+ .editor-wrap.hidden) {
223-
display: block;
232+
}
233+
/* 3 column layout */
234+
.main:not(:has(> .hidden)) {
235+
width: 300vw;
236+
&.view-notes {
237+
translate: 0;
238+
}
239+
&.view-code {
240+
translate: -100vw;
241+
}
242+
&.view-output {
243+
translate: -200vw;
244+
}
245+
}
246+
/* 2 column layout */
247+
.main:has(> .hidden) {
248+
&.view-notes,
249+
&.view-code {
250+
translate: 0;
251+
}
252+
&.view-output {
253+
translate: -100vw;
254+
}
255+
}
256+
/* 1 column layout */
257+
.main:has(.notes.hidden, .editorWrap.hidden) {
258+
&.view-output {
259+
translate: 0;
224260
}
225261
}
226262
}

frontend/lab/index.html

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040
<body>
4141
<header class="topnav">
4242
<div class="left">
43-
<button id="hamburger" class="icon-hamburger"></button>
43+
<button id="hamburger">
44+
<div class="mobile logo-small"></div>
45+
<div class="desktop icon-hamburger"></div>
46+
</button>
4447
<a href="/" class="desktop">
4548
<img alt="Evy logo" class="logo" />
4649
</a>
@@ -62,16 +65,16 @@
6265
</button>
6366
</div>
6467
<div class="right">
68+
<button class="mobile icon-book" id="show-notes" disabled></button>
6569
<button class="desktop share" id="share">
6670
<div class="icon-share"></div>
6771
<span>Share</span>
6872
</button>
69-
<a href="/" class="mobile logo-small"></a>
7073
</div>
7174
</header>
7275

7376
<div class="max-width-wrapper">
74-
<main class="main">
77+
<main class="main view-notes">
7578
<div class="notes" id="notes">
7679
<h1>👋 Welcome!</h1>
7780
<p>Programming is for everyone!</p>

frontend/lab/samples/samples.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
{
5353
"id": "bounce-show",
5454
"title": "🏓📺 Bouncing ball",
55-
"notes": true,
55+
"notes": false,
5656
"unlisted": true,
5757
"editor": "none"
5858
}

frontend/play/css/index.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ button.share > div {
256256
}
257257

258258
@media (max-width: 767px) {
259-
.main.view-output {
259+
.main.view-output:not(:has(> .hidden)) {
260260
translate: -100vw;
261261
}
262262
.main {

frontend/play/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
</header>
7171

7272
<div class="max-width-wrapper">
73-
<main class="main">
73+
<main class="main view-code">
7474
<div class="editor-wrap noscrollbar">
7575
<div class="editor language-evy" style="padding-left: calc(2ch + 1.5rem)">
7676
<!-- These editor sample contents are replaced by JS, once evy toolchain and editor are ready. -->

frontend/play/index.js

Lines changed: 96 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ async function initWasm() {
3535
const runButtonMob = document.querySelector("#run-mobile")
3636
runButton.onclick = handleRun
3737
runButton.classList.remove("loading")
38-
runButtonMob.onclick = handleMobRun
38+
runButtonMob.onclick = handlePrimaryClick
3939
runButtonMob.classList.remove("loading")
4040
}
4141

@@ -232,28 +232,52 @@ async function handleRun() {
232232
stopped ? start() : stop()
233233
}
234234

235-
// handleMobRun handles three states for mobile devices:
236-
// run -> stop -> code
237-
async function handleMobRun() {
235+
// handlePrimaryClick handles view states (mobile, code, output) on mobile.
236+
async function handlePrimaryClick() {
237+
// single column layout: run <-> stop
238238
if (editorHidden && notesHidden) {
239239
handleRun()
240240
return
241241
}
242-
if (onCodeScreen()) {
242+
const view = getView()
243+
const showNotesBtn = document.querySelector("#show-notes")
244+
if (view == "view-notes" && !editorHidden) {
245+
await slide("view-code")
246+
if (showNotesBtn) showNotesBtn.disabled = false
247+
return
248+
}
249+
if (view === "view-notes" || view === "view-code") {
243250
// we need to wait for the slide transition to finish otherwise
244251
// el.focus() in jsRead() messes up the layout
245-
await slide()
252+
await slide("view-output")
253+
if (showNotesBtn) showNotesBtn.disabled = false
246254
start()
247255
return
248256
}
249-
// on output screen
250-
if (stopped) {
251-
const runButtonMob = document.querySelector("#run-mobile")
252-
runButtonMob.innerText = "Run"
253-
slide()
257+
// on output view, running
258+
if (!stopped) {
259+
stop()
254260
return
255261
}
256-
stop()
262+
// on output view, stopped
263+
document.querySelector("#run-mobile").innerText = "Run"
264+
const nextScreen = editorHidden ? "view-notes" : "view-code"
265+
slide(nextScreen)
266+
}
267+
268+
function getView() {
269+
const cl = document.querySelector("main.main").classList
270+
if (cl.contains("view-output")) return "view-output"
271+
if (cl.contains("view-code")) return "view-code"
272+
if (cl.contains("view-notes")) return "view-notes"
273+
}
274+
275+
function showNotes() {
276+
if (notesHidden) return
277+
if (!stopped) stop()
278+
slide("view-notes")
279+
const showNotesBtn = document.querySelector("#show-notes")
280+
if (showNotesBtn) showNotesBtn.disabled = true
257281
}
258282

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

303327
const runButton = document.querySelector("#run")
304-
const runButtonMob = document.querySelector("#run-mobile")
305328
runButton.classList.remove("running")
306329
runButton.innerText = "Run"
307-
runButtonMob.classList.remove("running")
308-
runButtonMob.innerText = onCodeScreen() ? "Run" : "Code"
330+
updateMobilePrimaryButton()
309331

310332
const readEl = document.querySelector("#read")
311333
document.activeElement === readEl && readEl.blur()
312334
}
313335

314-
function onCodeScreen() {
315-
return !document.querySelector("main").classList.contains("view-output")
336+
function updateMobilePrimaryButton() {
337+
const classList = document.querySelector("#run-mobile")
338+
classList.classList.remove("running")
339+
classList.innerText = mobilePrimaryButtonText()
340+
}
341+
342+
function mobilePrimaryButtonText() {
343+
if (editorHidden && notesHidden) return "Run"
344+
const view = getView()
345+
if (view === "view-notes" && !editorHidden) return "Code"
346+
if (view === "view-notes" && editorHidden) return "Run"
347+
if (view === "view-code") return "Run"
348+
// output screen
349+
if (editorHidden) return "Notes"
350+
return "Code"
316351
}
317352

318-
async function slide() {
319-
const el = document.querySelector("main")
353+
async function slide(view) {
354+
const el = document.querySelector("main.main")
320355
const cl = el.classList
321356
return new Promise((resolve) => {
322357
el.ontransitionend = () => setTimeout(resolve, 100)
323358
el.onanimationend = () => cl.remove("animate")
324359
cl.add("animate")
325-
onCodeScreen() ? cl.add("view-output") : cl.remove("view-output")
360+
setView(view)
326361
})
327362
}
328363

329-
async function stopAndSlide() {
330-
if (!onCodeScreen()) {
331-
await slide()
332-
}
333-
stop()
364+
function setView(view) {
365+
const cl = document.querySelector("main.main").classList
366+
const viewClasses = ["view-code", "view-notes", "view-output"]
367+
viewClasses.map((c) => cl.remove(c))
368+
cl.add(view)
369+
updateMobilePrimaryButton()
334370
}
335371

336372
function clearOutput() {
@@ -346,10 +382,13 @@ async function initUI() {
346382
document.addEventListener("keydown", ctrlEnterListener)
347383
window.addEventListener("hashchange", handleHashChange)
348384
document.querySelector("#modal-close").onclick = hideModal
349-
document.querySelector("#share").onclick = share
350385
document.querySelector("#sidebar-about").onclick = showAbout
351386
document.querySelector("#sidebar-share").onclick = share
352387
document.querySelector("#sidebar-icon-share").onclick = share
388+
const shareBtn = document.querySelector("#share")
389+
if (shareBtn) shareBtn.onclick = share
390+
const showNotesBtn = document.querySelector("#show-notes")
391+
if (showNotesBtn) showNotesBtn.onclick = showNotes
353392
await fetchSamples()
354393
await handleHashChangeNoFormat() // Evy wasm for formatting might not be ready yet
355394
initModal()
@@ -409,15 +448,11 @@ async function handleHashChange() {
409448

410449
async function handleHashChangeNoFormat() {
411450
hideModal()
412-
await stopAndSlide() // go to code screen for new code
451+
await stop() // go to code screen for new code
413452
let opts = parseHash()
414453
if (!opts.source && !opts.sample && !opts.content) {
415-
if (hasEditorSession()) {
416-
currentSample = "<UNSET>"
417-
!editor && initEditor()
418-
editor.loadSession()
419-
loadNotes()
420-
toggleEditorVisibility(true)
454+
if (sessionStorage.getItem("evy-editor") !== null) {
455+
loadSession()
421456
return
422457
}
423458
const sample = "welcome"
@@ -428,9 +463,35 @@ async function handleHashChangeNoFormat() {
428463
updateNotes(notes)
429464
updateEditor(source, opts)
430465
updateSampleTitle()
466+
resetView()
431467
clearOutput()
432468
}
433469

470+
function loadSession() {
471+
currentSample = "<UNSET>"
472+
!editor && initEditor()
473+
editor.loadSession()
474+
loadNotes()
475+
toggleEditorVisibility(true)
476+
resetView() // on mobile go to Notes view or Code view if no notes available.
477+
}
478+
479+
// resetView resets the view based on content availability, on mobile only.
480+
// If notes are available, navigates to the notes view.
481+
// If no notes but the editor is present, switches to the code view.
482+
// Otherwise, defaults to the output view
483+
function resetView() {
484+
const showNotesBtn = document.querySelector("#show-notes")
485+
if (showNotesBtn) showNotesBtn.disabled = true
486+
if (!notesHidden) {
487+
setView("view-notes")
488+
} else if (!editorHidden) {
489+
setView("view-code")
490+
} else {
491+
setView("view-output")
492+
}
493+
}
494+
434495
function updateNotes(notes) {
435496
hasNotes(notes) ? addNotes(notes) : removeNotes()
436497
}
@@ -918,10 +979,6 @@ function initEditor() {
918979
document.querySelector(".editor-wrap").classList.remove("noscrollbar")
919980
}
920981

921-
function hasEditorSession() {
922-
return !!sessionStorage.getItem("evy-editor")
923-
}
924-
925982
// --- eventHandlers, evy `on` -----------------------------------------
926983

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

951-
function unfocusRunBotton() {
1008+
function unfocusRunButton() {
9521009
const runButton = document.querySelector("#run")
9531010
const runButtonMob = document.querySelector("#run-mobile")
9541011
document.activeElement === runButton && runButton.blur()

0 commit comments

Comments
 (0)