Skip to content

Commit 62818d3

Browse files
committed
[feat] restrict file actions to what tutorial needs
No create/edit/remove outside of what is needed to solve the tutorial chapter
1 parent dbb6b0c commit 62818d3

File tree

5 files changed

+138
-31
lines changed

5 files changed

+138
-31
lines changed

src/lib/components/Modal.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
const selection = window.getSelection();
99
1010
const active = document.activeElement;
11-
const sfnsp = selection.focusNode.parentElement;
11+
const sfnsp = selection?.focusNode?.parentElement;
1212
1313
if (modal.showModal) modal.showModal();
1414

src/lib/types/index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,8 @@ export interface FileTreeContext {
8787
remove: (stub: Stub) => Promise<void>;
8888
selected: Writable<FileStub | null>;
8989
}
90+
91+
export interface EditingConstraints {
92+
create: string[];
93+
remove: string[];
94+
}

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

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import { PUBLIC_USE_FILESYSTEM } from '$env/static/public';
1515
import ContextMenu from './ContextMenu.svelte';
1616
import ScreenToggle from './ScreenToggle.svelte';
17+
import Modal from '$lib/components/Modal.svelte';
1718
1819
/** @type {import('./$types').PageData} */
1920
export let data;
@@ -42,23 +43,35 @@
4243
Object.keys(complete_states).length > 0 && Object.values(complete_states).every(Boolean);
4344
4445
let path = '/';
46+
let modal_text = '';
4547
4648
let width = browser ? window.innerWidth : 1000;
4749
let selected_view = 0;
4850
$: mobile = width < 768;
4951
5052
/** @type {Record<string, import('$lib/types').Stub>} */
5153
let b;
54+
/** @type {import('$lib/types').EditingConstraints} list of files user is allowed to create/delete in the tutorial chapter */
55+
let editing_constraints;
5256
$: {
5357
b = { ...data.section.a };
58+
editing_constraints = {
59+
create: [],
60+
remove: []
61+
};
5462
for (const key in data.section.b) {
5563
if (key.endsWith('__delete')) {
64+
const to_delete = key.slice(0, -'/__delete'.length);
65+
editing_constraints.remove.push(to_delete);
5666
for (const k in b) {
57-
if (k.startsWith(key.slice(0, -'/__delete'.length))) {
67+
if (k.startsWith(to_delete)) {
5868
delete b[k];
5969
}
6070
}
6171
} else {
72+
if (!b[key]) {
73+
editing_constraints.create.push(key);
74+
}
6275
b[key] = data.section.b[key];
6376
}
6477
}
@@ -71,6 +84,16 @@
7184
},
7285
7386
add: async (stubs) => {
87+
const illegal_create = editing_constraints.create.some(
88+
(c) => !stubs.some((s) => (s.type === 'directory' && c.startsWith(s.name)) || s.name === c)
89+
);
90+
if (illegal_create) {
91+
modal_text =
92+
'Only the following files and folders are allowed to be created in this tutorial chapter:\n' +
93+
editing_constraints.create.join('\n');
94+
return;
95+
}
96+
7497
current_stubs = [...current_stubs, ...stubs];
7598
7699
await load_files(current_stubs);
@@ -81,6 +104,17 @@
81104
},
82105
83106
edit: async (to_rename, new_name) => {
107+
const illegal_rename = editing_constraints.remove.some(
108+
(r) =>
109+
(to_rename.type === 'directory' && r.startsWith(to_rename.name)) || to_rename.name === r
110+
);
111+
if (illegal_rename) {
112+
modal_text =
113+
'Only the following files and folders are allowed to be renamed in this tutorial chapter:\n' +
114+
editing_constraints.remove.join('\n');
115+
return;
116+
}
117+
84118
/** @type {Array<[import('$lib/types').Stub, import('$lib/types').Stub]>}*/
85119
const changed = [];
86120
current_stubs = current_stubs.map((s) => {
@@ -107,6 +141,16 @@
107141
},
108142
109143
remove: async (stub) => {
144+
const illegal_delete = editing_constraints.remove.some(
145+
(r) => (stub.type === 'directory' && r.startsWith(stub.name)) || stub.name === r
146+
);
147+
if (illegal_delete) {
148+
modal_text =
149+
'Only the following files and folders are allowed to be deleted in this tutorial chapter:\n' +
150+
editing_constraints.remove.join('\n');
151+
return;
152+
}
153+
110154
const out = current_stubs.filter((s) => s.name.startsWith(stub.name));
111155
current_stubs = current_stubs.filter((s) => !out.includes(s));
112156
@@ -319,6 +363,20 @@
319363
320364
<ContextMenu />
321365
366+
{#if modal_text}
367+
<Modal on:close={() => (modal_text = '')}>
368+
<div class="modal-contents">
369+
<h2>This action is not allowed</h2>
370+
371+
<p>
372+
{modal_text}
373+
</p>
374+
375+
<button on:click={() => (modal_text = '')}>OK</button>
376+
</div>
377+
</Modal>
378+
{/if}
379+
322380
<div class="container" style="--toggle-height: {mobile ? '4.6rem' : '0px'}">
323381
<SplitPane
324382
type="horizontal"
@@ -352,6 +410,8 @@
352410
files={current_stubs.filter((stub) => !hidden.has(stub.basename))}
353411
expanded
354412
read_only={mobile}
413+
can_create={!!editing_constraints.create.length}
414+
can_remove={!!editing_constraints.remove.length}
355415
/>
356416
</div>
357417
@@ -516,4 +576,19 @@
516576
.hidden {
517577
display: none;
518578
}
579+
580+
.modal-contents p {
581+
white-space: pre-line;
582+
}
583+
584+
.modal-contents button {
585+
display: block;
586+
background: var(--prime);
587+
color: white;
588+
padding: 1rem;
589+
width: 10em;
590+
margin: 1em 0 0 0;
591+
border-radius: var(--border-r);
592+
line-height: 1;
593+
}
519594
</style>

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
/** @type {import('$lib/types').FileStub} */
66
export let file;
77
export let read_only = false;
8+
export let can_create = true;
9+
export let can_remove = true;
810
911
/** @type {import('$lib/types').FileTreeContext} */
1012
const { select, selected, edit, remove } = getContext('filetree');
@@ -22,22 +24,30 @@
2224
2325
/** @param {MouseEvent} e */
2426
function open_menu(e) {
25-
if (restricted.has(file.basename) || read_only) return;
26-
open(e.clientX, e.clientY, [
27-
{
27+
if (restricted.has(file.basename) || read_only || !can_remove) {
28+
return;
29+
}
30+
31+
/** @type {import('./ContextMenu.svelte').MenuItems} */
32+
const actions = [];
33+
if (can_create && can_remove) {
34+
actions.push({
2835
name: 'Rename',
2936
action: () => {
3037
new_name = file.basename;
3138
editing = true;
3239
}
33-
},
34-
{
40+
});
41+
}
42+
if (can_remove) {
43+
actions.push({
3544
name: 'Delete',
3645
action: () => {
3746
remove(file);
3847
}
39-
}
40-
]);
48+
});
49+
}
50+
open(e.clientX, e.clientY, actions);
4151
}
4252
4353
/** @param {Event} e */
@@ -71,7 +81,7 @@
7181
</button>
7282
{:else}
7383
<!-- svelte-ignore a11y-autofocus -->
74-
<input type="text" autofocus bind:value={new_name} on:blur={done} on:keydown={done} />
84+
<input type="text" autofocus bind:value={new_name} on:blur={done} on:keyup={done} />
7585
{/if}
7686

7787
<style>

src/routes/tutorial/[slug]/Folder.svelte

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
/** @type {Array<import('$lib/types').Stub>} */
1919
export let files;
2020
21+
export let can_create = true;
22+
23+
export let can_remove = true;
24+
2125
export let read_only = false;
2226
2327
/** @type {'idle' | 'add_file' | 'add_folder' | 'edit_folder'} */
@@ -45,35 +49,46 @@
4549
4650
/** @param {MouseEvent} e */
4751
function open_menu(e) {
48-
if (depth === 0 || read_only) return;
52+
if (depth === 0 || read_only || (!can_create && !can_remove)) {
53+
return;
54+
}
4955
50-
open(e.clientX, e.clientY, [
51-
{
52-
name: 'New File',
53-
action: () => {
54-
state = 'add_file';
55-
}
56-
},
57-
{
58-
name: 'New Folder',
59-
action: () => {
60-
state = 'add_folder';
56+
/** @type {import('./ContextMenu.svelte').MenuItems} */
57+
const actions = [];
58+
if (can_create) {
59+
actions.push(
60+
{
61+
name: 'New File',
62+
action: () => {
63+
state = 'add_file';
64+
}
65+
},
66+
{
67+
name: 'New Folder',
68+
action: () => {
69+
state = 'add_folder';
70+
}
6171
}
62-
},
63-
{
72+
);
73+
}
74+
if (can_create && can_remove) {
75+
actions.push({
6476
name: 'Rename',
6577
action: () => {
6678
new_name = name;
6779
state = 'edit_folder';
6880
}
69-
},
70-
{
81+
});
82+
}
83+
if (can_remove) {
84+
actions.push({
7185
name: 'Delete',
7286
action: () => {
7387
remove(/** @type {import('$lib/types').DirectoryStub} */ (file));
7488
}
75-
}
76-
]);
89+
});
90+
}
91+
open(e.clientX, e.clientY, actions);
7792
}
7893
7994
/** @param {Event} e */
@@ -133,7 +148,7 @@
133148

134149
{#if state === 'edit_folder'}
135150
<!-- svelte-ignore a11y-autofocus -->
136-
<input type="text" autofocus bind:value={new_name} on:blur={done} on:keydown={done} />
151+
<input type="text" autofocus bind:value={new_name} on:blur={done} on:keyup={done} />
137152
{/if}
138153

139154
{#if expanded}
@@ -146,7 +161,7 @@
146161
autofocus
147162
bind:value={new_name}
148163
on:blur={done}
149-
on:keydown={done}
164+
on:keyup={done}
150165
/>
151166
{/if}
152167
{#each child_directories as directory}
@@ -157,13 +172,15 @@
157172
depth={depth + 1}
158173
files={children}
159174
{read_only}
175+
{can_create}
176+
{can_remove}
160177
/>
161178
</li>
162179
{/each}
163180

164181
{#each child_files as file}
165182
<li>
166-
<File {file} {read_only} />
183+
<File {file} {read_only} {can_create} {can_remove} />
167184
</li>
168185
{/each}
169186
</ul>

0 commit comments

Comments
 (0)