Skip to content

Commit

Permalink
basic file operations
Browse files Browse the repository at this point in the history
  • Loading branch information
hill committed Nov 12, 2024
1 parent 697767f commit 508a628
Show file tree
Hide file tree
Showing 16 changed files with 239 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"],
}
36 changes: 36 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": []
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"useAltText": "off",
"noSvgWithoutTitle": "off",
"useKeyWithClickEvents": "off"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}
Binary file modified bun.lockb
Binary file not shown.
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,22 @@
"@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-fs": "~2",
"@tauri-apps/plugin-shell": "^2",
"antd": "^5.22.0",
"clsx": "^2.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.27.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@tauri-apps/cli": "^2",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.14",
"typescript": "^5.2.2",
"vite": "^5.3.1",
"@tauri-apps/cli": "^2"
"vite": "^5.3.1"
}
}
6 changes: 6 additions & 0 deletions postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
15 changes: 10 additions & 5 deletions src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": [
"main"
],
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open",
"dialog:default",
"fs:allow-read-file",
"fs:allow-read-text-file",
{
"identifier": "fs:allow-read-dir",
"allow": [{ "path": "**" }]
},
{
"identifier": "fs:allow-read-text-file",
"allow": [{ "path": "**" }]
},
"fs:allow-open"
]
}
}
Empty file removed src/api/fs.ts
Empty file.
29 changes: 29 additions & 0 deletions src/assets/icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { SVGProps } from "react";

function createIcon(svgPath: string) {
return function Icon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
aria-hidden="true"
focusable="false"
viewBox="0 0 16 16"
width="16"
height="16"
{...props}
>
<path d={svgPath} />
</svg>
);
};
}
export const DirectoryIcon = createIcon(
"M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25v-8.5A1.75 1.75 0 0 0 14.25 3H7.5a.25.25 0 0 1-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75Z",
);
export const DirectoryOpenIcon = createIcon(
"M.513 1.513A1.75 1.75 0 0 1 1.75 1h3.5c.55 0 1.07.26 1.4.7l.9 1.2a.25.25 0 0 0 .2.1H13a1 1 0 0 1 1 1v.5H2.75a.75.75 0 0 0 0 1.5h11.978a1 1 0 0 1 .994 1.117L15 13.25A1.75 1.75 0 0 1 13.25 15H1.75A1.75 1.75 0 0 1 0 13.25V2.75c0-.464.184-.91.513-1.237Z",
);
export const FileIcon = createIcon(
"M2 1.75C2 .784 2.784 0 3.75 0h6.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16h-9.5A1.75 1.75 0 0 1 2 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h9.5a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 9 4.25V1.5Zm6.75.062V4.25c0 .138.112.25.25.25h2.688l-.011-.013-2.914-2.914-.013-.011Z",
);
5 changes: 5 additions & 0 deletions src/assets/icons/directory.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/assets/icons/file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions src/components/FilePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { DirEntry } from "@tauri-apps/plugin-fs";
import clsx from "clsx";
import { DirectoryIcon, FileIcon } from "../assets/icons";

export function FilePicker({
entries,
onSelectFile,
className,
}: {
entries: DirEntry[];
onSelectFile: (entry: DirEntry) => void;
className?: string;
}) {
return (
<ul className={clsx("w-full", className)}>
{entries.map((entry) => (
<FileRow key={entry.name} entry={entry} onClick={onSelectFile} />
))}
</ul>
);
}

function FileRow({
entry,
onClick,
}: { entry: DirEntry; onClick?: (entry: DirEntry) => void }) {
return (
<li
className="flex items-center p-1 px-2 rounded-md text-xs hover:bg-neutral-200 cursor-pointer"
onClick={() => onClick?.(entry)}
>
{!entry.isDirectory ? (
<FileIcon className="mr-2 fill-[#59636E]" />
) : (
<DirectoryIcon className="mr-2 fill-[#54AEFF]" />
)}
<span className="truncate cursor-pointer">{entry.name}</span>
</li>
);
}
10 changes: 10 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

