|
14 | 14 | import { PUBLIC_USE_FILESYSTEM } from '$env/static/public';
|
15 | 15 | import ContextMenu from './ContextMenu.svelte';
|
16 | 16 | import ScreenToggle from './ScreenToggle.svelte';
|
| 17 | + import Modal from '$lib/components/Modal.svelte'; |
17 | 18 |
|
18 | 19 | /** @type {import('./$types').PageData} */
|
19 | 20 | export let data;
|
|
42 | 43 | Object.keys(complete_states).length > 0 && Object.values(complete_states).every(Boolean);
|
43 | 44 |
|
44 | 45 | let path = '/';
|
| 46 | + let modal_text = ''; |
45 | 47 |
|
46 | 48 | let width = browser ? window.innerWidth : 1000;
|
47 | 49 | let selected_view = 0;
|
48 | 50 | $: mobile = width < 768;
|
49 | 51 |
|
50 | 52 | /** @type {Record<string, import('$lib/types').Stub>} */
|
51 | 53 | 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; |
52 | 56 | $: {
|
53 | 57 | b = { ...data.section.a };
|
| 58 | + editing_constraints = { |
| 59 | + create: [], |
| 60 | + remove: [] |
| 61 | + }; |
54 | 62 | for (const key in data.section.b) {
|
55 | 63 | if (key.endsWith('__delete')) {
|
| 64 | + const to_delete = key.slice(0, -'/__delete'.length); |
| 65 | + editing_constraints.remove.push(to_delete); |
56 | 66 | for (const k in b) {
|
57 |
| - if (k.startsWith(key.slice(0, -'/__delete'.length))) { |
| 67 | + if (k.startsWith(to_delete)) { |
58 | 68 | delete b[k];
|
59 | 69 | }
|
60 | 70 | }
|
61 | 71 | } else {
|
| 72 | + if (!b[key]) { |
| 73 | + editing_constraints.create.push(key); |
| 74 | + } |
62 | 75 | b[key] = data.section.b[key];
|
63 | 76 | }
|
64 | 77 | }
|
|
71 | 84 | },
|
72 | 85 |
|
73 | 86 | 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 | +
|
74 | 97 | current_stubs = [...current_stubs, ...stubs];
|
75 | 98 |
|
76 | 99 | await load_files(current_stubs);
|
|
81 | 104 | },
|
82 | 105 |
|
83 | 106 | 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 | +
|
84 | 118 | /** @type {Array<[import('$lib/types').Stub, import('$lib/types').Stub]>}*/
|
85 | 119 | const changed = [];
|
86 | 120 | current_stubs = current_stubs.map((s) => {
|
|
107 | 141 | },
|
108 | 142 |
|
109 | 143 | 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 | +
|
110 | 154 | const out = current_stubs.filter((s) => s.name.startsWith(stub.name));
|
111 | 155 | current_stubs = current_stubs.filter((s) => !out.includes(s));
|
112 | 156 |
|
|
319 | 363 |
|
320 | 364 | <ContextMenu />
|
321 | 365 |
|
| 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 | +
|
322 | 380 | <div class="container" style="--toggle-height: {mobile ? '4.6rem' : '0px'}">
|
323 | 381 | <SplitPane
|
324 | 382 | type="horizontal"
|
|
352 | 410 | files={current_stubs.filter((stub) => !hidden.has(stub.basename))}
|
353 | 411 | expanded
|
354 | 412 | read_only={mobile}
|
| 413 | + can_create={!!editing_constraints.create.length} |
| 414 | + can_remove={!!editing_constraints.remove.length} |
355 | 415 | />
|
356 | 416 | </div>
|
357 | 417 |
|
|
516 | 576 | .hidden {
|
517 | 577 | display: none;
|
518 | 578 | }
|
| 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 | + } |
519 | 594 | </style>
|
0 commit comments