detached 2c4b9db

fix: font sizing and mobile layout

commit
2c4b9db4d0523d5db7b3c7ed89bc992ba5d234e1
author
Kit · · 2025-02-09 18:04 UTC
parent
9e9498f
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
1 file · +116 -28
M src/lib/components/chat-room.svelte +116 -28
@@ -2,12 +2,16 @@
22 import { chatState } from '../states/chat.svelte';
33 import type { ChatMessage, User } from '../types/chat';
44 import { onMount } from 'svelte';
5+import { browser } from '$app/environment';
56
7+// Initialize with default values for SSR
68 let windowWidth = $state(800);
79 let windowHeight = $state(600);
810 let windowX = $state(0);
911 let windowY = $state(0);
12+let isMobile = $state(false);
1013 let currentMessage = $state('');
14+let showUserList = $state(false);
1115
1216 // Reactive state using derived values
1317 let messages = $derived(chatState.getMessages());
@@ -15,13 +19,29 @@ let onlineUsers = $derived(chatState.getOnlineUsers());
1519 let currentUser = $derived(chatState.getCurrentUser());
1620
1721 onMount(() => {
18- const centerWindow = () => {
19- windowX = Math.max(0, (window.innerWidth - windowWidth) / 2);
20- windowY = Math.max(0, (window.innerHeight - windowHeight) / 2);
22+ if (!browser) return;
23+
24+ const handleResize = () => {
25+ isMobile = window.innerWidth <= 768;
26+ if (isMobile) {
27+ windowWidth = window.innerWidth;
28+ windowHeight = window.innerHeight;
29+ windowX = 0;
30+ windowY = 0;
31+ } else {
32+ windowWidth = Math.min(800, window.innerWidth * 0.8);
33+ windowHeight = Math.min(600, window.innerHeight * 0.8);
34+ windowX = Math.max(0, (window.innerWidth - windowWidth) / 2);
35+ windowY = Math.max(0, (window.innerHeight - windowHeight) / 2);
36+ }
2137 };
22- centerWindow();
23- window.addEventListener('resize', centerWindow);
24- return () => window.removeEventListener('resize', centerWindow);
38+
39+ // Initial resize
40+ handleResize();
41+
42+ // Add resize listener
43+ window.addEventListener('resize', handleResize);
44+ return () => window.removeEventListener('resize', handleResize);
2545 });
2646
2747 function handleSubmit() {
@@ -60,18 +80,25 @@ function getStatusIcon(status: User['status']) {
6080 <div class="title-bar">
6181 <div class="title-bar-text">AIM Chat Room - {currentUser?.nickname}</div>
6282 <div class="title-bar-controls">
63- <button aria-label="Minimize"></button>
64- <button aria-label="Maximize"></button>
65- <button aria-label="Close"></button>
83+ {#if !isMobile}
84+ <button aria-label="Minimize"></button>
85+ <button aria-label="Maximize"></button>
86+ <button aria-label="Close"></button>
87+ {:else}
88+ <button
89+ aria-label="Toggle Buddy List"
90+ onclick={() => showUserList = !showUserList}
91+ >👥</button>
92+ {/if}
6693 </div>
6794 </div>
6895
69- <div class="window-body" style="display: flex; height: calc(100% - 32px); margin: 0; padding: 8px;">
96+ <div class="window-body" style="display: flex; height: calc(100% - 2rem); margin: 0; padding: 0.5rem;">
7097 <!-- Chat content area -->
71- <div style="flex: 1; display: flex; flex-direction: column; margin-right: 8px;">
98+ <div class="chat-container" style="flex: 1; display: flex; flex-direction: column; margin-right: 0.5rem;">
7299 <div
73100 class="sunken-panel chat-area"
74- style="flex: 1; margin-bottom: 8px; padding: 8px; overflow-y: auto;"
101+ style="flex: 1; margin-bottom: 0.5rem; padding: 0.5rem; overflow-y: auto;"
75102 >
76103 {#each messages as message}
77104 <div class="message {message.type} text">
@@ -87,7 +114,7 @@ function getStatusIcon(status: User['status']) {
87114 {/each}
88115 </div>
89116
90- <div class="field-row" style="margin: 0;">
117+ <div class="field-row input-container" style="margin: 0;">
91118 <input
92119 type="text"
93120 bind:value={currentMessage}
@@ -102,9 +129,11 @@ function getStatusIcon(status: User['status']) {
102129 <!-- Online users list -->
103130 <div
104131 class="sunken-panel users-list"
105- style="width: 150px; padding: 8px; overflow-y: auto;"
132+ class:mobile={isMobile}
133+ class:hidden={isMobile && !showUserList}
134+ style="width: {isMobile ? '100%' : '9.375rem'}; padding: 0.5rem; overflow-y: auto;"
106135 >
107- <p style="margin: 0 0 8px 0;"><strong>Buddy List</strong></p>
136+ <p style="margin: 0 0 0.5rem 0;"><strong>Buddy List</strong></p>
108137 {#each onlineUsers as user}
109138 <div class="user">
110139 <span class="status-icon">{getStatusIcon(user.status)}</span>
@@ -121,10 +150,12 @@ function getStatusIcon(status: User['status']) {
121150 </div>
122151
123152 <style>
153+ :global(*) {
154+ font-size: 1rem;
155+ }
124156
125-
126157 .title-bar {
127- height: 30px;
158+ height: 2rem;
128159 }
129160
130161 .chat-area {
@@ -133,20 +164,20 @@ function getStatusIcon(status: User['status']) {
133164 }
134165
135166 .message {
136- margin-bottom: 8px;
167+ margin-bottom: 0.5rem;
137168 word-break: break-word;
138169 }
139170
140171 .message .timestamp {
141172 color: #666;
142- font-size: 0.8em;
143- margin-right: 8px;
173+ font-size: 0.875rem;
174+ margin-right: 0.5rem;
144175 }
145176
146177 .message .nickname {
147178 font-weight: bold;
148179 color: #2d31a6;
149- margin-right: 8px;
180+ margin-right: 0.5rem;
150181 }
151182
152183 .message.emote {
@@ -157,12 +188,12 @@ function getStatusIcon(status: User['status']) {
157188 .user {
158189 display: flex;
159190 align-items: flex-start;
160- margin-bottom: 12px;
161- gap: 6px;
191+ margin-bottom: 0.75rem;
192+ gap: 0.375rem;
162193 }
163194
164195 .status-icon {
165- font-size: 0.8em;
196+ font-size: 0.875rem;
166197 }
167198
168199 .user-info {
@@ -172,11 +203,11 @@ function getStatusIcon(status: User['status']) {
172203
173204 .user .nickname {
174205 font-weight: bold;
175- margin-bottom: 2px;
206+ margin-bottom: 0.125rem;
176207 }
177208
178209 .user .status-message {
179- font-size: 0.8em;
210+ font-size: 0.875rem;
180211 color: #666;
181212 font-style: italic;
182213 overflow: hidden;
@@ -186,11 +217,68 @@ function getStatusIcon(status: User['status']) {
186217
187218 .sunken-panel {
188219 background: white;
189- border: 2px inset #dfdfdf;
220+ border: 0.125rem inset #dfdfdf;
190221 }
191222
192223 input[type="text"] {
193224 font-size: 1rem;
194- margin-right: 4px;
225+ margin-right: 0.25rem;
226+ }
227+
228+ button {
229+ font-size: 1rem;
230+ }
231+
232+ .chat-container {
233+ position: relative;
234+ z-index: 1;
235+ }
236+
237+ .input-container {
238+ position: relative;
239+ z-index: 2;
240+ }
241+
242+ .hidden {
243+ display: none !important;
244+ }
245+
246+ @media (max-width: 768px) {
247+ .window {
248+ position: fixed !important;
249+ top: 0 !important;
250+ left: 0 !important;
251+ right: 0 !important;
252+ bottom: 0 !important;
253+ width: 100% !important;
254+ height: 100% !important;
255+ }
256+
257+ .window-body {
258+ flex-direction: column;
259+ }
260+
261+ .chat-container {
262+ margin-right: 0 !important;
263+ margin-bottom: 0 !important;
264+ height: 100%;
265+ }
266+
267+ .users-list.mobile {
268+ position: fixed;
269+ bottom: 0;
270+ left: 0;
271+ right: 0;
272+ height: auto;
273+ max-height: 30vh;
274+ z-index: 1000;
275+ border-top: 0.125rem solid #dfdfdf;
276+ background: white;
277+ }
278+
279+ .input-container {
280+ padding: 0.5rem;
281+ border-top: 0.125rem solid #dfdfdf;
282+ }
195283 }
196284 </style>⏎̸
@@ -2,12 +2,16 @@
2 import { chatState } from '../states/chat.svelte';2 import { chatState } from '../states/chat.svelte';
3 import type { ChatMessage, User } from '../types/chat';3 import type { ChatMessage, User } from '../types/chat';
4 import { onMount } from 'svelte';4 import { onMount } from 'svelte';
5+import { browser } from '$app/environment';
5 6
7+// Initialize with default values for SSR
6 let windowWidth = $state(800);8 let windowWidth = $state(800);
7 let windowHeight = $state(600);9 let windowHeight = $state(600);
8 let windowX = $state(0);10 let windowX = $state(0);
9 let windowY = $state(0);11 let windowY = $state(0);
12+let isMobile = $state(false);
10 let currentMessage = $state('');13 let currentMessage = $state('');
14+let showUserList = $state(false);
11 15
12 // Reactive state using derived values16 // Reactive state using derived values
13 let messages = $derived(chatState.getMessages());17 let messages = $derived(chatState.getMessages());
@@ -15,13 +19,29 @@ let onlineUsers = $derived(chatState.getOnlineUsers());
15 let currentUser = $derived(chatState.getCurrentUser());19 let currentUser = $derived(chatState.getCurrentUser());
16 20
17 onMount(() => {21 onMount(() => {
18- const centerWindow = () => {22+ if (!browser) return;
19- windowX = Math.max(0, (window.innerWidth - windowWidth) / 2);23+
20- windowY = Math.max(0, (window.innerHeight - windowHeight) / 2);24+ const handleResize = () => {
25+ isMobile = window.innerWidth <= 768;
26+ if (isMobile) {
27+ windowWidth = window.innerWidth;
28+ windowHeight = window.innerHeight;
29+ windowX = 0;
30+ windowY = 0;
31+ } else {
32+ windowWidth = Math.min(800, window.innerWidth * 0.8);
33+ windowHeight = Math.min(600, window.innerHeight * 0.8);
34+ windowX = Math.max(0, (window.innerWidth - windowWidth) / 2);
35+ windowY = Math.max(0, (window.innerHeight - windowHeight) / 2);
36+ }
21 };37 };
22- centerWindow();38+
23- window.addEventListener('resize', centerWindow);39+ // Initial resize
24- return () => window.removeEventListener('resize', centerWindow);40+ handleResize();
41+
42+ // Add resize listener
43+ window.addEventListener('resize', handleResize);
44+ return () => window.removeEventListener('resize', handleResize);
25 });45 });
26 46
27 function handleSubmit() {47 function handleSubmit() {
@@ -60,18 +80,25 @@ function getStatusIcon(status: User['status']) {
60 <div class="title-bar">80 <div class="title-bar">
61 <div class="title-bar-text">AIM Chat Room - {currentUser?.nickname}</div>81 <div class="title-bar-text">AIM Chat Room - {currentUser?.nickname}</div>
62 <div class="title-bar-controls">82 <div class="title-bar-controls">
63- <button aria-label="Minimize"></button>83+ {#if !isMobile}
64- <button aria-label="Maximize"></button>84+ <button aria-label="Minimize"></button>
65- <button aria-label="Close"></button>85+ <button aria-label="Maximize"></button>
86+ <button aria-label="Close"></button>
87+ {:else}
88+ <button
89+ aria-label="Toggle Buddy List"
90+ onclick={() => showUserList = !showUserList}
91+ >👥</button>
92+ {/if}
66 </div>93 </div>
67 </div>94 </div>
68 95
69- <div class="window-body" style="display: flex; height: calc(100% - 32px); margin: 0; padding: 8px;">96+ <div class="window-body" style="display: flex; height: calc(100% - 2rem); margin: 0; padding: 0.5rem;">
70 <!-- Chat content area -->97 <!-- Chat content area -->
71- <div style="flex: 1; display: flex; flex-direction: column; margin-right: 8px;">98+ <div class="chat-container" style="flex: 1; display: flex; flex-direction: column; margin-right: 0.5rem;">
72 <div 99 <div
73 class="sunken-panel chat-area"100 class="sunken-panel chat-area"
74- style="flex: 1; margin-bottom: 8px; padding: 8px; overflow-y: auto;"101+ style="flex: 1; margin-bottom: 0.5rem; padding: 0.5rem; overflow-y: auto;"
75 >102 >
76 {#each messages as message}103 {#each messages as message}
77 <div class="message {message.type} text">104 <div class="message {message.type} text">
@@ -87,7 +114,7 @@ function getStatusIcon(status: User['status']) {
87 {/each}114 {/each}
88 </div>115 </div>
89 116
90- <div class="field-row" style="margin: 0;">117+ <div class="field-row input-container" style="margin: 0;">
91 <input 118 <input
92 type="text" 119 type="text"
93 bind:value={currentMessage}120 bind:value={currentMessage}
@@ -102,9 +129,11 @@ function getStatusIcon(status: User['status']) {
102 <!-- Online users list -->129 <!-- Online users list -->
103 <div 130 <div
104 class="sunken-panel users-list"131 class="sunken-panel users-list"
105- style="width: 150px; padding: 8px; overflow-y: auto;"132+ class:mobile={isMobile}
133+ class:hidden={isMobile && !showUserList}
134+ style="width: {isMobile ? '100%' : '9.375rem'}; padding: 0.5rem; overflow-y: auto;"
106 >135 >
107- <p style="margin: 0 0 8px 0;"><strong>Buddy List</strong></p>136+ <p style="margin: 0 0 0.5rem 0;"><strong>Buddy List</strong></p>
108 {#each onlineUsers as user}137 {#each onlineUsers as user}
109 <div class="user">138 <div class="user">
110 <span class="status-icon">{getStatusIcon(user.status)}</span>139 <span class="status-icon">{getStatusIcon(user.status)}</span>
@@ -121,10 +150,12 @@ function getStatusIcon(status: User['status']) {
121 </div>150 </div>
122 151
123 <style>152 <style>
153+ :global(*) {
154+ font-size: 1rem;
155+ }
124 156
125-
126 .title-bar {157 .title-bar {
127- height: 30px;158+ height: 2rem;
128 }159 }
129 160
130 .chat-area {161 .chat-area {
@@ -133,20 +164,20 @@ function getStatusIcon(status: User['status']) {
133 }164 }
134 165
135 .message {166 .message {
136- margin-bottom: 8px;167+ margin-bottom: 0.5rem;
137 word-break: break-word;168 word-break: break-word;
138 }169 }
139 170
140 .message .timestamp {171 .message .timestamp {
141 color: #666;172 color: #666;
142- font-size: 0.8em;173+ font-size: 0.875rem;
143- margin-right: 8px;174+ margin-right: 0.5rem;
144 }175 }
145 176
146 .message .nickname {177 .message .nickname {
147 font-weight: bold;178 font-weight: bold;
148 color: #2d31a6;179 color: #2d31a6;
149- margin-right: 8px;180+ margin-right: 0.5rem;
150 }181 }
151 182
152 .message.emote {183 .message.emote {
@@ -157,12 +188,12 @@ function getStatusIcon(status: User['status']) {
157 .user {188 .user {
158 display: flex;189 display: flex;
159 align-items: flex-start;190 align-items: flex-start;
160- margin-bottom: 12px;191+ margin-bottom: 0.75rem;
161- gap: 6px;192+ gap: 0.375rem;
162 }193 }
163 194
164 .status-icon {195 .status-icon {
165- font-size: 0.8em;196+ font-size: 0.875rem;
166 }197 }
167 198
168 .user-info {199 .user-info {
@@ -172,11 +203,11 @@ function getStatusIcon(status: User['status']) {
172 203
173 .user .nickname {204 .user .nickname {
174 font-weight: bold;205 font-weight: bold;
175- margin-bottom: 2px;206+ margin-bottom: 0.125rem;
176 }207 }
177 208
178 .user .status-message {209 .user .status-message {
179- font-size: 0.8em;210+ font-size: 0.875rem;
180 color: #666;211 color: #666;
181 font-style: italic;212 font-style: italic;
182 overflow: hidden;213 overflow: hidden;
@@ -186,11 +217,68 @@ function getStatusIcon(status: User['status']) {
186 217
187 .sunken-panel {218 .sunken-panel {
188 background: white;219 background: white;
189- border: 2px inset #dfdfdf;220+ border: 0.125rem inset #dfdfdf;
190 }221 }
191 222
192 input[type="text"] {223 input[type="text"] {
193 font-size: 1rem;224 font-size: 1rem;
194- margin-right: 4px;225+ margin-right: 0.25rem;
226+ }
227+
228+ button {
229+ font-size: 1rem;
230+ }
231+
232+ .chat-container {
233+ position: relative;
234+ z-index: 1;
235+ }
236+
237+ .input-container {
238+ position: relative;
239+ z-index: 2;
240+ }
241+
242+ .hidden {
243+ display: none !important;
244+ }
245+
246+ @media (max-width: 768px) {
247+ .window {
248+ position: fixed !important;
249+ top: 0 !important;
250+ left: 0 !important;
251+ right: 0 !important;
252+ bottom: 0 !important;
253+ width: 100% !important;
254+ height: 100% !important;
255+ }
256+
257+ .window-body {
258+ flex-direction: column;
259+ }
260+
261+ .chat-container {
262+ margin-right: 0 !important;
263+ margin-bottom: 0 !important;
264+ height: 100%;
265+ }
266+
267+ .users-list.mobile {
268+ position: fixed;
269+ bottom: 0;
270+ left: 0;
271+ right: 0;
272+ height: auto;
273+ max-height: 30vh;
274+ z-index: 1000;
275+ border-top: 0.125rem solid #dfdfdf;
276+ background: white;
277+ }
278+
279+ .input-container {
280+ padding: 0.5rem;
281+ border-top: 0.125rem solid #dfdfdf;
282+ }
195 }283 }
196 </style>⏎̸284 </style>⏎̸

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close