Skip to content

Commit 1e20af8

Browse files
author
Rich Harris
committed
mobile toggle
1 parent 8ab615a commit 1e20af8

File tree

9 files changed

+214
-134
lines changed

9 files changed

+214
-134
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"@lezer/javascript": "^1.4.1",
4747
"@lezer/lr": "^1.3.3",
4848
"@replit/codemirror-lang-svelte": "^6.0.0",
49-
"@rich_harris/svelte-split-pane": "^1.0.1",
49+
"@rich_harris/svelte-split-pane": "^1.0.2",
5050
"@webcontainer/api": "^1.0.2",
5151
"adm-zip": "^0.5.10",
5252
"ansi-to-html": "^0.7.2",

pnpm-lock.yaml

Lines changed: 7 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/routes/tutorial/[slug]/+page.svelte

Lines changed: 91 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@
2323
2424
export let data;
2525
26-
let width = browser ? window.innerWidth : 1000;
27-
let selected_view = 0;
26+
let show_editor = false;
2827
2928
let path = data.exercise.path;
3029
let paused = false;
@@ -46,9 +45,6 @@
4645
paused = false;
4746
});
4847
49-
$: mobile = writable(false);
50-
$: $mobile = width < 768;
51-
5248
$: completed = is_completed($files, data.exercise.b);
5349
5450
$: files.set(Object.values(data.exercise.a));
@@ -105,72 +101,81 @@
105101
106102
<ContextMenu />
107103
108-
<div class="container" style="--toggle-height: {$mobile ? '4.6rem' : '0px'}">
109-
<SplitPane
110-
type="horizontal"
111-
min={$mobile ? '0px' : '360px'}
112-
max={$mobile ? '100%' : '50%'}
113-
pos={$mobile ? (selected_view === 0 ? '100%' : '0%') : '33%'}
114-
>
115-
<section slot="a" class="content">
116-
<Sidebar
117-
index={data.index}
118-
exercise={data.exercise}
119-
on:select={(e) => {
120-
select_file(e.detail.file);
121-
}}
122-
/>
123-
</section>
124-
125-
<section slot="b" class:hidden={$mobile && selected_view === 0}>
126-
<SplitPane
127-
type="vertical"
128-
min={$mobile ? '0px' : '100px'}
129-
max={$mobile ? '100%' : '50%'}
130-
pos={$mobile ? (selected_view === 1 ? '100%' : '0%') : '50%'}
131-
>
132-
<section slot="a">
133-
<SplitPane type="horizontal" min="120px" max="300px" pos="200px">
134-
<section class="navigator" slot="a">
135-
<Filetree readonly={mobile} exercise={data.exercise} />
136-
137-
<button
138-
class:completed
139-
disabled={!data.exercise.has_solution}
140-
on:click={() => {
141-
reset_files(Object.values(completed ? data.exercise.a : data.exercise.b));
142-
}}
143-
>
144-
{#if completed && data.exercise.has_solution}
145-
reset
146-
{:else}
147-
solve <Icon name="arrow-right" />
148-
{/if}
149-
</button>
150-
</section>
151-
152-
<section class="editor-container" slot="b">
153-
<Editor />
154-
<ImageViewer selected={$selected_file} />
155-
</section>
156-
</SplitPane>
157-
</section>
158-
159-
<section slot="b" class="preview">
160-
<Output exercise={data.exercise} {paused} />
161-
</section>
162-
</SplitPane>
163-
</section>
164-
</SplitPane>
165-
{#if $mobile}
166-
<ScreenToggle labels={['Tutorial', 'Input', 'Output']} bind:selected={selected_view} />
167-
{/if}
104+
<div class="container">
105+
<div class="top" class:offset={show_editor}>
106+
<SplitPane id="main" type="horizontal" min="360px" max="50%" pos="33%">
107+
<section slot="a" class="content">
108+
<Sidebar
109+
index={data.index}
110+
exercise={data.exercise}
111+
on:select={(e) => {
112+
select_file(e.detail.file);
113+
}}
114+
/>
115+
</section>
116+
117+
<section slot="b">
118+
<SplitPane type="vertical" min="100px" max="50%" pos="50%">
119+
<section slot="a">
120+
<SplitPane type="horizontal" min="120px" max="300px" pos="200px">
121+
<section class="navigator" slot="a">
122+
<Filetree exercise={data.exercise} />
123+
124+
<button
125+
class:completed
126+
disabled={!data.exercise.has_solution}
127+
on:click={() => {
128+
reset_files(Object.values(completed ? data.exercise.a : data.exercise.b));
129+
}}
130+
>
131+
{#if completed && data.exercise.has_solution}
132+
reset
133+
{:else}
134+
solve <Icon name="arrow-right" />
135+
{/if}
136+
</button>
137+
</section>
138+
139+
<section class="editor-container" slot="b">
140+
<Editor />
141+
<ImageViewer selected={$selected_file} />
142+
</section>
143+
</SplitPane>
144+
</section>
145+
146+
<section slot="b" class="preview">
147+
<Output exercise={data.exercise} {paused} />
148+
</section>
149+
</SplitPane>
150+
</section>
151+
</SplitPane>
152+
</div>
153+
154+
<div class="screen-toggle">
155+
<ScreenToggle bind:selected={show_editor} />
156+
</div>
168157
</div>
169158
170159
<style>
171160
.container {
172-
height: calc(100% - var(--toggle-height));
173-
max-height: 100%;
161+
display: flex;
162+
flex-direction: column;
163+
height: 100%;
164+
}
165+
166+
.top {
167+
width: 200vw;
168+
height: 0;
169+
flex: 1;
170+
transition: transform 0.2s;
171+
}
172+
173+
.top.offset {
174+
transform: translate(-50%);
175+
}
176+
177+
.screen-toggle {
178+
height: 4.6rem;
174179
}
175180
176181
.content {
@@ -224,7 +229,24 @@
224229
background-color: var(--sk-back-3);
225230
}
226231
227-
.hidden {
228-
display: none;
232+
/* on mobile, override the <SplitPane> controls */
233+
@media (max-width: 799px) {
234+
:global([data-pane='main']) {
235+
--pos: 50% !important;
236+
}
237+
238+
:global([data-pane='main']) :global(.divider) {
239+
cursor: default;
240+
}
241+
}
242+
243+
@media (min-width: 800px) {
244+
.top {
245+
width: 100vw;
246+
}
247+
248+
.screen-toggle {
249+
display: none;
250+
}
229251
}
230252
</style>
Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,35 @@
11
<script>
2-
/** @type {string[]} */
3-
export let labels;
4-
export let selected = 0;
2+
import ToggleButton from './ToggleButton.svelte';
3+
4+
/** @type {boolean} */
5+
export let selected;
56
</script>
67

7-
<div class="toggle">
8-
{#each labels as label, index}
9-
<button class:selected={selected === index} on:click={() => (selected = index)}>
10-
{label}
11-
</button>
12-
{/each}
8+
<div class="input-output-toggle">
9+
<span aria-hidden="true">Tutorial</span>
10+
<ToggleButton bind:pressed={selected} label="Show editor" />
11+
<span aria-hidden="true">Editor</span>
1312
</div>
1413

1514
<style>
16-
.toggle {
17-
position: fixed;
18-
bottom: 0;
19-
width: 100%;
20-
height: var(--toggle-height);
15+
.input-output-toggle {
16+
position: relative;
2117
display: flex;
22-
justify-content: center;
18+
gap: 0.5em;
19+
user-select: none;
2320
align-items: center;
24-
border-top: 1px solid var(--sk-text-2);
25-
background-color: var(--sk-back-1);
26-
}
27-
button {
28-
margin: 0 0.15em;
29-
width: 4em;
30-
height: 1em;
31-
padding: 0.3em 0.4em;
32-
border-radius: var(--sk-border-radius);
33-
line-height: 1em;
34-
box-sizing: content-box;
35-
color: var(--sk-text-2);
36-
border: 1px solid var(--sk-back-3);
21+
justify-content: center;
22+
width: 100%;
23+
height: 100%;
24+
z-index: 2;
25+
margin: 0 auto;
3726
}
38-
.selected {
39-
background-color: var(--sk-theme-1);
40-
color: white;
27+
28+
@media (min-width: 832px) {
29+
.input-output-toggle {
30+
padding-left: 3.2rem;
31+
width: var(--sidebar-menu-width);
32+
margin: 0 0 0 auto;
33+
}
4134
}
4235
</style>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<!-- TODO this is copied from kit.svelte.dev — probably belongs in site-kit -->
2+
<script>
3+
/** @type {boolean} */
4+
export let pressed;
5+
6+
/** @type {string} */
7+
export let label;
8+
</script>
9+
10+
<button aria-pressed={pressed ? 'true' : 'false'} on:click={() => (pressed = !pressed)}>
11+
<span class="visually-hidden">{label}</span>
12+
</button>
13+
14+
<style>
15+
button {
16+
--size: 1em;
17+
--bg: var(--sk-theme-2);
18+
--fg: white;
19+
--bg-active: var(--bg);
20+
--fg-active: var(--fg);
21+
position: relative;
22+
height: var(--size);
23+
width: calc(100% - 0.6em);
24+
max-width: calc(2 * var(--size));
25+
top: -2px;
26+
border-radius: 0.5em;
27+
-webkit-appearance: none;
28+
appearance: none;
29+
outline-offset: 2px;
30+
border: transparent;
31+
}
32+
33+
button::before {
34+
content: '';
35+
position: absolute;
36+
display: block;
37+
height: 100%;
38+
width: 100%;
39+
padding: 2px;
40+
border-radius: var(--size);
41+
top: 0;
42+
left: 0;
43+
background: var(--bg);
44+
box-sizing: border-box;
45+
}
46+
47+
button[aria-pressed='true']::before {
48+
background: var(--bg-active);
49+
}
50+
51+
button::after {
52+
content: '';
53+
position: absolute;
54+
display: block;
55+
width: calc(var(--size) - 4px);
56+
height: calc(var(--size) - 4px);
57+
aspect-ratio: 1;
58+
top: 2px;
59+
left: 2px;
60+
border-radius: 50%;
61+
background: var(--fg);
62+
box-shadow: 0 0px 1px rgba(0, 0, 0, 0.4), 0 4px 2px rgba(0, 0, 0, 0.1);
63+
transition: background 0.2s ease-out, left 0.2s ease-out;
64+
}
65+
66+
button[aria-pressed='true']::after {
67+
background: var(--fg-active);
68+
left: calc(100% - var(--size) + 2px);
69+
}
70+
</style>

src/routes/tutorial/[slug]/filetree/File.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
/** @type {number} */
1111
export let depth;
1212
13-
const { rename, remove, readonly } = context.get();
13+
const { rename, remove } = context.get();
1414
1515
let renaming = false;
1616
17-
$: can_remove = !$readonly && !$solution[file.name];
17+
$: can_remove = !$solution[file.name];
1818
1919
/** @type {import('./ContextMenu.svelte').MenuItems} */
2020
$: actions = can_remove

0 commit comments

Comments
 (0)