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() {

Rate Limiting

- 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.

- X-Ratelimit-Limit: 30 -

X-Ratelimit-Remaining: 29 + x-ratelimit-limit: 60 +

x-ratelimit-remaining: 59 +

x-ratelimit-reset: 1691712960
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 (
+ + + + + + with color +
+ + + + + + + + + +
+
+
+ - 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;