Skip to content

Commit 66f95ec

Browse files
committed
Mobile friendly update
1 parent 2632894 commit 66f95ec

5 files changed

Lines changed: 89 additions & 20 deletions

File tree

‎web/frontend/src/App.svelte‎

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,24 @@
2020
const EDITOR_OPEN_KEY = "glua-editor-open";
2121
2222
function updatePanelWidths() {
23-
if (consolePanel && !resizing) {
24-
if ($isEditorOpen) {
25-
const savedWidth = localStorage.getItem(EDITOR_WIDTH_KEY) || "33%";
26-
const consoleWidth = `calc(100% - ${savedWidth})`;
27-
consolePanel.style.width = consoleWidth;
28-
if (editorPanel) editorPanel.style.width = savedWidth;
29-
} else {
30-
consolePanel.style.width = "100%";
31-
}
23+
if (!consolePanel || resizing) return;
24+
25+
// On mobile, the layout is handled entirely by Tailwind responsive classes.
26+
// Clear any inline widths so the CSS can take over when crossing the breakpoint.
27+
const isDesktop = window.matchMedia("(min-width: 768px)").matches;
28+
if (!isDesktop) {
29+
consolePanel.style.width = "";
30+
if (editorPanel) editorPanel.style.width = "";
31+
return;
32+
}
33+
34+
if ($isEditorOpen) {
35+
const savedWidth = localStorage.getItem(EDITOR_WIDTH_KEY) || "33%";
36+
const consoleWidth = `calc(100% - ${savedWidth})`;
37+
consolePanel.style.width = consoleWidth;
38+
if (editorPanel) editorPanel.style.width = savedWidth;
39+
} else {
40+
consolePanel.style.width = "100%";
3241
}
3342
}
3443
@@ -49,8 +58,13 @@
4958
isEditorOpen.update((open) => !open);
5059
}
5160
};
61+
const handleResize = () => updatePanelWidths();
5262
window.addEventListener("keydown", handleKeydown);
53-
return () => window.removeEventListener("keydown", handleKeydown);
63+
window.addEventListener("resize", handleResize);
64+
return () => {
65+
window.removeEventListener("keydown", handleKeydown);
66+
window.removeEventListener("resize", handleResize);
67+
};
5468
});
5569
5670
afterUpdate(() => {
@@ -136,7 +150,7 @@
136150
</script>
137151

138152
<ScriptViewer />
139-
<main class="flex flex-row h-screen overflow-hidden">
153+
<main class="flex flex-row h-dvh overflow-hidden relative">
140154
{#if view === "ip-limited"}
141155
<IPLimited limit={ipLimit} />
142156
{:else if view === "modal"}
@@ -172,8 +186,8 @@
172186
</div>
173187
{#if $isEditorOpen}
174188
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
175-
<div id="resizer" on:mousedown={handleMouseDown} role="separator" aria-orientation="vertical"></div>
176-
<div bind:this={editorPanel} class="h-full" style="width: 33%;">
189+
<div id="resizer" class="hidden md:block" on:mousedown={handleMouseDown} role="separator" aria-orientation="vertical"></div>
190+
<div bind:this={editorPanel} class="h-full absolute inset-0 z-30 md:relative md:inset-auto md:z-auto">
177191
<Editor {socket} />
178192
</div>
179193
{/if}

‎web/frontend/src/app.css‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,23 @@ select {
6565
#status-panel .toggle-icon { transition: transform 0.3s ease; }
6666
#status-panel.collapsed .toggle-icon { transform: rotate(180deg); }
6767

68+
/* Mobile: expanded panel-content overlays the console instead of pushing it down.
69+
Anchored to #status-panel's inner wrapper, positioned just below the minimized bar. */
70+
@media (max-width: 767px) {
71+
#status-panel > div {
72+
position: relative;
73+
}
74+
#status-panel .panel-content {
75+
position: absolute;
76+
top: 100%;
77+
left: 0;
78+
right: 0;
79+
background-color: #1f2937;
80+
border-bottom: 1px solid rgba(55, 65, 81, 0.5);
81+
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3), 0 4px 6px -4px rgb(0 0 0 / 0.3);
82+
}
83+
}
84+
6885
#editor-panel {
6986
height: 100%;
7087
overflow: hidden;
@@ -93,6 +110,16 @@ select {
93110
background-color: #4f46e5;
94111
}
95112

113+
/* On mobile the middle-right position overlaps the full-screen editor and the top status bar.
114+
Move it to a thumb-friendly bottom-right. */
115+
@media (max-width: 767px) {
116+
#editor-toggle-button {
117+
top: auto;
118+
bottom: 1rem;
119+
transform: none;
120+
}
121+
}
122+
96123
#resizer {
97124
width: 8px;
98125
cursor: col-resize;

‎web/frontend/src/components/Console.svelte‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@
199199
<div class="flex items-center border-t border-gray-700 pt-3">
200200
<span class="{inactive ? 'text-gray-600' : 'text-green-400'} mr-2 shrink-0">&gt;</span>
201201
<!-- svelte-ignore a11y-autofocus -->
202-
<input type="text" bind:this={commandInput} bind:value={commandValue} maxlength={MAX_COMMAND_LENGTH} on:keydown={handleKeydown} class="w-full bg-transparent border-none focus:ring-0 focus:outline-none {inactive ? 'text-gray-600 placeholder-gray-600 cursor-not-allowed' : 'text-gray-200 placeholder-gray-500'}" placeholder="{inactive ? 'Session ended' : 'Enter command...'}" autocomplete="off" autofocus disabled>
202+
<input type="text" bind:this={commandInput} bind:value={commandValue} maxlength={MAX_COMMAND_LENGTH} on:keydown={handleKeydown} class="w-full bg-transparent border-none focus:ring-0 focus:outline-none {inactive ? 'text-gray-600 placeholder-gray-600 cursor-not-allowed' : 'text-gray-200 placeholder-gray-500'}" placeholder="{inactive ? 'Session ended' : 'Enter command...'}" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" autofocus disabled>
203203
{#if showCounter}
204204
<span class="ml-2 text-xs font-mono tabular-nums shrink-0 {counterColor}">{remaining}</span>
205205
{/if}

‎web/frontend/src/components/StatusPanel.svelte‎

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import { createEventDispatcher } from "svelte";
3-
import { sessionState, sessionMetadata, sessionTimer } from "../lib/stores";
3+
import { sessionState, sessionMetadata, sessionTimeRemaining } from "../lib/stores";
44
import { healthData } from "../lib/socket";
55
import { formatTime } from "../lib/format";
66
import { DISCORD_URL } from "../lib/links";
@@ -12,14 +12,20 @@
1212
1313
export let socket: WebSocket | null;
1414
15-
let collapsed = false;
15+
// Default to collapsed on mobile so the panel reads as a slim top bar; desktop keeps the
16+
// floating card expanded by default.
17+
let collapsed = window.matchMedia("(max-width: 767px)").matches;
1618
let sessionDuration = "";
1719
1820
$: if ($sessionState === "closed" || $sessionState === "readonly") {
1921
computeDuration();
22+
// Auto-expand when the session ends so the final metadata and New Session button
23+
// are immediately visible on mobile without requiring a tap.
24+
collapsed = false;
2025
}
2126
2227
$: inactive = $sessionState === "closed" || $sessionState === "readonly";
28+
$: minTimeRemaining = $sessionTimeRemaining > 0 ? formatTime($sessionTimeRemaining) : "—:—";
2329
2430
function computeDuration() {
2531
const meta = $sessionMetadata;
@@ -29,9 +35,21 @@
2935
}
3036
</script>
3137

32-
<div id="status-panel" class="absolute top-4 right-4 z-20 w-72" class:collapsed>
33-
<div class="bg-gray-800 rounded-lg shadow-lg border border-gray-700/50">
34-
<div class="w-full flex justify-between items-center p-3">
38+
<div id="status-panel" class="z-20 md:absolute md:top-4 md:right-4 md:w-72 relative w-full" class:collapsed>
39+
<div class="bg-gray-800 md:rounded-lg md:shadow-lg md:border border-b border-gray-700/50">
40+
<button
41+
type="button"
42+
on:click={() => collapsed = !collapsed}
43+
class="md:hidden flex w-full items-center justify-between px-4 py-2.5 focus:outline-none"
44+
aria-expanded={!collapsed}
45+
aria-label="Toggle session status"
46+
>
47+
<span class="text-xs font-mono {inactive ? 'text-gray-400' : 'text-indigo-300'}">
48+
{inactive ? "Session Ended" : `⏱ ${minTimeRemaining}`}
49+
</span>
50+
<svg class="toggle-icon w-5 h-5 text-gray-400" class:rotate-180={collapsed} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" /></svg>
51+
</button>
52+
<div class="hidden md:flex w-full justify-between items-center p-3">
3553
<button on:click={() => collapsed = !collapsed} class="flex-1 flex justify-between items-center text-left focus:outline-none pr-2">
3654
<span class="font-bold text-sm {inactive ? 'text-gray-400' : 'text-white'}">{inactive ? "Session Ended" : "Session Status"}</span>
3755
</button>

‎web/frontend/src/lib/stores.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { writable } from "svelte/store";
1+
import { derived, readable, writable } from "svelte/store";
22
import type { SessionMetadata, SessionTimerPayload, ScriptEntry } from "@glua/shared";
33

44
export type SessionState = "connecting" | "provisioning" | "active" | "closed" | "readonly";
@@ -9,3 +9,13 @@ export const scriptMap = writable<Record<string, ScriptEntry>>({});
99
export const viewingScript = writable<{ name: string; content: string } | null>(null);
1010
export const sessionMetadata = writable<SessionMetadata | null>(null);
1111
export const sessionTimer = writable<SessionTimerPayload | null>(null);
12+
13+
const clock = readable(Date.now(), (set) => {
14+
const id = setInterval(() => set(Date.now()), 1000);
15+
return () => clearInterval(id);
16+
});
17+
18+
export const sessionTimeRemaining = derived(
19+
[sessionTimer, clock],
20+
([$t, $c]) => ($t ? Math.max(0, $t.endTime - $c) : 0),
21+
);

0 commit comments

Comments
 (0)