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",
|
||||
"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",
|
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 notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use tauri::Emitter;
|
||||
@ -11,7 +10,6 @@ use tauri::AppHandle;
|
||||
use tauri::{TitleBarStyle, WebviewUrl, WebviewWindowBuilder};
|
||||
|
||||
|
||||
// Global state to store the watcher
|
||||
struct WatcherState {
|
||||
watcher: Option<RecommendedWatcher>,
|
||||
}
|
||||
@ -115,6 +113,39 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.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)
|
||||
.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!())
|
||||
.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 utilities;
|
||||
|
||||
|
||||
: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-size: 16px;
|
||||
line-height: 24px;
|
@ -4,5 +4,5 @@ export default {
|
||||
theme: {
|
||||
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>
|
||||
);
|
||||
}
|