Added file deletion safety net
mellobacon committed
Dec 18, 2022 at 19:13 UTC
ee54e7a29b6a421de27ae088478b778b29c55cd7
9 files changed
+357
-11
src-tauri/Cargo.lock
+184
-3
@@ -32,6 +32,15 @@ dependencies = [
32
"alloc-no-stdlib",
33
]
34
35
+[[package]]
36
+name = "android_system_properties"
37
+version = "0.1.5"
38
+source = "registry+https://github.com/rust-lang/crates.io-index"
39
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
40
+dependencies = [
41
+ "libc",
42
+]
43
+
44
[[package]]
45
name = "anyhow"
46
version = "1.0.66"
@@ -48,6 +57,7 @@ dependencies = [
57
"serde_json",
58
"tauri",
59
"tauri-build",
60
+ "trash",
61
"windows 0.32.0",
62
]
63
@@ -264,6 +274,21 @@ version = "1.0.0"
274
source = "registry+https://github.com/rust-lang/crates.io-index"
275
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
276
277
+[[package]]
278
+name = "chrono"
279
+version = "0.4.23"
280
+source = "registry+https://github.com/rust-lang/crates.io-index"
281
+checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
282
+dependencies = [
283
+ "iana-time-zone",
284
+ "js-sys",
285
+ "num-integer",
286
+ "num-traits",
287
+ "time 0.1.45",
288
+ "wasm-bindgen",
289
+ "winapi 0.3.9",
290
+]
291
+
292
[[package]]
293
name = "cmake"
294
version = "0.1.49"
@@ -304,6 +329,16 @@ dependencies = [
329
"objc",
330
]
331
332
+[[package]]
333
+name = "codespan-reporting"
334
+version = "0.11.1"
335
+source = "registry+https://github.com/rust-lang/crates.io-index"
336
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
337
+dependencies = [
338
+ "termcolor",
339
+ "unicode-width",
340
+]
341
+
342
[[package]]
343
name = "color_quant"
344
version = "1.1.0"
@@ -469,6 +504,50 @@ version = "0.2.2"
504
source = "registry+https://github.com/rust-lang/crates.io-index"
505
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
506
507
+[[package]]
508
+name = "cxx"
509
+version = "1.0.85"
510
+source = "registry+https://github.com/rust-lang/crates.io-index"
511
+checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd"
512
+dependencies = [
513
+ "cc",
514
+ "cxxbridge-flags",
515
+ "cxxbridge-macro",
516
+ "link-cplusplus",
517
+]
518
+
519
+[[package]]
520
+name = "cxx-build"
521
+version = "1.0.85"
522
+source = "registry+https://github.com/rust-lang/crates.io-index"
523
+checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0"
524
+dependencies = [
525
+ "cc",
526
+ "codespan-reporting",
527
+ "once_cell",
528
+ "proc-macro2",
529
+ "quote",
530
+ "scratch",
531
+ "syn",
532
+]
533
+
534
+[[package]]
535
+name = "cxxbridge-flags"
536
+version = "1.0.85"
537
+source = "registry+https://github.com/rust-lang/crates.io-index"
538
+checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59"
539
+
540
+[[package]]
541
+name = "cxxbridge-macro"
542
+version = "1.0.85"
543
+source = "registry+https://github.com/rust-lang/crates.io-index"
544
+checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
545
+dependencies = [
546
+ "proc-macro2",
547
+ "quote",
548
+ "syn",
549
+]
550
+
551
[[package]]
552
name = "darling"
553
version = "0.13.4"
@@ -1161,6 +1240,30 @@ version = "0.1.5"
1240
source = "registry+https://github.com/rust-lang/crates.io-index"
1241
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
1242
1243
+[[package]]
1244
+name = "iana-time-zone"
1245
+version = "0.1.53"
1246
+source = "registry+https://github.com/rust-lang/crates.io-index"
1247
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
1248
+dependencies = [
1249
+ "android_system_properties",
1250
+ "core-foundation-sys",
1251
+ "iana-time-zone-haiku",
1252
+ "js-sys",
1253
+ "wasm-bindgen",
1254
+ "winapi 0.3.9",
1255
+]
1256
+
1257
+[[package]]
1258
+name = "iana-time-zone-haiku"
1259
+version = "0.1.1"
1260
+source = "registry+https://github.com/rust-lang/crates.io-index"
1261
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
1262
+dependencies = [
1263
+ "cxx",
1264
+ "cxx-build",
1265
+]
1266
+
1267
[[package]]
1268
name = "ico"
1269
version = "0.2.0"
@@ -1408,6 +1511,15 @@ dependencies = [
1511
"safemem",
1512
]
1513
1514
+[[package]]
1515
+name = "link-cplusplus"
1516
+version = "1.0.8"
1517
+source = "registry+https://github.com/rust-lang/crates.io-index"
1518
+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
1519
+dependencies = [
1520
+ "cc",
1521
+]
1522
+
1523
[[package]]
1524
name = "lock_api"
1525
version = "0.4.9"
@@ -1458,7 +1570,7 @@ dependencies = [
1570
"dirs-next",
1571
"objc-foundation",
1572
"objc_id",
1461
- "time",
1573
+ "time 0.3.17",
1574
]
1575
1576
[[package]]
@@ -2064,7 +2176,7 @@ dependencies = [
2176
"indexmap",
2177
"line-wrap",
2178
"serde",
2067
- "time",
2179
+ "time 0.3.17",
2180
"xml-rs",
2181
]
2182
@@ -2396,6 +2508,12 @@ version = "1.1.0"
2508
source = "registry+https://github.com/rust-lang/crates.io-index"
2509
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
2510
2511
+[[package]]
2512
+name = "scratch"
2513
+version = "1.0.3"
2514
+source = "registry+https://github.com/rust-lang/crates.io-index"
2515
+checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
2516
+
2517
[[package]]
2518
name = "security-framework"
2519
version = "2.7.0"
@@ -2926,7 +3044,7 @@ dependencies = [
3044
"sha2",
3045
"tauri-utils",
3046
"thiserror",
2929
- "time",
3047
+ "time 0.3.17",
3048
"uuid 1.2.2",
3049
"walkdir",
3050
]
@@ -3049,6 +3167,15 @@ dependencies = [
3167
"utf-8",
3168
]
3169
3170
+[[package]]
3171
+name = "termcolor"
3172
+version = "1.1.3"
3173
+source = "registry+https://github.com/rust-lang/crates.io-index"
3174
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
3175
+dependencies = [
3176
+ "winapi-util",
3177
+]
3178
+
3179
[[package]]
3180
name = "thin-slice"
3181
version = "0.1.1"
@@ -3084,6 +3211,17 @@ dependencies = [
3211
"once_cell",
3212
]
3213
3214
+[[package]]
3215
+name = "time"
3216
+version = "0.1.45"
3217
+source = "registry+https://github.com/rust-lang/crates.io-index"
3218
+checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
3219
+dependencies = [
3220
+ "libc",
3221
+ "wasi 0.10.0+wasi-snapshot-preview1",
3222
+ "winapi 0.3.9",
3223
+]
3224
+
3225
[[package]]
3226
name = "time"
3227
version = "0.3.17"
@@ -3210,6 +3348,22 @@ dependencies = [
3348
"tracing-log",
3349
]
3350
3351
+[[package]]
3352
+name = "trash"
3353
+version = "3.0.0"
3354
+source = "registry+https://github.com/rust-lang/crates.io-index"
3355
+checksum = "6f74274f95c7e7340d7c5bcd4863af87a9ed6a117cf73cf483c71cb4d744a948"
3356
+dependencies = [
3357
+ "chrono",
3358
+ "libc",
3359
+ "log",
3360
+ "objc",
3361
+ "once_cell",
3362
+ "scopeguard",
3363
+ "url",
3364
+ "windows 0.43.0",
3365
+]
3366
+
3367
[[package]]
3368
name = "treediff"
3369
version = "3.0.2"
@@ -3258,6 +3412,12 @@ version = "1.10.0"
3412
source = "registry+https://github.com/rust-lang/crates.io-index"
3413
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
3414
3415
+[[package]]
3416
+name = "unicode-width"
3417
+version = "0.1.10"
3418
+source = "registry+https://github.com/rust-lang/crates.io-index"
3419
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
3420
+
3421
[[package]]
3422
name = "url"
3423
version = "2.3.1"
@@ -3338,6 +3498,12 @@ version = "0.9.0+wasi-snapshot-preview1"
3498
source = "registry+https://github.com/rust-lang/crates.io-index"
3499
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
3500
3501
+[[package]]
3502
+name = "wasi"
3503
+version = "0.10.0+wasi-snapshot-preview1"
3504
+source = "registry+https://github.com/rust-lang/crates.io-index"
3505
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
3506
+
3507
[[package]]
3508
name = "wasi"
3509
version = "0.11.0+wasi-snapshot-preview1"
@@ -3588,6 +3754,21 @@ dependencies = [
3754
"windows_x86_64_msvc 0.39.0",
3755
]
3756
3757
+[[package]]
3758
+name = "windows"
3759
+version = "0.43.0"
3760
+source = "registry+https://github.com/rust-lang/crates.io-index"
3761
+checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244"
3762
+dependencies = [
3763
+ "windows_aarch64_gnullvm",
3764
+ "windows_aarch64_msvc 0.42.0",
3765
+ "windows_i686_gnu 0.42.0",
3766
+ "windows_i686_msvc 0.42.0",
3767
+ "windows_x86_64_gnu 0.42.0",
3768
+ "windows_x86_64_gnullvm",
3769
+ "windows_x86_64_msvc 0.42.0",
3770
+]
3771
+
3772
[[package]]
3773
name = "windows-bindgen"
3774
version = "0.39.0"
src-tauri/Cargo.toml
+1
@@ -19,6 +19,7 @@ serde = { version = "1.0", features = ["derive"] }
19
tauri = { version = "1.1", features = ["api-all"] }
20
notify = "4.0.0"
21
font-loader = "0.11.0"
22
+trash = "3.0.0"
23
24
[dev-dependencies]
25
windows = "0.32.0"
src-tauri/src/main.rs
+6
-1
@@ -48,9 +48,14 @@ fn get_installed_fonts() -> Vec<String> {
48
system_fonts::query_all()
49
}
50
51
+#[tauri::command]
52
+fn delete_file(path: &str) {
53
+ trash::delete(path).unwrap();
54
+}
55
+
56
fn main() {
57
tauri::Builder::default()
53
- .invoke_handler(tauri::generate_handler![open_in_explorer, open_terminal, get_installed_fonts])
58
+ .invoke_handler(tauri::generate_handler![open_in_explorer, open_terminal, get_installed_fonts, delete_file])
59
.run(tauri::generate_context!())
60
.expect("error while running tauri application");
61
}
src/components/FileTree/LeafNodeMenu.svelte
+26
-2
@@ -5,11 +5,16 @@
5
import CopyFile from "carbon-icons-svelte/lib/CopyFile.svelte";
6
import Cut from "carbon-icons-svelte/lib/Cut.svelte";
7
import Script from "carbon-icons-svelte/lib/Script.svelte";
8
- import { fs, clipboard, path, invoke } from "@tauri-apps/api";
8
+ import { clipboard, invoke } from "@tauri-apps/api";
9
+ import { updateTree } from "./scripts/TreeData";
10
+ import { deleteFile } from "../../scripts/EditorFile";
11
+ import Model from "../Modal/Model.svelte";
12
+ import { closeActiveTab } from "../Tabs/scripts/Tab";
13
export let target;
14
export let filename;
15
export let filepath;
16
let open;
17
+ let deleteopen;
18
19
async function copyToClipboard(input) {
20
await clipboard.writeText(input);
@@ -44,7 +49,9 @@
49
50
<ContextMenuDivider></ContextMenuDivider>
51
<ContextMenuOption labelText="Delete..." on:click={ async() => {
47
- await fs.removeFile(filepath);
52
+ //deleteFile(filepath);
53
+ //await updateTree();
54
+ deleteopen = true;
55
}}>
56
<span class="contextshortcut" slot="shortcutText">Delete</span>
57
</ContextMenuOption>
@@ -53,6 +60,23 @@
60
{#if open}
61
<RenameModel bind:open bind:filename={filename} bind:path={filepath} />
62
{/if}
63
+{#if deleteopen}
64
+<Model bind:open={deleteopen} heading="Delete File" description="Are you sure you want to delete {filename}? Deleted files are put in your recycling bin." size="sm" buttons={[
65
+ {name: "Move to recycle bin", action: async () => {
66
+ await deleteFile(filepath);
67
+ await updateTree();
68
+ closeActiveTab(filepath);
69
+ deleteopen = false;
70
+ }},
71
+ {name: "Delete permanently", title: "Delete file permanently off of OS. Does not go into the recycling bin and cannot be undone", danger: true, action: async () => {
72
+ await deleteFile(filepath, true);
73
+ await updateTree();
74
+ closeActiveTab(filepath);
75
+ deleteopen = false;
76
+ }}
77
+]}
78
+></Model>
79
+{/if}
80
81
<style>
82
.contextshortcut {
src/components/FileTree/ParentNodeMenu.svelte
+30
-2
@@ -5,11 +5,17 @@
5
import Paste from "carbon-icons-svelte/lib/Paste.svelte";
6
import Cut from "carbon-icons-svelte/lib/Cut.svelte";
7
import RenameModel from "../Modal/RenameModel.svelte";
8
- import { fs, clipboard, invoke, path } from "@tauri-apps/api";
8
+ import { clipboard, invoke } from "@tauri-apps/api";
9
+ import { updateTree } from "./scripts/TreeData";
10
+ import Model from "../Modal/Model.svelte";
11
+ import { deleteDir } from "../../scripts/EditorFile";
12
+ import { closeActiveTab } from "../Tabs/scripts/Tab";
13
export let target;
14
export let filename;
15
export let filepath;
16
+ export let children = [];
17
let open;
18
+ let deleteopen;
19
20
async function copyToClipboard(input) {
21
await clipboard.writeText(input);
@@ -45,7 +51,8 @@
51
}}></ContextMenuOption>
52
<ContextMenuDivider></ContextMenuDivider>
53
<ContextMenuOption labelText="Delete..." on:click={ async() => {
48
- await fs.removeDir(filepath, {recursive: true});
54
+ deleteopen = true;
55
+ await updateTree();
56
}}>
57
<span class="contextshortcut" slot="shortcutText">Delete</span>
58
</ContextMenuOption>
@@ -54,6 +61,27 @@
61
{#if open}
62
<RenameModel bind:open bind:filename={filename} bind:path={filepath} />
63
{/if}
64
+{#if deleteopen}
65
+<Model bind:open={deleteopen} heading="Delete Directory" description="Are you sure you want to delete {filename} and all its contents? Deleted files are put in your recycling bin." size="sm" buttons={[
66
+ {name: "Move to recycle bin", action: async () => {
67
+ await deleteDir(filepath);
68
+ await updateTree();
69
+ for (const child of children) {
70
+ closeActiveTab(child.path);
71
+ }
72
+ deleteopen = false;
73
+ }},
74
+ {name: "Delete permanently", title: "Delete file permanently off of OS. Does not go into the recycling bin and cannot be undone", danger: true, action: async () => {
75
+ await deleteDir(filepath, true);
76
+ await updateTree();
77
+ for (const child of children) {
78
+ closeActiveTab(child.path);
79
+ }
80
+ deleteopen = false;
81
+ }}
82
+]}
83
+></Model>
84
+{/if}
85
86
<style>
87
.contextshortcut {
src/components/FileTree/TreeViewNodeList.svelte
+1
-1
@@ -164,7 +164,7 @@
164
{/if}
165
166
{#if contextmenu}
167
-<ParentNodeMenu target={[refLabel]} filename={name} filepath={path}></ParentNodeMenu>
167
+<ParentNodeMenu target={[refLabel]} children={children} filename={name} filepath={path}></ParentNodeMenu>
168
{/if}
169
170
<style>
src/components/Modal/Model.svelte
new
+80
@@ -0,0 +1,80 @@
1
+<script lang="ts">
2
+ import { Modal } from "carbon-components-svelte";
3
+ export let open = false;
4
+ export let heading = "";
5
+ export let description = "";
6
+ export let buttons = [];
7
+ export let size: "xs" | "sm" | "lg" = undefined;
8
+</script>
9
+
10
+<Modal
11
+ preventCloseOnClickOutside
12
+ bind:open
13
+ modalHeading={heading}
14
+ size={size}
15
+ on:open
16
+ on:close
17
+ passiveModal
18
+>
19
+ <div class="modelcontent">
20
+ <div class="description">
21
+ <p>{description}</p>
22
+ </div>
23
+ <div id="content">
24
+ <slot></slot>
25
+ </div>
26
+ <div class="buttongroup">
27
+ {#each buttons as button}
28
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
29
+ <div class="button" class:disabled={button.disabled} class:danger={button.danger} on:click={button.action} title={button.title}>{button.name}</div>
30
+ {/each}
31
+ </div>
32
+ </div>
33
+</Modal>
34
+
35
+<style lang="scss">
36
+ .modelcontent {
37
+ display: flex;
38
+ flex-direction: column;
39
+ justify-content: center;
40
+ }
41
+ .description {
42
+ width: 100%;
43
+ }
44
+ #content {
45
+ display: flex;
46
+ }
47
+ .button {
48
+ min-width: 80px;
49
+ height: 35px;
50
+ border-radius: 1px;
51
+ margin: 0 5px 0 5px;
52
+ display: flex;
53
+ justify-content: center;
54
+ align-items: center;
55
+ background-color: #2276b2;
56
+ color: #f9f9f9;
57
+ cursor: pointer;
58
+ padding: 0 5px;
59
+ &.danger {
60
+ background-color: #b22222 !important;
61
+ &:hover {
62
+ background-color: #8a1919 !important;
63
+ }
64
+ }
65
+ }
66
+ .button:hover {
67
+ background-color: #195b8a;
68
+ }
69
+ .buttongroup {
70
+ width: 100%;
71
+ display: flex;
72
+ justify-content: center;
73
+ margin-top: 40px;
74
+ }
75
+ .disabled {
76
+ background-color: #a9a9a9;
77
+ color: #7a7a7a;
78
+ cursor: not-allowed;
79
+ }
80
+</style>
src/components/Tabs/scripts/Tab.ts
+6
@@ -109,6 +109,12 @@ export function closeTab(tabid: number) {
109
id = 0;
110
}
111
}
112
+export function closeActiveTab(path) {
113
+ for (const tab of tablist) {
114
+ if (tab.isfile && tab.path === path)
115
+ closeTab(tab.id);
116
+ }
117
+}
118
export function closeAllTabs() {
119
for (const tab of tablist) {
120
closeTab(tab.id);
src/scripts/EditorFile.ts
+23
-2
@@ -1,8 +1,10 @@
1
-import { readBinaryFile, readTextFile, writeFile } from "@tauri-apps/api/fs";
1
+import { readTextFile } from "@tauri-apps/api/fs";
2
import { extname, sep } from "@tauri-apps/api/path";
3
-import { dialog, fs } from "@tauri-apps/api";
3
+import { dialog, fs, path } from "@tauri-apps/api";
4
import { setActive, tablist } from "../components/Tabs/scripts/Tab";
5
import { getLang } from "../components/Editor/scripts/Editor";
6
+import { updateTree } from "../components/FileTree/scripts/TreeData";
7
+import { invoke } from "@tauri-apps/api/tauri";
8
9
export class EditorFile {
10
filename: string;
@@ -66,6 +68,7 @@ export async function saveFile() {
68
tab.path = path;
69
tab.saved = true;
70
setActive(tab.id);
71
+ await updateTree();
72
}
73
else {
74
tab.saved = true;
@@ -89,6 +92,7 @@ export async function saveFileAs() {
92
tab.path = path;
93
tab.saved = !tab.saved;
94
setActive(tab.id);
95
+ await updateTree();
96
break;
97
}
98
}
@@ -125,3 +129,20 @@ export function setFileLanguage(lang, mode = null) {
129
}
130
}
131
}
132
+
133
+export async function deleteFile(filepath, force = false) {
134
+ if (force) {
135
+ await fs.removeFile(filepath);
136
+ }
137
+ else {
138
+ await invoke("delete_file", {path: filepath});
139
+ }
140
+}
141
+export async function deleteDir(filepath, force = false) {
142
+ if (force) {
143
+ await fs.removeDir(filepath, {recursive: true});
144
+ }
145
+ else {
146
+ await invoke("delete_file", {path: filepath});
147
+ }
148
+}
\ No newline at end of file