feat: super basic image search

This commit is contained in:
2025-03-08 15:37:10 +00:00
parent 53ebbb6e8d
commit 863716c096
6 changed files with 117 additions and 32 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -134,6 +135,10 @@ func main() {
r.Get("/image", func(w http.ResponseWriter, r *http.Request) { r.Get("/image", func(w http.ResponseWriter, r *http.Request) {
userId := r.Header.Get("userId") userId := r.Header.Get("userId")
w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Credentials", "*")
w.Header().Add("Access-Control-Allow-Headers", "*")
images, err := models.GetUserImages(userId) images, err := models.GetUserImages(userId)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -156,6 +161,10 @@ func main() {
r.Get("/image/{id}", func(w http.ResponseWriter, r *http.Request) { r.Get("/image/{id}", func(w http.ResponseWriter, r *http.Request) {
imageId := r.PathValue("id") imageId := r.PathValue("id")
w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Credentials", "*")
w.Header().Add("Access-Control-Allow-Headers", "*")
// TODO: really need authorization here! // TODO: really need authorization here!
image, err := models.GetImage(imageId) image, err := models.GetImage(imageId)
if err != nil { if err != nil {
@ -188,17 +197,42 @@ func main() {
return return
} }
bodyData, err := io.ReadAll(r.Body)
// TODO: check headers
contentType := r.Header.Get("Content-Type") contentType := r.Header.Get("Content-Type")
log.Println(contentType)
// TODO: length checks on body
// TODO: extract this shit out
image := make([]byte, 0) image := make([]byte, 0)
if contentType == "application/base64" { if contentType == "application/base64" {
base64.StdEncoding.Decode(image, bodyData) decoder := base64.NewDecoder(base64.StdEncoding, r.Body)
buf := &bytes.Buffer{}
decodedIamge, err := io.Copy(buf, decoder)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "bruh, base64 aint decoding")
return
}
fmt.Println(string(image))
fmt.Println(decodedIamge)
image = buf.Bytes()
} else if contentType == "application/oclet-stream" { } else if contentType == "application/oclet-stream" {
bodyData, err := io.ReadAll(r.Body)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "bruh, binary aint binaring")
return
}
// TODO: check headers
image = bodyData image = bodyData
} else { } else {
log.Println("bad stuff?")
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "Bruh, you need oclet stream or base64") fmt.Fprintf(w, "Bruh, you need oclet stream or base64")
return return

Binary file not shown.

View File

@ -16,11 +16,13 @@
"dependencies": { "dependencies": {
"@kobalte/core": "^0.13.9", "@kobalte/core": "^0.13.9",
"@kobalte/tailwindcss": "^0.9.0", "@kobalte/tailwindcss": "^0.9.0",
"@solidjs/router": "^0.15.3",
"@tabler/icons-solidjs": "^3.30.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",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"fuse.js": "^7.1.0",
"solid-js": "^1.9.3", "solid-js": "^1.9.3",
"tailwind-scrollbar-hide": "^2.0.0", "tailwind-scrollbar-hide": "^2.0.0",
"valibot": "^1.0.0-rc.2" "valibot": "^1.0.0-rc.2"

View File

@ -5,46 +5,52 @@ import clsx from "clsx";
import { ImageViewer } from "./components/ImageViewer"; import { ImageViewer } from "./components/ImageViewer";
import { getUserImages } from "./network"; import { getUserImages } from "./network";
import { image } from "@tauri-apps/api"; import { image } from "@tauri-apps/api";
import { A, useNavigate } from "@solidjs/router";
import Fuse from "fuse.js";
type Emoji = { type UserImages = Awaited<ReturnType<typeof getUserImages>>;
emoji: string;
name: string;
};
function App() { function App() {
const [options, setOptions] = createSignal<Emoji[]>([]); const [searchResults, setSearchResults] = createSignal<UserImages[number]['Text']>([]);
const [emoji, setEmoji] = createSignal<Emoji | null>(null);
const [images] = createResource(getUserImages); const [images] = createResource(getUserImages);
const nav = useNavigate();
let fuze = new Fuse<UserImages>([], { keys: ["Text.ImageText"] });
// TODO: there's probably a better way?
createEffect(() => { createEffect(() => {
console.log(images()?.map(image => image.ID)) const userImages = images();
if (userImages == null) {
return;
}
fuze = new Fuse(userImages, { keys: ["Text.ImageText"] });
}); });
const emojiData: Emoji[] = [ const onInputChange = (query: string) => {
{ emoji: "😀", name: "Grinning Face" }, const searched = fuze.search(query).flatMap(s => s.item).flatMap(s => s.Text ?? []);
{ emoji: "😃", name: "Grinning Face with Big Eyes" }, setSearchResults(searched);
{ 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()),
);
};
return ( return (
<main class="container pt-2"> <main class="container pt-2">
<div class="px-4"> <div class="px-4">
<Search <Search
triggerMode="focus" triggerMode="focus"
options={options()} options={searchResults() ?? []}
onInputChange={(query) => setOptions(queryEmojiData(query))} onInputChange={onInputChange}
onChange={(result) => setEmoji(result)} onChange={(item) => {
optionValue="name" if (item?.ImageID == null) {
optionLabel="name" console.error("ImageID was null");
return;
}
nav(`/image/${item.ImageID}`);
}}
optionValue="ID"
optionLabel="ImageText"
placeholder="Search for stuff..." placeholder="Search for stuff..."
itemComponent={(props) => ( itemComponent={(props) => (
<Search.Item <Search.Item
@ -55,7 +61,7 @@ function App() {
)} )}
> >
<Search.ItemLabel class="mx-[-100px]"> <Search.ItemLabel class="mx-[-100px]">
{props.item.rawValue.emoji} {props.item.rawValue.ImageText ?? ''}
</Search.ItemLabel> </Search.ItemLabel>
</Search.Item> </Search.Item>
)} )}
@ -107,7 +113,7 @@ function App() {
<div class="col-span-6 row-span-3 bg-yellow-200 rounded-xl" /> <div class="col-span-6 row-span-3 bg-yellow-200 rounded-xl" />
<For each={images()}> <For each={images()}>
{(image) => ( {(image) => (
<img src={`http://localhost:3040/image/${image.ID}`} class="col-span-3 row-span-3 rounded-xl" /> <A href={`/image/${image.ID}`}><img src={`http://localhost:3040/image/${image.ID}`} class="col-span-3 row-span-3 rounded-xl" /></A>
)} )}
</For> </For>
</div> </div>

View File

@ -0,0 +1,36 @@
import { A, useParams } from "@solidjs/router"
import { createEffect, createResource, For, Suspense } from "solid-js"
import { getUserImages } from "./network"
export function ImagePage() {
const { imageId } = useParams<{ imageId: string }>()
const [image] = createResource(async () => {
const userImages = await getUserImages();
const currentImage = userImages.find(image => image.ID === imageId);
if (currentImage == null) {
// TODO: this error handling.
throw new Error("must be valid");
}
return currentImage;
});
createEffect(() => {
console.log(image());
})
return (<Suspense fallback={<>Loading...</>}>
<A href="/">Back</A>
<h1 class="text-2xl font-bold">{image()?.Image.ImageName}</h1>
<img src={`http://localhost:3040/image/${image()?.ID}`} />
<div class="flex flex-col">
<For each={image()?.Tags ?? []}>
{(tag) => (
<div>{tag.Tag}</div>
)}
</For>
</div>
</Suspense>)
}

View File

@ -2,5 +2,12 @@
import { render } from "solid-js/web"; import { render } from "solid-js/web";
import App from "./App"; import App from "./App";
import "./index.css"; import "./index.css";
import { Route, Router } from "@solidjs/router";
import { ImagePage } from "./ImagePage";
render(() => <App />, document.getElementById("root") as HTMLElement); render(() => (
<Router>
<Route path="/" component={App} />
<Route path="/image/:imageId" component={ImagePage} />
</Router>
), document.getElementById("root") as HTMLElement);