added a bunch of frontend things
2
.vscode/extensions.json
vendored
@ -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"]
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
0
.gitignore → frontend/.gitignore
vendored
BIN
frontend/bun.lockb
Executable 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",
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 974 B After Width: | Height: | Size: 974 B |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 903 B |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@ -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");
|
||||||
}
|
}
|
30
frontend/src-tauri/tauri.conf.json
Normal 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
@ -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;
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@ -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;
|
@ -4,5 +4,5 @@ export default {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [require("@kobalte/tailwindcss")],
|
||||||
};
|
};
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
45
src/App.tsx
@ -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;
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|