@setoelkahfi / svara / commits / 0fb423e

fix: added proper file name handling

Renaming/creating a file with an invalid name should no longer create an empty file in the editor

mellbacon committed Oct 8, 2023 at 20:30 UTC 0fb423e437fe3c80dc3efc51b1777c382ff83560
8 files changed +73 -21
src/App.svelte
+1 -1
@@ -68,7 +68,7 @@
68 }
69 </script>
70 <script lang="ts" context="module">
71 - const _openPopup = writable(false);
71 + export const _openPopup = writable(false);
72 const popupProps = writable({});
73 const popup = writable(null);
74 export const fullscreen = writable(false);
src/config/commands.ts
+1 -1
@@ -196,7 +196,7 @@ export const commands = {
196 openRenameModal(`Rename ${filename}`,
197 `Give a new name to ${filename}`, [
198 {name: "Rename", action: async (name) => {await renameFile(name, oldpath)}},
199 - {name: "Cancel", action: () => {}}
199 + {name: "Cancel", cancel: true, action: () => {}}
200 ])
201 }
202 },
src/lib/FileTree.svelte
+2 -2
@@ -30,12 +30,12 @@
30 {name: "New Folder...", shortcut: "", action: () => {openInputModal("Create New Folder",
31 `Create a new folder in ${path}`, [
32 {name: "Create Folder", action: async (name) => { await createFolder(`${path}${p.sep}${name}`)}},
33 - {name: "Cancel", action: () => {}}
33 + {name: "Cancel", cancel: true, action: () => {}}
34 ], {label: "Folder Name"})}},
35 {name: "New File...", shortcut: "", action: () => {openInputModal("Create New File",
36 `Create a new file in ${path}`, [
37 {name: "Create File", action: (name) => {createFile(`${path}${p.sep}${name}`)}},
38 - {name: "Cancel", action: () => {}}
38 + {name: "Cancel", cancel: true, action: () => {}}
39 ], {label: "File Name"})}},
40 {name: "Copy", shortcut: "Ctrl + C", action: () => {console.warn("Feature not implemented yet.")}},
41 {name: "Cut", disabled: true, shortcut: "Ctrl + X", action: () => {console.warn("Feature not implemented yet.")}},
src/lib/FileTree/FileTreeList.svelte
+2 -2
@@ -42,12 +42,12 @@
42 {name: "New Folder...", shortcut: "", action: () => {openInputModal("Create New Folder",
43 `Create a new folder in ${path}`, [
44 {name: "Create Folder", action: async (name) => { await createFolder(`${path}${p.sep}${name}`)}},
45 - {name: "Cancel", action: () => {}}
45 + {name: "Cancel", cancel: true, action: () => {}}
46 ], {label: "Folder Name"})}},
47 {name: "New File...", shortcut: "", action: () => {openInputModal("Create New File",
48 `Create a new file in ${path}`, [
49 {name: "Create File", action: (name) => {createFile(`${path}${p.sep}${name}`)}},
50 - {name: "Cancel", action: () => {}}
50 + {name: "Cancel", cancel: true, action: () => {}}
51 ], {label: "File Name"})}},
52 {name: "Copy", shortcut: "Ctrl + C", action: () => {console.warn("Feature not implemented yet.")}},
53 {name: "Cut", disabled: isroot, shortcut: "Ctrl + X", action: () => {console.warn("Feature not implemented yet.")}},
src/lib/Modals/InputModal.svelte
+20 -8
@@ -2,13 +2,16 @@
2 import { onMount } from "svelte";
3 import Input from "../utility/Input.svelte";
4 import Popup from "../utility/Popup.svelte";
5 + import { _openPopup } from "../../App.svelte";
6 + import { checkValidFileName } from "../File";
7
8 export let title;
9 export let buttons;
10 export let description = "";
11 export let options = {label: null, placeholder: "Enter text..."};
10 - export let open = false;
11 -
12 + let invalid = false;
13 + let helpText = ""
14 + let invalidText = "";
15
16 onMount(() => {
17 if (!options.placeholder) {
@@ -17,17 +20,26 @@
20 })
21
22 let value = "";
20 - async function handleButtonClick(buttonAction) {
23 + async function handleButtonClick(buttonAction, cancel) {
24 await buttonAction(value);
22 - open = false;
25 + if (!invalid || cancel) {
26 + _openPopup.set(false);
27 + }
28 }
29
30 </script>
26 -<Popup bind:open {title} {description}>
27 - <Input bind:value label={options.label} placeholder={options.placeholder} />
31 +<Popup bind:open={$_openPopup} {title} {description}>
32 + <Input bind:value bind:invalid label={options.label} placeholder={options.placeholder} hintText={helpText} invalidText={invalidText} />
33 <svelte:fragment slot="buttons">
34 {#each buttons as button}
30 - <button on:click={async () => {await handleButtonClick(button.action)}}>{button.name}</button>
35 + <button on:click={async () => {
36 + if (!button.cancel && (!checkValidFileName(value) || value === undefined || !value)) {
37 + invalid = true;
38 + invalidText = `{${value === "" ? "null" : value}} is not a valid file or directory name`;
39 + return;
40 + }
41 + await handleButtonClick(button.action, button.cancel);
42 + }}>{button.name}</button>
43 {/each}
44 </svelte:fragment>
33 -</Popup>
\ No newline at end of file
45 +</Popup>
src/lib/Modals/RenameModal.svelte
+19 -6
@@ -2,12 +2,16 @@
2 import { onMount } from "svelte";
3 import Input from "../utility/Input.svelte";
4 import Popup from "../utility/Popup.svelte";
5 + import { _openPopup } from "../../App.svelte";
6 + import { checkValidFileName } from "../File";
7
8 export let title;
9 export let buttons;
10 export let description = "";
11 export let options = {label: "Name: ", placeholder: "Enter text..."};
10 - export let open = false;
12 + let invalid = false;
13 + let helpText = ""
14 + let invalidText = "";
15
16 onMount(() => {
17 if (!options.placeholder) {
@@ -16,21 +20,30 @@
20 })
21
22 let value = "";
19 - async function handleButtonClick(buttonAction) {
23 + async function handleButtonClick(buttonAction, cancel) {
24 await buttonAction(value);
21 - open = false;
25 + if (!invalid || cancel) {
26 + _openPopup.set(false);
27 + }
28 }
29
30 </script>
25 -<Popup bind:open {title} {description}>
31 +<Popup bind:open={$_openPopup} {title} {description}>
32 <div class="rename-input">
27 - <Input bind:value label={"File Name"} placeholder={"Enter name..."} />
33 + <Input bind:value bind:invalid label={"File Name"} placeholder={"Enter name..."} hintText={helpText} invalidText={invalidText} />
34 <div class="divider"></div>
35 <Input label={"File Type"} placeholder={"-"} readonly _class="ext" />
36 </div>
37 <svelte:fragment slot="buttons">
38 {#each buttons as button}
33 - <button on:click={async () => {await handleButtonClick(button.action)}}>{button.name}</button>
39 + <button on:click={async () => {
40 + if (!button.cancel && (!checkValidFileName(value) || value === undefined || !value)) {
41 + invalid = true;
42 + invalidText = `{${value === "" ? "null" : value}} is not a valid file name`;
43 + return;
44 + }
45 + await handleButtonClick(button.action, button.cancel);
46 + }}>{button.name}</button>
47 {/each}
48 </svelte:fragment>
49 </Popup>
src/lib/utility/Input.svelte
+25 -1
@@ -5,15 +5,19 @@
5 export let readonly = false;
6 export let label = null;
7 export let placeholder = "";
8 + export let hintText = "";
9 + export let invalidText = "";
10
11 export let value = "";
12 export let _class = "";
13 export let extra_small = false;
14 export let medium = false;
15 + export let invalid = false;
16
17 let _ = null;
18 async function handleInput(e) {
19 clearTimeout(_);
20 + invalid = false;
21 _ = setTimeout(() => {
22 dispatch("d_input", {value: value})
23 }, 500);
@@ -25,10 +29,23 @@
29 <label for="textInput">{label}:</label>
30 {/if}
31
28 - <input on:input={handleInput} class="mousetrap" class:medium class:extra_small class:readonly bind:value type="text" name="textInput" id="textInput" readonly={readonly} placeholder={placeholder}>
32 + <input on:input={handleInput}
33 + class="mousetrap"
34 + class:invalid
35 + class:medium
36 + class:extra_small
37 + class:readonly
38 + bind:value type="text" name="textInput" id="textInput" readonly={readonly} placeholder={placeholder}>
39 + {#if hintText && !invalid}
40 + <div class="hint-text" >{hintText}</div>
41 + {/if}
42 + {#if invalidText && invalid}
43 + <div class="hint-text" class:invalid>{invalidText}</div>
44 + {/if}
45 </div>
46
47 <style lang="scss">
48 + $invalid-color: #fa4d56;
49 .input-container {
50 display: flex;
51 flex-direction: column;
@@ -50,6 +67,13 @@
67 padding: 0.6em 0.4em 0.6em 0.8em;
68 width: 100%;
69 }
70 + .hint-text {
71 + padding: 0 2px;
72 + font-size: 0.9rem;
73 + &.invalid {
74 + color: $invalid-color;
75 + }
76 + }
77 .readonly {
78 cursor: not-allowed;
79 background-color: #2d2d2d;
src/theme.scss
+3
@@ -104,6 +104,9 @@ input {
104 border-bottom: 1px solid var(--window-inputBorder) !important;
105 background-color: var(--window-inputBackground);
106 color: var(--window-inputForeground);
107 + &.invalid {
108 + border-bottom: 1px solid #fa4d56 !important;
109 + }
110 }
111
112 .divider {