Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve file explorer #2

Closed
wants to merge 10 commits into from
2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_repr = "0.1"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
tauri = { version = "1.2", features = ["dialog-all", "devtools"] }
tauri = { version = "1.2", features = ["devtools", "dialog-all"] }
anyhow = "1.0"
thiserror = "1.0"
enumset = { version = "1.0", features = ["serde"] }
Expand Down
30 changes: 30 additions & 0 deletions src-tauri/src/ipc/commands/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,36 @@ pub async fn fs_create_file<R: Runtime>(
Ok(())
}

#[tauri::command]
pub async fn fs_create_folder<R: Runtime>(
window: Window<R>,
project_manager: State<'_, Arc<ProjectManager<R>>>,
path: PathBuf,
) -> Result<()> {
let (_, path) = project_path(&window, &project_manager, path)?;
// Not sure if there's a scenario where this condition is not met
// unless the project is located at `/`
fs::create_dir_all(path).map_err(Into::<Error>::into)?;

Ok(())
}
#[tauri::command]
pub async fn fs_delete<R: Runtime>(
window: Window<R>,
project_manager: State<'_, Arc<ProjectManager<R>>>,
path: PathBuf,
) -> Result<()> {
let (_, path) = project_path(&window, &project_manager, path)?;
// Not sure if there's a scenario where this condition is not met
// unless the project is located at `/`
if (path).is_file() {
fs::remove_file(path).map_err(Into::<Error>::into)?;
} else {
fs::remove_dir_all(path).map_err(Into::<Error>::into)?;
}

Ok(())
}
#[tauri::command]
pub async fn fs_write_file_binary<R: Runtime>(
window: Window<R>,
Expand Down
4 changes: 3 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ async fn main() {
ipc::commands::fs_read_file_binary,
ipc::commands::fs_read_file_text,
ipc::commands::fs_create_file,
ipc::commands::fs_create_folder,
ipc::commands::fs_delete,
ipc::commands::fs_write_file_binary,
ipc::commands::fs_write_file_text,
ipc::commands::typst_compile,
ipc::commands::typst_render,
ipc::commands::typst_autocomplete,
ipc::commands::clipboard_paste
ipc::commands::clipboard_paste,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
74 changes: 68 additions & 6 deletions src/components/ExplorerNode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,77 @@
import FolderIcon from "./icons/FolderIcon.svelte";
import ArrowDropDownIcon from "./icons/ArrowDropDownIcon.svelte";
import ArrowRightIcon from "./icons/ArrowRightIcon.svelte";
import type { FileItem, FileType, FSRefreshEvent } from "../lib/ipc";
import {
createFile,
createFolder,
type FileItem,
type FileType,
type FSRefreshEvent,
} from "../lib/ipc";
import { project, shell } from "../lib/stores";
import { listDir } from "../lib/ipc";
import { listDir, deleteByPath } from "../lib/ipc";
import { onMount } from "svelte";
import { appWindow } from "@tauri-apps/api/window";
import CreateNewFolder from "./icons/CreateNewFolder.svelte";
import AddIcon from "./icons/AddIcon.svelte";
import FileAdd from "./icons/FileAdd.svelte";
import Delete from "./icons/Delete.svelte";

export let type: FileType;
export let path: string;

let expanded = path === "";
let files: FileItem[] = [];

const handleClick = () => {
const handleClick = (e: MouseEvent) => {
e.stopPropagation();
if (type === "directory") {
expanded = !expanded;
} else {
shell.selectFile(path);
}
};

$: handleCreateFile = (e: MouseEvent) => {
OlshaMB marked this conversation as resolved.
Show resolved Hide resolved
e.stopPropagation();
shell.createModal({
type: "input",
title: "Create file",
initialText: path + "/",
callback: (path) => {
if (path && path !== path + "/") {
expanded = true;
createFile(path);
}
},
});
};
$: handleCreateFolder = (e: MouseEvent) => {
e.stopPropagation();
expanded = true;
shell.createModal({
type: "input",
title: "Create folder",
initialText: path + "/",
callback: (path) => {
if (path) {
expanded = true;
createFolder(path);
}
},
});
};
const deleteEntry = (e: MouseEvent) => {
e.stopPropagation();
shell.createModal({
title: `Delete file ${path}?`,
type: "confirm",
callback: (canceled) => {
if (!canceled) {
deleteByPath(path);
}
},
});
};
const update = async () => {
files = await listDir(path);
};
Expand Down Expand Up @@ -67,8 +118,19 @@
class={clsx("w-4 h-4 inline fill-neutral-500 mr-2", type === "file" && "ml-5")}
/>
<span class="flex-1 truncate">
{path === "" ? "root" : path.slice(path.lastIndexOf("/") + 1)}
</span>
{path === "" ? "root" : path.slice(path.lastIndexOf("/") + 1)}
</span>
<button class="p-1 transition-colors hover:bg-neutral-700" on:click={deleteEntry}>
<Delete class="w-4 h-4" />
</button>
{#if type === "directory"}
<button class="p-1 transition-colors hover:bg-neutral-700" on:click={handleCreateFile}>
<FileAdd class="w-4 h-4" />
</button>
<button class="p-1 transition-colors hover:bg-neutral-700" on:click={handleCreateFolder}>
<CreateNewFolder class="w-4 h-4" />
</button>
{/if}
</div>
{/if}
{#if expanded}
Expand Down
28 changes: 22 additions & 6 deletions src/components/ExplorerTree.svelte
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
<script lang="ts">

import clsx from "clsx";
import ExplorerNode from "./ExplorerNode.svelte";
import AddIcon from "./icons/AddIcon.svelte";
import { project, shell } from "$lib/stores";
import { createFile } from "$lib/ipc";
import { createFile, createFolder } from "$lib/ipc";
import CreateNewFolder from "./icons/CreateNewFolder.svelte";

const handleCreate = () => {
const handleCreateFile = () => {
shell.createModal({
type: "input",
title: "Create file",
callback: (path) => {
if (path) {
createFile(path);
}
}
},
});
};
const handleCreateFolder = () => {
shell.createModal({
type: "input",
title: "Create folder",
callback: (path) => {
if (path) {
createFolder(path);
}
},
});
};
</script>
Expand All @@ -23,11 +34,16 @@
{#if $project}
<div class="flex flex-row mx-2 mt-1 mb-3 items-center">
<span class="text-lg font-bold block flex-1">Project</span>
<div class="flex flex-row rounded-md border border-neutral-700 overflow-clip">
<button class="p-1 transition-colors hover:bg-neutral-700" on:click={handleCreate}>
<div class="flex flex-row rounded-md border border-neutral-700 overflow-clip mr-2">
<button class="p-1 transition-colors hover:bg-neutral-700" on:click={handleCreateFile}>
<AddIcon class="w-4 h-4" />
</button>
</div>
<div class="flex flex-row rounded-md border border-neutral-700 overflow-clip">
<button class="p-1 transition-colors hover:bg-neutral-700" on:click={handleCreateFolder}>
<CreateNewFolder class="w-4 h-4" />
</button>
</div>
</div>
<div class="flex-1 min-h-0 overflow-auto">
<ExplorerNode type="directory" path="" />
Expand Down
21 changes: 20 additions & 1 deletion src/components/ShellModal.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
<script lang="ts">
import { onMount } from "svelte";
import { writable } from "svelte/store";
import type { Modal } from "../lib/stores";
import { shell } from "../lib/stores";
import CloseIcon from "./icons/CloseIcon.svelte";

let modal: Modal | undefined;
$: modal = $shell.modals[0];
$: text = writable(modal?.type === "input" ? modal.initialText : "");

const handleClose = (cancel: boolean = true) => {
if (cancel && modal?.type === "input") {
modal.callback(null);
} else if (cancel && modal?.type === "confirm") {
modal.callback(true);
}
shell.popModal();
};
Expand All @@ -24,6 +29,12 @@
break;
}
};
const handleConfirm = (event: MouseEvent) => {
if (modal?.type === "confirm") {
modal.callback(false);
}
handleClose(false);
};
</script>

{#if modal}
Expand All @@ -45,8 +56,16 @@
<input
class="w-full rounded-md bg-neutral-600 px-2 py-1 mt-2 text-sm"
on:keyup={handleInputKeyUp}
bind:value={$text}
autofocus
>
/>
{:else if modal.type === "confirm"}
<div class="flex justify-between mt-3">
<button on:click={handleClose} class="rounded-md border-2 border-neutral-700 px-2 py-1"
>Cancel</button
>
OlshaMB marked this conversation as resolved.
Show resolved Hide resolved
<button on:click={handleConfirm} class="rounded-md px-5 py-1 bg-red-500">Ok</button>
</div>
{/if}
</div>
</div>
Expand Down
12 changes: 12 additions & 0 deletions src/components/icons/CreateNewFolder.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<svg
xmlns="http://www.w3.org/2000/svg"
height="48"
viewBox="0 96 960 960"
width="48"
class={$$props.class}
stroke-width="3"
fill="currentColor"
><path
d="M570 736h60v-90h90v-60h-90v-90h-60v90h-90v60h90v90ZM141 896q-24 0-42-18.5T81 836V316q0-23 18-41.5t42-18.5h280l60 60h340q23 0 41.5 18.5T881 376v460q0 23-18.5 41.5T821 896H141Zm0-580v520h680V376H456l-60-60H141Zm0 0v520-520Z"
/></svg
>
12 changes: 12 additions & 0 deletions src/components/icons/Delete.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<svg
xmlns="http://www.w3.org/2000/svg"
height="48"
viewBox="0 96 960 960"
width="48"
class={$$props.class}
stroke-width="3"
fill="currentColor"
><path
d="M261 936q-24.75 0-42.375-17.625T201 876V306h-41v-60h188v-30h264v30h188v60h-41v570q0 24-18 42t-42 18H261Zm438-630H261v570h438V306ZM367 790h60V391h-60v399Zm166 0h60V391h-60v399ZM261 306v570-570Z"
/></svg
>
12 changes: 12 additions & 0 deletions src/components/icons/FileAdd.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<svg
xmlns="http://www.w3.org/2000/svg"
height="48"
viewBox="0 96 960 960"
width="48"
class={$$props.class}
stroke-width="3"
fill="currentColor"
><path
d="M450 822h60V693h130v-60H510V503h-60v130H320v60h130v129ZM220 976q-24 0-42-18t-18-42V236q0-24 18-42t42-18h361l219 219v521q0 24-18 42t-42 18H220Zm331-554V236H220v680h520V422H551ZM220 236v186-186 680-680Z"
/></svg
>
3 changes: 3 additions & 0 deletions src/lib/ipc/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export const readFileText = (path: string): Promise<string> =>
invoke<string>("fs_read_file_text", { path });

export const createFile = (path: string): Promise<never> => invoke("fs_create_file", { path });
export const createFolder = (path: string): Promise<never> => invoke("fs_create_folder", { path });
export const deleteByPath = (path: string): Promise<never> => invoke("fs_delete", { path });

OlshaMB marked this conversation as resolved.
Show resolved Hide resolved

export const writeFileText = (path: string, content: string): Promise<string> =>
invoke("fs_write_file_text", { path, content });
Expand Down
8 changes: 6 additions & 2 deletions src/lib/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ export interface BaseModal {
export interface InputModal extends BaseModal {
type: "input";
placeholder?: string;
initialText?: string;
callback: (content: string | null) => void;
}

OlshaMB marked this conversation as resolved.
Show resolved Hide resolved
export type Modal = InputModal;
export interface ConfirmModal extends BaseModal {
type: "confirm",
callback: (canceled: boolean) => void
}
export type Modal = InputModal | ConfirmModal;

const createShell = () => {
const { subscribe, set, update } = writable<Shell>({
Expand Down