added a bunch of frontend things

This commit is contained in:
2025-02-23 19:30:11 +01:00
parent f4690b52a9
commit df16298b1f
49 changed files with 177 additions and 105 deletions

View File

@ -1,3 +1,3 @@
{ {
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer", "biomejs.biome", "golang.go", "bradlc.vscode-tailwindcss"]
} }

View File

@ -1,7 +0,0 @@
# Tauri + Solid + Typescript
This template should help get you started developing with Tauri, Solid and Typescript in Vite.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)

BIN
bun.lockb

Binary file not shown.

View File

BIN
frontend/bun.lockb Executable file

Binary file not shown.

View File

@ -14,6 +14,9 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@kobalte/core": "^0.13.9",
"@kobalte/tailwindcss": "^0.9.0",
"@tabler/icons-solidjs": "^3.30.0",
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "~2", "@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-opener": "^2",

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 974 B

After

Width:  |  Height:  |  Size: 974 B

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 903 B

After

Width:  |  Height:  |  Size: 903 B

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,4 +1,3 @@
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
use std::path::PathBuf; use std::path::PathBuf;
use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher}; use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
use tauri::Emitter; use tauri::Emitter;
@ -11,7 +10,6 @@ use tauri::AppHandle;
use tauri::{TitleBarStyle, WebviewUrl, WebviewWindowBuilder}; use tauri::{TitleBarStyle, WebviewUrl, WebviewWindowBuilder};
// Global state to store the watcher
struct WatcherState { struct WatcherState {
watcher: Option<RecommendedWatcher>, watcher: Option<RecommendedWatcher>,
} }
@ -115,6 +113,39 @@ pub fn run() {
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.manage(watcher_state) .manage(watcher_state)
.invoke_handler(tauri::generate_handler![handle_selected_folder]) .invoke_handler(tauri::generate_handler![handle_selected_folder])
.setup(|app| {
let win_builder =
WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
.hidden_title(true)
.inner_size(480.0, 320.0);
// 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,
250.0 / 255.0,
250.0 / 255.0,
250.5 / 255.0,
1.0,
);
ns_window.setBackgroundColor_(bg_color);
}
}
Ok(())
})
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }

View File

@ -0,0 +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"
]
}
}

108
frontend/src/App.tsx Normal file
View File

@ -0,0 +1,108 @@
import { FolderPicker } from "./components/FolderPicker";
import { listen } from "@tauri-apps/api/event";
import { createEffect, createSignal } from "solid-js";
import { Search } from "@kobalte/core/search";
import { IconSearch, IconRefresh } from "@tabler/icons-solidjs";
type Emoji = {
emoji: string;
name: string;
};
function App() {
const [latestImage, setLatestImage] = createSignal<string | null>(null);
const [options, setOptions] = createSignal<Emoji[]>([]);
const [emoji, setEmoji] = createSignal<Emoji | null>(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 queryEmojiData = (query: string) => {
return emojiData.filter((emoji) =>
emoji.name.toLowerCase().includes(query.toLowerCase())
);
};
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 (
<main class="container pt-8">
<h1>hello???</h1>
<FolderPicker />
{latestImage() && (
<div class="mt-4">
<h3>Latest Processed Image:</h3>
<img
src={latestImage() || undefined}
alt="Latest processed"
class="max-w-md"
/>
</div>
)}
<Search
triggerMode="focus"
options={options()}
onInputChange={(query) => setOptions(queryEmojiData(query))}
onChange={(result) => setEmoji(result)}
optionValue="name"
optionLabel="name"
placeholder="Search an emoji…"
itemComponent={(props) => (
<Search.Item item={props.item} class="search__item">
<Search.ItemLabel>{props.item.rawValue.emoji}</Search.ItemLabel>
</Search.Item>
)}
>
<Search.Control class="search__control" aria-label="Emoji">
<Search.Indicator
class="search__indicator"
loadingComponent={
<Search.Icon class="load__icon">
<IconRefresh class="spin__icon" />
</Search.Icon>
}
>
<Search.Icon class="search__icon">
<IconSearch class="center__icon" />
</Search.Icon>
</Search.Indicator>
<Search.Input class="search__input" />
</Search.Control>
<Search.Portal>
<Search.Content
class="search__content"
onCloseAutoFocus={(e) => e.preventDefault()}
>
<Search.Listbox class="search__listbox" />
<Search.NoResult class="search__no_result">
😬 No emoji found
</Search.NoResult>
</Search.Content>
</Search.Portal>
</Search>
<div class="result__content">
Emoji selected: {emoji()?.emoji} {emoji()?.name}
</div>
</main>
);
}
export default App;

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -2,9 +2,8 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
:root { :root {
@apply bg-gray-100 text-black rounded-xl; @apply bg-neutral-50 text-black rounded-xl;
font-family: Inter, Avenir, Helvetica, Arial, sans-serif; font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;

View File

@ -4,5 +4,5 @@ export default {
theme: { theme: {
extend: {}, extend: {},
}, },
plugins: [], plugins: [require("@kobalte/tailwindcss")],
}; };

View File

@ -1,40 +0,0 @@
{
"$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": [
{
"title": "Haystack",
"width": 640,
"height": 480,
"decorations": false,
"transparent": true,
"resizable": false,
"shadow": true
}
],
"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"
]
}
}

View File

@ -1,45 +0,0 @@
import { TitleBar } from "./components/TitleBar";
import { FolderPicker } from "./components/FolderPicker";
import { listen } from "@tauri-apps/api/event";
import { createEffect, createSignal } from "solid-js";
function App() {
const [latestImage, setLatestImage] = createSignal<string | null>(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}`);
});
return () => {
unlisten.then((fn) => fn()); // Cleanup listener
};
});
return (
<>
<TitleBar />
<main class="container pt-8">
<h1>Pick screenshots folder</h1>
<FolderPicker />
{/* Display the latest processed image */}
{latestImage() && (
<div class="mt-4">
<h3>Latest Processed Image:</h3>
<img
src={latestImage() || undefined}
alt="Latest processed"
class="max-w-md"
/>
</div>
)}
</main>
</>
);
}
export default App;

View File

@ -1,7 +0,0 @@
export function TitleBar() {
return (
<div class="h-8 flex items-center rounded-t-xl px-4" data-tauri-drag-region>
<div class="text-sm">Haystack</div>
</div>
);
}