@setoelkahfi / svara / commits / ee54e7a

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