diff --git a/frontend/biome.json b/frontend/biome.json index decb4ed..f8702e4 100644 --- a/frontend/biome.json +++ b/frontend/biome.json @@ -1,12 +1,12 @@ { - "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - } - } + "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } } diff --git a/frontend/bun.lockb b/frontend/bun.lockb index d69237e..66c8f4e 100755 Binary files a/frontend/bun.lockb and b/frontend/bun.lockb differ diff --git a/frontend/package.json b/frontend/package.json index 68a9eed..a904fae 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,38 +1,39 @@ { - "name": "haystack", - "version": "0.1.0", - "description": "", - "type": "module", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "vite build", - "serve": "vite preview", - "tauri": "tauri", - "lint": "bunx @biomejs/biome lint .", - "format": "bunx @biomejs/biome format . --write" - }, - "license": "MIT", - "dependencies": { - "@kobalte/core": "^0.13.9", - "@kobalte/tailwindcss": "^0.9.0", - "@tabler/icons-solidjs": "^3.30.0", - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-dialog": "~2", - "@tauri-apps/plugin-opener": "^2", - "clsx": "^2.1.1", - "solid-js": "^1.9.3", - "tailwind-scrollbar-hide": "^2.0.0" - }, - "devDependencies": { - "@biomejs/biome": "^1.9.4", - "@tauri-apps/cli": "^2", - "autoprefixer": "^10.4.20", - "postcss": "^8.5.3", - "postcss-cli": "^11.0.0", - "tailwindcss": "3.4.0", - "typescript": "~5.6.2", - "vite": "^6.0.3", - "vite-plugin-solid": "^2.11.0" - } + "name": "haystack", + "version": "0.1.0", + "description": "", + "type": "module", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "tauri": "tauri", + "lint": "bunx @biomejs/biome lint .", + "format": "bunx @biomejs/biome format . --write" + }, + "license": "MIT", + "dependencies": { + "@kobalte/core": "^0.13.9", + "@kobalte/tailwindcss": "^0.9.0", + "@tabler/icons-solidjs": "^3.30.0", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "~2", + "@tauri-apps/plugin-opener": "^2", + "clsx": "^2.1.1", + "solid-js": "^1.9.3", + "tailwind-scrollbar-hide": "^2.0.0", + "valibot": "^1.0.0-rc.2" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@tauri-apps/cli": "^2", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.3", + "postcss-cli": "^11.0.0", + "tailwindcss": "3.4.0", + "typescript": "~5.6.2", + "vite": "^6.0.3", + "vite-plugin-solid": "^2.11.0" + } } diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 2aa7205..7b75c83 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -1,6 +1,6 @@ export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, }; diff --git a/frontend/src-tauri/capabilities/default.json b/frontend/src-tauri/capabilities/default.json index e8480e0..d8e24c2 100644 --- a/frontend/src-tauri/capabilities/default.json +++ b/frontend/src-tauri/capabilities/default.json @@ -1,12 +1,12 @@ { - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Capability for the main window", - "windows": ["main"], - "permissions": [ - "core:default", - "opener:default", - "dialog:default", - "core:window:allow-start-dragging" - ] + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + "opener:default", + "dialog:default", + "core:window:allow-start-dragging" + ] } diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 1d4a06c..3920ce6 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -1,14 +1,13 @@ -use std::path::PathBuf; +use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _}; use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher}; -use tauri::Emitter; -use std::sync::mpsc::channel; use std::fs; -use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; -use std::sync::Mutex; +use std::path::PathBuf; +use std::sync::mpsc::channel; use std::sync::Arc; +use std::sync::Mutex; use tauri::AppHandle; -use tauri::{TitleBarStyle, WebviewUrl, WebviewWindowBuilder}; - +use tauri::Emitter; +use tauri::{WebviewUrl, WebviewWindowBuilder}; struct WatcherState { watcher: Option, @@ -25,8 +24,7 @@ fn process_png_file(path: &PathBuf, app: AppHandle) -> Result<(), String> { println!("Processing PNG file: {}", path.display()); // Read the file - let contents = fs::read(path) - .map_err(|e| format!("Failed to read file: {}", e))?; + let contents = fs::read(path).map_err(|e| format!("Failed to read file: {}", e))?; // Convert to base64 let base64_string = BASE64.encode(&contents); @@ -47,26 +45,27 @@ async fn handle_selected_folder( app: AppHandle, ) -> Result { let path_buf = PathBuf::from(&path); - + if !path_buf.exists() || !path_buf.is_dir() { return Err("Invalid directory path".to_string()); } // Stop existing watcher if any - let mut state = state.lock().map_err(|_| "Failed to lock state".to_string())?; + let mut state = state + .lock() + .map_err(|_| "Failed to lock state".to_string())?; state.watcher = None; // Create a channel to receive file system events let (tx, rx) = channel(); // Create a new watcher - let mut watcher = RecommendedWatcher::new( - tx, - Config::default(), - ).map_err(|e| format!("Failed to create watcher: {}", e))?; + let mut watcher = RecommendedWatcher::new(tx, Config::default()) + .map_err(|e| format!("Failed to create watcher: {}", e))?; // Start watching the directory - watcher.watch(path_buf.as_ref(), RecursiveMode::Recursive) + watcher + .watch(path_buf.as_ref(), RecursiveMode::Recursive) .map_err(|e| format!("Failed to watch directory: {}", e))?; // Store the watcher in state @@ -114,38 +113,36 @@ pub fn run() { .manage(watcher_state) .invoke_handler(tauri::generate_handler![handle_selected_folder]) .setup(|app| { - let win_builder = - WebviewWindowBuilder::new(app, "main", WebviewUrl::default()) - .hidden_title(true) + let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default()) .inner_size(480.0, 360.0) .resizable(false); // set transparent title bar only when building for macOS #[cfg(target_os = "macos")] let win_builder = win_builder.title_bar_style(TitleBarStyle::Transparent); - + let window = win_builder.build().unwrap(); - + // set background color only when building for macOS #[cfg(target_os = "macos")] { - use cocoa::appkit::{NSColor, NSWindow}; - use cocoa::base::{id, nil}; - - let ns_window = window.ns_window().unwrap() as id; - unsafe { - let bg_color = NSColor::colorWithRed_green_blue_alpha_( - nil, - 245.0 / 255.0, - 245.0 / 255.0, - 245.0 / 255.0, - 1.0, - ); - ns_window.setBackgroundColor_(bg_color); - } + use cocoa::appkit::{NSColor, NSWindow}; + use cocoa::base::{id, nil}; + + let ns_window = window.ns_window().unwrap() as id; + unsafe { + let bg_color = NSColor::colorWithRed_green_blue_alpha_( + nil, + 245.0 / 255.0, + 245.0 / 255.0, + 245.0 / 255.0, + 1.0, + ); + ns_window.setBackgroundColor_(bg_color); + } } - + Ok(()) - }) + }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index 2aaa7b5..d056e73 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -1,30 +1,30 @@ { - "$schema": "https://schema.tauri.app/config/2", - "productName": "haystack", - "version": "0.1.0", - "identifier": "com.haystack.app", - "build": { - "beforeDevCommand": "bun run dev", - "devUrl": "http://localhost:1420", - "beforeBuildCommand": "bun run build", - "frontendDist": "../dist" - }, - "app": { - "windows": [], - "macOSPrivateApi": true, - "security": { - "csp": null - } - }, - "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ] - } + "$schema": "https://schema.tauri.app/config/2", + "productName": "haystack", + "version": "0.1.0", + "identifier": "com.haystack.app", + "build": { + "beforeDevCommand": "bun run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "bun run build", + "frontendDist": "../dist" + }, + "app": { + "windows": [], + "macOSPrivateApi": true, + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2129247..cd87c9e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,105 +4,105 @@ import { IconSearch, IconRefresh } from "@tabler/icons-solidjs"; import clsx from "clsx"; type Emoji = { - emoji: string; - name: string; + emoji: string; + name: string; }; function App() { - const [options, setOptions] = createSignal([]); - const [emoji, setEmoji] = createSignal(null); + const [options, setOptions] = createSignal([]); + const [emoji, setEmoji] = createSignal(null); - const emojiData: Emoji[] = [ - { emoji: "😀", name: "Grinning Face" }, - { emoji: "😃", name: "Grinning Face with Big Eyes" }, - { emoji: "😄", name: "Grinning Face with Smiling Eyes" }, - { emoji: "😁", name: "Beaming Face with Smiling Eyes" }, - { emoji: "😆", name: "Grinning Squinting Face" }, - ]; + const emojiData: Emoji[] = [ + { emoji: "😀", name: "Grinning Face" }, + { emoji: "😃", name: "Grinning Face with Big Eyes" }, + { emoji: "😄", name: "Grinning Face with Smiling Eyes" }, + { emoji: "😁", name: "Beaming Face with Smiling Eyes" }, + { emoji: "😆", name: "Grinning Squinting Face" }, + ]; - const queryEmojiData = (query: string) => { - return emojiData.filter((emoji) => - emoji.name.toLowerCase().includes(query.toLowerCase()) - ); - }; + const queryEmojiData = (query: string) => { + return emojiData.filter((emoji) => + emoji.name.toLowerCase().includes(query.toLowerCase()), + ); + }; - return ( -
-
- setOptions(queryEmojiData(query))} - onChange={(result) => setEmoji(result)} - optionValue="name" - optionLabel="name" - placeholder="Search for stuff..." - itemComponent={(props) => ( - - - {props.item.rawValue.emoji} - - - )} - > - - - - - } - > - - - - - - - - e.preventDefault()} - > - - - 😬 No emoji found - - - - -
- {/*
+ return ( +
+
+ setOptions(queryEmojiData(query))} + onChange={(result) => setEmoji(result)} + optionValue="name" + optionLabel="name" + placeholder="Search for stuff..." + itemComponent={(props) => ( + + + {props.item.rawValue.emoji} + + + )} + > + + + + + } + > + + + + + + + + e.preventDefault()} + > + + + 😬 No emoji found + + + + +
+ {/*
Emoji selected: {emoji()?.emoji} {emoji()?.name}
*/} -
-
-
-
-
-
-
-
-
-
-
-
-
-
- footer -
-
- ); +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ footer +
+
+ ); } export default App; diff --git a/frontend/src/components/FolderPicker.tsx b/frontend/src/components/FolderPicker.tsx index 585fbbe..285a2bb 100644 --- a/frontend/src/components/FolderPicker.tsx +++ b/frontend/src/components/FolderPicker.tsx @@ -3,47 +3,47 @@ import { open } from "@tauri-apps/plugin-dialog"; import { invoke } from "@tauri-apps/api/core"; export function FolderPicker() { - const [selectedPath, setSelectedPath] = createSignal(""); - const [status, setStatus] = createSignal(""); + const [selectedPath, setSelectedPath] = createSignal(""); + const [status, setStatus] = createSignal(""); - const handleFolderSelect = async () => { - try { - const selected = await open({ - directory: true, - multiple: false, - }); + const handleFolderSelect = async () => { + try { + const selected = await open({ + directory: true, + multiple: false, + }); - if (selected) { - setSelectedPath(selected as string); - // Send the path to Rust - const response = await invoke("handle_selected_folder", { - path: selected, - }); - setStatus(`Folder processed: ${response}`); - } - } catch (error) { - setStatus(`Error: ${error}`); - } - }; + if (selected) { + setSelectedPath(selected as string); + // Send the path to Rust + const response = await invoke("handle_selected_folder", { + path: selected, + }); + setStatus(`Folder processed: ${response}`); + } + } catch (error) { + setStatus(`Error: ${error}`); + } + }; - return ( -
- + return ( +
+ - {selectedPath() && ( -
-

Selected folder:

-

{selectedPath()}

-
- )} + {selectedPath() && ( +
+

Selected folder:

+

{selectedPath()}

+
+ )} - {status() &&

{status()}

} -
- ); + {status() &&

{status()}

} +
+ ); } diff --git a/frontend/src/components/ImageViewer.tsx b/frontend/src/components/ImageViewer.tsx index 85fc08a..2fffb5c 100644 --- a/frontend/src/components/ImageViewer.tsx +++ b/frontend/src/components/ImageViewer.tsx @@ -3,35 +3,35 @@ import { listen } from "@tauri-apps/api/event"; import { FolderPicker } from "./FolderPicker"; export function ImageViewer() { - const [latestImage, setLatestImage] = createSignal(null); + const [latestImage, setLatestImage] = createSignal(null); - createEffect(() => { - // Listen for PNG processing events - const unlisten = listen("png-processed", (event) => { - console.log("Received processed PNG"); - const base64Data = event.payload as string; - setLatestImage(`data:image/png;base64,${base64Data}`); - }); + createEffect(() => { + // Listen for PNG processing events + const unlisten = listen("png-processed", (event) => { + console.log("Received processed PNG"); + const base64Data = event.payload as string; + setLatestImage(`data:image/png;base64,${base64Data}`); + }); - return () => { - unlisten.then((fn) => fn()); // Cleanup listener - }; - }); + return () => { + unlisten.then((fn) => fn()); // Cleanup listener + }; + }); - return ( -
- + return ( +
+ - {latestImage() && ( -
-

Latest Processed Image:

- Latest processed -
- )} -
- ); + {latestImage() && ( +
+

Latest Processed Image:

+ Latest processed +
+ )} +
+ ); } diff --git a/frontend/src/index.css b/frontend/src/index.css index 71bc21a..d61995c 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -3,21 +3,21 @@ @tailwind utilities; @font-face { - font-family: "Manrope"; - src: url("./assets/fonts/Manrope-VariableFont_wght.ttf") format("truetype"); - font-weight: 100 900; - font-display: swap; + font-family: "Manrope"; + src: url("./assets/fonts/Manrope-VariableFont_wght.ttf") format("truetype"); + font-weight: 100 900; + font-display: swap; } :root { - @apply bg-neutral-100 text-black rounded-xl; - font-family: Manrope, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 500; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; + @apply bg-neutral-100 text-black rounded-xl; + font-family: Manrope, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 500; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; } diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 97f179f..f749829 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,15 +1,15 @@ /** @type {import('tailwindcss').Config} */ export default { - content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], - theme: { - extend: { - fontFamily: { - sans: ["Manrope", "sans-serif"], - }, - }, - }, - plugins: [ - require("@kobalte/tailwindcss"), - require("tailwind-scrollbar-hide"), - ], + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], + theme: { + extend: { + fontFamily: { + sans: ["Manrope", "sans-serif"], + }, + }, + }, + plugins: [ + require("@kobalte/tailwindcss"), + require("tailwind-scrollbar-hide"), + ], }; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 3999958..f7f13c7 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,26 +1,26 @@ { - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "preserve", - "jsxImportSource": "solid-js", + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json index 42872c5..eca6668 100644 --- a/frontend/tsconfig.node.json +++ b/frontend/tsconfig.node.json @@ -1,10 +1,10 @@ { - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.ts"] + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index af994b4..815ec0f 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -8,31 +8,31 @@ const host = process.env.TAURI_DEV_HOST; // https://vitejs.dev/config/ export default defineConfig(async () => ({ - plugins: [solid()], - css: { - postcss: { - plugins: [tailwindcss, autoprefixer], - }, - }, - // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` - // - // 1. prevent vite from obscuring rust errors - clearScreen: false, - // 2. tauri expects a fixed port, fail if that port is not available - server: { - port: 1420, - strictPort: true, - host: host || false, - hmr: host - ? { - protocol: "ws", - host, - port: 1421, - } - : undefined, - watch: { - // 3. tell vite to ignore watching `src-tauri` - ignored: ["**/src-tauri/**"], - }, - }, + plugins: [solid()], + css: { + postcss: { + plugins: [tailwindcss, autoprefixer], + }, + }, + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // + // 1. prevent vite from obscuring rust errors + clearScreen: false, + // 2. tauri expects a fixed port, fail if that port is not available + server: { + port: 1420, + strictPort: true, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + // 3. tell vite to ignore watching `src-tauri` + ignored: ["**/src-tauri/**"], + }, + }, }));