feat: super basic image search
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -134,6 +135,10 @@ func main() {
|
||||
r.Get("/image", func(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
@ -156,6 +161,10 @@ func main() {
|
||||
r.Get("/image/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
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!
|
||||
image, err := models.GetImage(imageId)
|
||||
if err != nil {
|
||||
@ -188,17 +197,42 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
bodyData, err := io.ReadAll(r.Body)
|
||||
// TODO: check headers
|
||||
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
|
||||
log.Println(contentType)
|
||||
|
||||
// TODO: length checks on body
|
||||
// TODO: extract this shit out
|
||||
image := make([]byte, 0)
|
||||
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" {
|
||||
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
|
||||
} else {
|
||||
log.Println("bad stuff?")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "Bruh, you need oclet stream or base64")
|
||||
return
|
||||
|
Binary file not shown.
@ -16,11 +16,13 @@
|
||||
"dependencies": {
|
||||
"@kobalte/core": "^0.13.9",
|
||||
"@kobalte/tailwindcss": "^0.9.0",
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@tabler/icons-solidjs": "^3.30.0",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"clsx": "^2.1.1",
|
||||
"fuse.js": "^7.1.0",
|
||||
"solid-js": "^1.9.3",
|
||||
"tailwind-scrollbar-hide": "^2.0.0",
|
||||
"valibot": "^1.0.0-rc.2"
|
||||
|
@ -5,46 +5,52 @@ import clsx from "clsx";
|
||||
import { ImageViewer } from "./components/ImageViewer";
|
||||
import { getUserImages } from "./network";
|
||||
import { image } from "@tauri-apps/api";
|
||||
import { A, useNavigate } from "@solidjs/router";
|
||||
import Fuse from "fuse.js";
|
||||
|
||||
type Emoji = {
|
||||
emoji: string;
|
||||
name: string;
|
||||
};
|
||||
type UserImages = Awaited<ReturnType<typeof getUserImages>>;
|
||||
|
||||
function App() {
|
||||
const [options, setOptions] = createSignal<Emoji[]>([]);
|
||||
const [emoji, setEmoji] = createSignal<Emoji | null>(null);
|
||||
const [searchResults, setSearchResults] = createSignal<UserImages[number]['Text']>([]);
|
||||
|
||||
const [images] = createResource(getUserImages);
|
||||
|
||||
const nav = useNavigate();
|
||||
|
||||
let fuze = new Fuse<UserImages>([], { keys: ["Text.ImageText"] });
|
||||
|
||||
// TODO: there's probably a better way?
|
||||
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[] = [
|
||||
{ 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 onInputChange = (query: string) => {
|
||||
const searched = fuze.search(query).flatMap(s => s.item).flatMap(s => s.Text ?? []);
|
||||
setSearchResults(searched);
|
||||
}
|
||||
|
||||
return (
|
||||
<main class="container pt-2">
|
||||
<div class="px-4">
|
||||
<Search
|
||||
triggerMode="focus"
|
||||
options={options()}
|
||||
onInputChange={(query) => setOptions(queryEmojiData(query))}
|
||||
onChange={(result) => setEmoji(result)}
|
||||
optionValue="name"
|
||||
optionLabel="name"
|
||||
options={searchResults() ?? []}
|
||||
onInputChange={onInputChange}
|
||||
onChange={(item) => {
|
||||
if (item?.ImageID == null) {
|
||||
console.error("ImageID was null");
|
||||
return;
|
||||
}
|
||||
|
||||
nav(`/image/${item.ImageID}`);
|
||||
}}
|
||||
optionValue="ID"
|
||||
optionLabel="ImageText"
|
||||
placeholder="Search for stuff..."
|
||||
itemComponent={(props) => (
|
||||
<Search.Item
|
||||
@ -55,7 +61,7 @@ function App() {
|
||||
)}
|
||||
>
|
||||
<Search.ItemLabel class="mx-[-100px]">
|
||||
{props.item.rawValue.emoji}
|
||||
{props.item.rawValue.ImageText ?? ''}
|
||||
</Search.ItemLabel>
|
||||
</Search.Item>
|
||||
)}
|
||||
@ -107,7 +113,7 @@ function App() {
|
||||
<div class="col-span-6 row-span-3 bg-yellow-200 rounded-xl" />
|
||||
<For each={images()}>
|
||||
{(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>
|
||||
</div>
|
||||
|
36
frontend/src/ImagePage.tsx
Normal file
36
frontend/src/ImagePage.tsx
Normal 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>)
|
||||
}
|
@ -2,5 +2,12 @@
|
||||
import { render } from "solid-js/web";
|
||||
import App from "./App";
|
||||
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);
|
||||
|
Reference in New Issue
Block a user