feat: super basic image search
This commit is contained in:
@ -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.
@ -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"
|
||||||
|
@ -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>
|
||||||
|
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 { 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);
|
||||||
|
Reference in New Issue
Block a user