diff --git a/web/components/documentation.js b/web/components/documentation.js
index 9164622..a7b6010 100644
--- a/web/components/documentation.js
+++ b/web/components/documentation.js
@@ -246,13 +246,15 @@ export default function Documentation() {
- The Analogdb API currently places a limit of 30 requests/min.
+ The Analogdb API currently places a limit of 60 requests/min.
Current rate limit status is returned in response headers after each
- request.
+ request including remaining requests and reset time in unix epoch
+ seconds.
diff --git a/web/components/gallery.js b/web/components/gallery.js
index 8bb9bb9..d0d1766 100644
--- a/web/components/gallery.js
+++ b/web/components/gallery.js
@@ -4,13 +4,15 @@ import Footer from "./footer";
import InfiniteGallery from "../components/infiniteGallery";
import ScrollTop from "../components/scrollTop";
import { useState, useEffect } from "react";
+import { useBreakpoint } from "../providers/breakpoint.js";
import useKeyPress from "../hooks/useKeyPress";
+import { useQueryState, queryTypes } from "next-usequerystate";
import {
IconSearch,
IconArrowsSort,
IconAdjustmentsHorizontal,
+ IconPalette,
} from "@tabler/icons";
-import useQuery from "../stores/query";
import {
TextInput,
@@ -18,7 +20,9 @@ import {
SegmentedControl,
Menu,
Radio,
+ Checkbox,
} from "@mantine/core";
+
import { baseURL } from "../constants.js";
async function makeRequest(queryParams) {
@@ -28,7 +32,7 @@ async function makeRequest(queryParams) {
return data;
}
-function filterQueryParams(sort, nsfw, bw, sprocket, search) {
+function filterQueryParams(sort, nsfw, bw, sprocket, text, color) {
let queryParams = "?" + "sort=" + sort;
switch (nsfw) {
@@ -58,8 +62,27 @@ function filterQueryParams(sort, nsfw, bw, sprocket, search) {
break;
}
- if (search !== "") {
- queryParams = queryParams.concat("&title=" + search);
+ if (text !== "") {
+ let keywords = text.split(/[ ,]+/).filter(Boolean);
+ keywords.forEach(
+ (word) => (queryParams = queryParams.concat("&keyword=" + word))
+ );
+ }
+
+ if (color !== "") {
+ queryParams = queryParams.concat("&color=" + color);
+ if (color === "black" || color === "gray") {
+ queryParams = queryParams.concat("&min_color=" + "0.8");
+ }
+ if (color === "white") {
+ queryParams = queryParams.concat("&min_color=" + "0.6");
+ }
+ if (color === "teal") {
+ queryParams = queryParams.concat("&min_color=" + "0.25");
+ }
+ if (color === "navy" || color === "green") {
+ queryParams = queryParams.concat("&min_color=" + "0.15");
+ }
}
queryParams = queryParams.concat("&page_size=" + 100);
@@ -67,55 +90,255 @@ function filterQueryParams(sort, nsfw, bw, sprocket, search) {
return queryParams;
}
+const defaultSort = "latest";
+const defaultNsfw = "exclude";
+const defaultBw = "exclude";
+const defaultSprocket = "include";
+const defaultColor = "";
+const defaultText = "";
+
export default function Gallery(props) {
- const { search, sort, nsfw, bw, sprocket } = useQuery((store) => ({
- search: store.search,
- sort: store.sort,
- nsfw: store.nsfw,
- bw: store.bw,
- sprocket: store.sprocket,
- }));
-
- const { setSearch, setSort, setNsfw, setBw, setSprocket } = useQuery(
- (store) => ({
- setSearch: store.setSearch,
- setSort: store.setSort,
- setNsfw: store.setNsfw,
- setBw: store.setBw,
- setSprocket: store.setSprocket,
- })
+ // querystate
+ const [sort, setSort] = useQueryState(
+ "sort",
+ queryTypes.string.withDefault(defaultSort)
+ );
+ const [nsfw, setNsfw] = useQueryState(
+ "nsfw",
+ queryTypes.string.withDefault(defaultNsfw)
+ );
+ const [bw, setBw] = useQueryState(
+ "bw",
+ queryTypes.string.withDefault(defaultBw)
+ );
+ const [sprocket, setSprocket] = useQueryState(
+ "sprocket",
+ queryTypes.string.withDefault(defaultSprocket)
+ );
+
+ // handle setting colors
+ const [color, setColor] = useQueryState(
+ "color",
+ queryTypes.string.withDefault(defaultColor)
+ );
+
+ const handleColorClick = (event) => {
+ let clickedColor = event.target.id;
+ if (clickedColor === color) {
+ setColor(null);
+ } else {
+ setColor(clickedColor);
+ }
+ };
+
+ // handle setting keywords.
+ // hold input text in temp variable and
+ // only set query state on updateRequest.
+ const [textTemp, setTextTemp] = useState("");
+ const [text, setText] = useQueryState(
+ "text",
+ queryTypes.string.withDefault(defaultText)
);
const [response, setResponse] = useState(props.data);
const updateRequest = async () => {
- let request = filterQueryParams(sort, nsfw, bw, sprocket, search);
+ if (textTemp == defaultText) {
+ setText(null);
+ } else {
+ setText(textTemp);
+ }
+
+ let request = filterQueryParams(sort, nsfw, bw, sprocket, text, color);
const response = await makeRequest(request);
setResponse(response);
};
const returnPress = useKeyPress("Enter");
+ const breakpoints = useBreakpoint();
+
+ let onlyIcon = false;
+ if (breakpoints["xs"] || breakpoints["sm"]) {
+ onlyIcon = true;
+ }
+
+ // const textPlaceholder = () => {
+ // onlyIcon ? "films, cameras..." : "films, cameras, places...";
+ // };
+
+ const textPlaceholder = () => {
+ const placeholder = onlyIcon
+ ? "films, cameras..."
+ : "films, cameras, places...";
+ return placeholder;
+ };
+
useEffect(() => {
updateRequest();
- }, [sort, nsfw, bw, sprocket, returnPress]);
+ }, [sort, nsfw, bw, sprocket, color, text, returnPress]);
return (
+
+
+
+ }
+ styles={() => ({
+ root: {
+ marginRight: 10,
+ paddingLeft: 10,
+ paddingRight: onlyIcon ? 0 : 10,
+ color: "#2E2E2E",
+ fontWeight: 400,
+ borderColor: "#CED4DA",
+ "&:hover": {
+ backgroundColor: "#fbfbfc",
+ },
+ leftIcon: {
+ marginRight: 5,
+ },
+ },
+ })}
+ >
+ {!onlyIcon && color }
+
+
+
+ with color
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}
+ leftIcon={
+
+ }
styles={() => ({
root: {
marginRight: 10,
paddingLeft: 10,
- paddingRight: 10,
+ paddingRight: onlyIcon ? 0 : 10,
color: "#2E2E2E",
fontWeight: 400,
borderColor: "#CED4DA",
@@ -128,11 +351,11 @@ export default function Gallery(props) {
},
})}
>
- sort
+ {!onlyIcon && sort }
- sort posts by
+ sort by
}
+ leftIcon={
+
+ }
styles={() => ({
root: {
marginRight: 10,
paddingLeft: 10,
- paddingRight: 10,
+ paddingRight: onlyIcon ? 0 : 10,
color: "#2E2E2E",
fontWeight: 400,
borderColor: "#CED4DA",
@@ -184,11 +412,11 @@ export default function Gallery(props) {
},
})}
>
- filter
+ {!onlyIcon &&
filter }
- filter posts by
+ filter by
18+
@@ -231,10 +459,10 @@ export default function Gallery(props) {
setSearch(event.currentTarget.value)}
+ value={textTemp}
+ onChange={(event) => setTextTemp(event.currentTarget.value)}
icon={ }
- placeholder="films, cameras, places..."
+ placeholder={textPlaceholder()}
/>
diff --git a/web/components/gallery.module.css b/web/components/gallery.module.css
index 315625d..06f2628 100644
--- a/web/components/gallery.module.css
+++ b/web/components/gallery.module.css
@@ -57,6 +57,21 @@
padding-left: 0.3rem;
}
+.colors {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding-right: 1rem;
+ padding-left: 1rem;
+ padding-top: 0.5rem;
+ padding-bottom: 1rem;
+}
+
+.colorButton {
+ margin: 0.2rem;
+}
+
@media screen and (max-width: 720px) {
.container {
text-align: left;
diff --git a/web/components/grid.js b/web/components/grid.js
index a52fc6c..ac154ab 100644
--- a/web/components/grid.js
+++ b/web/components/grid.js
@@ -5,7 +5,7 @@ import GridImage from "./gridImage";
export default function Grid(props) {
const breakpoints = useBreakpoint();
- let numColumn = 5;
+ let numColumn = 6;
if (breakpoints["xs"]) {
numColumn = 2;
} else if (breakpoints["sm"]) {
diff --git a/web/components/gridImage.js b/web/components/gridImage.js
index 0d0b145..828e3b5 100644
--- a/web/components/gridImage.js
+++ b/web/components/gridImage.js
@@ -3,6 +3,9 @@ import Link from "next/link";
export default function GridImage(props) {
let post = props.post;
+ if (post == null) {
+ return;
+ }
let low = post.images[0];
let medium = post.images[1];
@@ -16,12 +19,7 @@ export default function GridImage(props) {
}
return (
-
+
0
? post.keywords
.map((item) => {
- return {item.word} ;
+ return (
+
+
+ {item.word}
+
+
+ );
})
.slice(0, 15)
: [];
diff --git a/web/components/imageTag.module.css b/web/components/imageTag.module.css
index b3fe469..0b8b27f 100644
--- a/web/components/imageTag.module.css
+++ b/web/components/imageTag.module.css
@@ -100,6 +100,11 @@
background-color: #f5f5f5;
/* box-shadow: 0.5px 0.5px 20px 0.5px lightgray; */
}
+.keyword:hover {
+ color: #f5f5f5;
+ background-color: #636363;
+ border-color: #636363;
+}
.title {
font-size: 1.3rem;
diff --git a/web/components/infiniteGallery.js b/web/components/infiniteGallery.js
index 7eef7a4..625d8e1 100644
--- a/web/components/infiniteGallery.js
+++ b/web/components/infiniteGallery.js
@@ -3,6 +3,7 @@ import Grid from "../components/grid";
import InfiniteScroll from "react-infinite-scroll-component";
import { useState, useEffect } from "react";
import { baseURL } from "../constants.js";
+import { Loader } from "@mantine/core";
export default function InfiniteGallery(props) {
const [posts, setPosts] = useState(props.response.posts);
@@ -35,24 +36,32 @@ export default function InfiniteGallery(props) {
});
};
+ const loader = () => (
+
+
+
+ );
+
return (
{totalPosts != 0 && (
-
loading...}
- endMessage={
-
- thats all folks, go take some pictures...
-
- }
- style={{ overflowY: "hidden" }}
- >
-
-
-
+
+
+ thats all folks, go take some pictures...
+
+ }
+ style={{ overflowY: "hidden" }}
+ >
+
+
+
+
)}
{totalPosts == 0 && (
diff --git a/web/components/infiniteGallery.module.css b/web/components/infiniteGallery.module.css
index ce1d4c6..bb12597 100644
--- a/web/components/infiniteGallery.module.css
+++ b/web/components/infiniteGallery.module.css
@@ -2,6 +2,12 @@
padding-top: 3rem;
padding-bottom: 3rem;
text-align: center;
+}
+
+.end {
+ padding-top: 5rem;
+ padding-bottom: 5rem;
+ text-align: center;
font-weight: 400;
}
diff --git a/web/package-lock.json b/web/package-lock.json
index 9105ce3..c83e3c3 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -22,6 +22,7 @@
"@tabler/icons": "^1.118.0",
"embla-carousel-react": "^7.1.0",
"next": "^13.4.12",
+ "next-usequerystate": "^1.7.3",
"node-fetch": "^3.3.2",
"prettier": "^2.8.1",
"react": "^18.2.0",
@@ -3906,6 +3907,16 @@
}
}
},
+ "node_modules/next-usequerystate": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/next-usequerystate/-/next-usequerystate-1.7.3.tgz",
+ "integrity": "sha512-U45N1KGL7e6Rp8kRmisbM26q/sV2JFPahuV1H0SgF0VgG6970CdtheqLicytYURzgpc4twdiQ+4QgZhuFmX3Tg==",
+ "peerDependencies": {
+ "next": "*",
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
"node_modules/node-abi": {
"version": "3.24.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.24.0.tgz",
@@ -8363,6 +8374,12 @@
"zod": "3.21.4"
}
},
+ "next-usequerystate": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/next-usequerystate/-/next-usequerystate-1.7.3.tgz",
+ "integrity": "sha512-U45N1KGL7e6Rp8kRmisbM26q/sV2JFPahuV1H0SgF0VgG6970CdtheqLicytYURzgpc4twdiQ+4QgZhuFmX3Tg==",
+ "requires": {}
+ },
"node-abi": {
"version": "3.24.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.24.0.tgz",
diff --git a/web/package.json b/web/package.json
index 146a184..1cad1af 100644
--- a/web/package.json
+++ b/web/package.json
@@ -24,6 +24,7 @@
"@tabler/icons": "^1.118.0",
"embla-carousel-react": "^7.1.0",
"next": "^13.4.12",
+ "next-usequerystate": "^1.7.3",
"node-fetch": "^3.3.2",
"prettier": "^2.8.1",
"react": "^18.2.0",
diff --git a/web/pages/_app.js b/web/pages/_app.js
index bcfaade..06c6dea 100644
--- a/web/pages/_app.js
+++ b/web/pages/_app.js
@@ -5,7 +5,7 @@ import { MantineProvider } from "@mantine/core";
import { NavigationProgress } from "@mantine/nprogress";
const queries = {
- xs: "(max-width: 360px)",
+ xs: "(max-width: 480px)",
sm: "(max-width: 720px)",
md: "(max-width: 1024px)",
lg: "(max-width: 1440px)",
diff --git a/web/pages/post/[pid]/index.js b/web/pages/post/[pid]/index.js
index c9a8e44..2ce7258 100644
--- a/web/pages/post/[pid]/index.js
+++ b/web/pages/post/[pid]/index.js
@@ -39,9 +39,16 @@ export async function getStaticProps({ params }) {
if (post.nsfw) {
query = "";
}
+
const similarRoute = `/post/${params.pid}/similar` + query;
- const similarResponse = await authorized_fetch(similarRoute, "GET");
- const similar = await similarResponse.json();
+ let similar;
+ try {
+ const similarResponse = await authorized_fetch(similarRoute, "GET");
+ similar = await similarResponse.json();
+ } catch (e) {
+ similar = {};
+ }
+
return {
props: {
post,
diff --git a/web/stores/query.js b/web/stores/query.js
deleted file mode 100644
index 020e30b..0000000
--- a/web/stores/query.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import create from "zustand";
-
-const useQuery = create((set) => ({
- search: "",
- sort: "latest",
- nsfw: "exclude",
- bw: "exclude",
- sprocket: "include",
- setSearch: (search) => set({ search }),
- setSort: (sort) => set({ sort }),
- setNsfw: (nsfw) => set({ nsfw }),
- setBw: (bw) => set({ bw }),
- setSprocket: (sprocket) => set({ sprocket }),
-}));
-
-export default useQuery;