feat: frontend responding to backend SSE and refetching images
This commit is contained in:
@@ -7,6 +7,7 @@ permissions = [
|
||||
"core:default",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-show",
|
||||
"core:window:allow-set-focus",
|
||||
"fs:default",
|
||||
{ identifier = "http:default", allow = [
|
||||
{ url = "https://haystack.johncosta.tech" },
|
||||
|
||||
@@ -6,10 +6,10 @@ import { ProtectedRoute } from "./ProtectedRoute";
|
||||
import { Search } from "./Search";
|
||||
import { Settings } from "./Settings";
|
||||
import { ImageViewer } from "./components/ImageViewer";
|
||||
import { ShareTarget } from "./components/share-target/ShareTarget";
|
||||
import type { sendImage } from "./network";
|
||||
import { ImageStatus } from "./components/image-status/ImageStatus";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { SearchImageContextProvider } from "./contexts/SearchImageContext";
|
||||
|
||||
export const App = () => {
|
||||
const [processingImage, setProcessingImage] =
|
||||
@@ -57,7 +57,7 @@ export const App = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchImageContextProvider>
|
||||
<button type="button" onClick={onTakeScreenshot}>
|
||||
Take Screenshot [wayland :(]
|
||||
</button>
|
||||
@@ -71,6 +71,6 @@ export const App = () => {
|
||||
<Route path="/settings" component={Settings} />
|
||||
</Route>
|
||||
</Router>
|
||||
</>
|
||||
</SearchImageContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,52 +1,21 @@
|
||||
import { Button } from "@kobalte/core/button";
|
||||
|
||||
import { IconSearch, IconSettings } from "@tabler/icons-solidjs";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
import Fuse from "fuse.js";
|
||||
import {
|
||||
For,
|
||||
Show,
|
||||
createEffect,
|
||||
createResource,
|
||||
createSignal,
|
||||
onCleanup,
|
||||
onMount,
|
||||
} from "solid-js";
|
||||
|
||||
import { SearchCard } from "./components/search-card/SearchCard";
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { ItemModal } from "./components/item-modal/ItemModal";
|
||||
import type { Shortcut } from "./components/shortcuts/hooks/useShortcutEditor";
|
||||
import { type UserImage, getUserImages } from "./network";
|
||||
|
||||
// How wonderfully functional
|
||||
const getAllValues = (object: object): Array<string> => {
|
||||
const loop = (acc: Array<string>, next: object): Array<string> => {
|
||||
for (const _value of Object.values(next)) {
|
||||
const value: unknown = _value;
|
||||
switch (typeof value) {
|
||||
case "object":
|
||||
if (value != null) {
|
||||
acc.push(...loop(acc, value));
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
case "number":
|
||||
case "boolean":
|
||||
acc.push(value.toString());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
};
|
||||
|
||||
return loop([], object);
|
||||
};
|
||||
import type { UserImage } from "./network";
|
||||
import { useSearchImageContext } from "./contexts/SearchImageContext";
|
||||
|
||||
export const Search = () => {
|
||||
const [searchResults, setSearchResults] = createSignal<UserImage[]>([]);
|
||||
@@ -55,17 +24,9 @@ export const Search = () => {
|
||||
null,
|
||||
);
|
||||
|
||||
const [data] = createResource(() =>
|
||||
getUserImages().then((data) => {
|
||||
console.log("DBG: ", data);
|
||||
return data.map((d) => ({
|
||||
...d,
|
||||
rawData: getAllValues(d),
|
||||
}));
|
||||
}),
|
||||
);
|
||||
const { images } = useSearchImageContext();
|
||||
|
||||
let fuze = new Fuse<UserImage>(data() ?? [], {
|
||||
let fuze = new Fuse<UserImage>(images() ?? [], {
|
||||
keys: [
|
||||
{ name: "rawData", weight: 1 },
|
||||
{ name: "title", weight: 1 },
|
||||
@@ -74,10 +35,10 @@ export const Search = () => {
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
console.log("DBG: ", data());
|
||||
setSearchResults(data() ?? []);
|
||||
console.log("DBG: ", images());
|
||||
setSearchResults(images() ?? []);
|
||||
|
||||
fuze = new Fuse<UserImage>(data() ?? [], {
|
||||
fuze = new Fuse<UserImage>(images() ?? [], {
|
||||
keys: [
|
||||
{ name: "data.Name", weight: 2 },
|
||||
{ name: "rawData", weight: 1 },
|
||||
|
||||
@@ -29,7 +29,6 @@ export const ImageViewer: Component<ImageViewerProps> = (props) => {
|
||||
|
||||
props.onSendImage(result);
|
||||
|
||||
window.location.reload();
|
||||
console.log("DBG: ", result);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Show, type Accessor, type Component } from "solid-js";
|
||||
import { createEffect, Show, type Accessor, type Component } from "solid-js";
|
||||
import type { sendImage } from "../../network";
|
||||
import { useSearchImageContext } from "../../contexts/SearchImageContext";
|
||||
|
||||
type ImageStatusProps = {
|
||||
processingImage: Accessor<
|
||||
@@ -7,7 +8,38 @@ type ImageStatusProps = {
|
||||
>;
|
||||
};
|
||||
|
||||
type EventData = "in-progress" | "complete";
|
||||
|
||||
export const ImageStatus: Component<ImageStatusProps> = (props) => {
|
||||
const { onRefetchImages } = useSearchImageContext();
|
||||
|
||||
const onEvent = (e: MessageEvent<EventData>) => {
|
||||
console.log(e.data);
|
||||
|
||||
if (e.data !== "complete") {
|
||||
return;
|
||||
}
|
||||
|
||||
onRefetchImages();
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
const image = props.processingImage();
|
||||
if (image == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventSource = new EventSource(
|
||||
`http://192.168.1.199:3040/image-events/${image.ID}`,
|
||||
);
|
||||
|
||||
eventSource.addEventListener("data", onEvent);
|
||||
|
||||
return () => {
|
||||
eventSource.removeEventListener("data", onEvent);
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Show when={props.processingImage()}>
|
||||
{(image) => (
|
||||
|
||||
79
frontend/src/contexts/SearchImageContext.tsx
Normal file
79
frontend/src/contexts/SearchImageContext.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
createContext,
|
||||
type Resource,
|
||||
type Component,
|
||||
type ParentProps,
|
||||
createResource,
|
||||
useContext,
|
||||
} from "solid-js";
|
||||
import { getUserImages } from "../network";
|
||||
|
||||
type ImageWithRawData = Awaited<ReturnType<typeof getUserImages>>[number] & {
|
||||
rawData: string[];
|
||||
};
|
||||
|
||||
type SearchImageStore = {
|
||||
images: Resource<ImageWithRawData[]>;
|
||||
onRefetchImages: () => void;
|
||||
};
|
||||
|
||||
// How wonderfully functional
|
||||
const getAllValues = (object: object): Array<string> => {
|
||||
const loop = (acc: Array<string>, next: object): Array<string> => {
|
||||
for (const _value of Object.values(next)) {
|
||||
const value: unknown = _value;
|
||||
switch (typeof value) {
|
||||
case "object":
|
||||
if (value != null) {
|
||||
acc.push(...loop(acc, value));
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
case "number":
|
||||
case "boolean":
|
||||
acc.push(value.toString());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
};
|
||||
|
||||
return loop([], object);
|
||||
};
|
||||
|
||||
const SearchImageContext = createContext<SearchImageStore>();
|
||||
export const SearchImageContextProvider: Component<ParentProps> = (props) => {
|
||||
const [images, { refetch }] = createResource(() =>
|
||||
getUserImages().then((data) => {
|
||||
return data.map((d) => ({
|
||||
...d,
|
||||
rawData: getAllValues(d),
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<SearchImageContext.Provider
|
||||
value={{
|
||||
images,
|
||||
onRefetchImages: refetch,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</SearchImageContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useSearchImageContext = () => {
|
||||
const context = useContext(SearchImageContext);
|
||||
if (context == null) {
|
||||
throw new Error(
|
||||
"Unreachable: We should always have a mounted context and no undefined values",
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
@@ -3,19 +3,4 @@ import { render } from "solid-js/web";
|
||||
import "./index.css";
|
||||
import { App } from "./App";
|
||||
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
console.log("Hello android!");
|
||||
|
||||
render(() => <App />, document.getElementById("root") as HTMLElement);
|
||||
|
||||
Reference in New Issue
Block a user