html,
body {
overscroll-behavior-y: none; /* Stop rubber banding */
overscroll-behavior-x: none; /* Stop rubber banding */
user-select: none; /* Disable text selection */
}
File renamed without changes.
6 changes: 2 additions & 4 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";

import {
createMemoryRouter,
RouterProvider,
} from "react-router-dom";
import { RouterProvider, createMemoryRouter } from "react-router-dom";
import FileSystemView from "./views/FileSystemView";

const router = createMemoryRouter([
Expand Down
103 changes: 78 additions & 25 deletions src/views/FileSystemView.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,98 @@
import { open } from '@tauri-apps/plugin-dialog';
import { readTextFile } from '@tauri-apps/plugin-fs';
import { useState } from 'react';
import { open } from "@tauri-apps/plugin-dialog";
import { type DirEntry, readDir, readTextFile } from "@tauri-apps/plugin-fs";
import { Button } from "antd";
import { useEffect, useState } from "react";
import { FilePicker } from "../components/FilePicker";

export default function FileSystemView() {

const [currentPath, setCurrentPath] = useState<string>(
"/Users/tomhill/Developer/projects/DNAvigate/examples",
);
const [fileContents, setFileContents] = useState<string | null>(null);
const [entries, setEntries] = useState<DirEntry[]>([]);

const handlePick = (entry: DirEntry) => {
if (entry.name === "..") {
setCurrentPath(currentPath.replace(/\/[^/]+$/, ""));
return;
}

if (entry.isDirectory) {
setCurrentPath(`${currentPath}/${entry.name}`);
} else {
handleSelectFile(`${currentPath}/${entry.name}`);
}
};

const handleFileOpen = async () => {
const path = await open({
multiple: false,
directory: false,
title: 'Open File',
filters: [{
name: 'Text Files',
extensions: ['txt']
}, {
name: "FASTA Files",
extensions: ["fasta", "fa", "fasta.gz", "fa.gz"]
}, {
name: "FASTQ Files",
extensions: ["fastq", "fq", "fastq.gz", "fq.gz"]
}]
title: "Open File",
filters: [
{
name: "Text Files",
extensions: ["txt"],
},
{
name: "FASTA Files",
extensions: ["fasta", "fa", "fasta.gz", "fa.gz"],
},
{
name: "FASTQ Files",
extensions: ["fastq", "fq", "fastq.gz", "fq.gz"],
},
],
});

handleSelectFile(path);
};

const handleSelectFile = async (path: string | null) => {
if (!path) {
console.log('No file selected');
return;
}

console.log('Selected file:', path);
// TODO: consider how to handle very large files
const contents = await readTextFile(path);
setFileContents(contents);
};

async function getFiles(path: string) {
const contents = await readDir(path);
setEntries(contents);
}

// biome-ignore lint/correctness/useExhaustiveDependencies: not getFiles
useEffect(() => {
getFiles(currentPath);
}, [currentPath]);

const sortedEntries = [
{ name: "..", isDirectory: true, isSymlink: false, isFile: false },
...entries
.sort((a, b) => {
// Sort directories before files
if (a.isDirectory !== b.isDirectory) {
return a.isDirectory ? -1 : 1;
}
// If both are directories or both are files, sort by name
return a.name.localeCompare(b.name);
})
.filter((entry) => !entry.name.startsWith(".")), // Hide hidden files
];

return (
<div>
<h1>File System</h1>
<p>File System view</p>
<button onClick={handleFileOpen}>Open File</button>
{fileContents && <pre>{fileContents}</pre>}
<div className="flex justify-between border-t border-neutral-200">
<div className="w-60 border-r border-neutral-200 h-screen p-2">
<p className="text-sm">Files</p>
<FilePicker entries={sortedEntries} onSelectFile={handlePick} />
<Button size="small" onClick={handleFileOpen}>
Open File
</Button>
</div>
<div className="overflow-y-scroll h-screen w-full text-select">
{fileContents && <pre className="text-xs">{fileContents}</pre>}
</div>
</div>
)
}
);
}
8 changes: 8 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};

0 comments on commit 508a628

Please sign in to comment.