diff --git a/.changeset/many-ways-smell.md b/.changeset/many-ways-smell.md new file mode 100644 index 00000000..3d266c1c --- /dev/null +++ b/.changeset/many-ways-smell.md @@ -0,0 +1,5 @@ +--- +"@namehash/namegraph-sdk": minor +--- + +Add fetchCollectionsByMember method in NameGraph SDK package diff --git a/apps/namegraph.dev/app/collections/[id]/explore-collection-page.tsx b/apps/namegraph.dev/app/collections/[id]/explore-collection-page.tsx index ae29f7ca..1f33ffe8 100644 --- a/apps/namegraph.dev/app/collections/[id]/explore-collection-page.tsx +++ b/apps/namegraph.dev/app/collections/[id]/explore-collection-page.tsx @@ -1,4 +1,5 @@ "use client"; +/* eslint-disable react-hooks/exhaustive-deps */ import { fetchCollectionMembers, @@ -11,6 +12,7 @@ import { NameGraphCollection, NameGraphFindCollectionsResponse, NameGraphSuggestion, + ScrambleMethod, } from "@namehash/namegraph-sdk/utils"; import { useEffect, useState } from "react"; import { Noto_Emoji } from "next/font/google"; @@ -18,6 +20,15 @@ import { useQueryParams } from "@/components/use-query-params"; import { Button } from "@/components/ui/button"; import { ChevronLeft, ChevronRight, Loader } from "lucide-react"; import Skeleton from "@/components/skeleton"; +import { CollectionCard } from "@/components/collections/collection-card"; +import { Tooltip } from "@namehash/namekit-react/client"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; const notoBlack = Noto_Emoji({ preload: false }); @@ -30,6 +41,16 @@ interface SuggestionsData { suggestions: NameGraphSuggestion[]; } +const FromNameGraphScrambleMethodToDropdownTextContent: Record< + ScrambleMethod, + string +> = { + [ScrambleMethod["full-shuffle"]]: "Full Shuffle", + [ScrambleMethod["left-right-shuffle"]]: "Left - Right Shuffle", + [ScrambleMethod["left-right-shuffle-with-unigrams"]]: + "Left - Right Shuffle with Unigrams", +}; + export const ExploreCollectionPage = ({ id }: { id: string }) => { const [loadingCollection, setLoadingCollection] = useState(true); const [loadingCollectionMembers, setLoadingCollectionMembers] = @@ -84,9 +105,6 @@ export const ExploreCollectionPage = ({ id }: { id: string }) => { loadCollectionMembers(); loadCollectionRelatedCollections(); }, [id]); - useEffect(() => { - console.log(collectionMembers); - }, [collectionMembers]); const loadCollectionMembers = () => { if (!!collectionMembers?.[params.page]) { @@ -95,7 +113,7 @@ export const ExploreCollectionPage = ({ id }: { id: string }) => { setLoadingCollectionMembers(true); fetchCollectionMembers(id, { - offset: params.page - 1, + offset: (params.page - 1) * navigationConfig.itemsPerPage, }) .then((res) => { if (res) { @@ -155,12 +173,19 @@ export const ExploreCollectionPage = ({ id }: { id: string }) => { } }; + const [scrambleMethod, setScrambleMethod] = useState( + ScrambleMethod["left-right-shuffle-with-unigrams"], + ); + const scramble = () => { if (id) { const randomSeed = Number(Number(Math.random() * 10).toFixed(0)); setLoadingScramble(true); - scrambleNamesByCollectionId(id, { seed: randomSeed }) + scrambleNamesByCollectionId(id, { + seed: randomSeed, + method: scrambleMethod, + }) .then((res) => { setScrambledNameIdeas(res); }) @@ -221,9 +246,8 @@ export const ExploreCollectionPage = ({ id }: { id: string }) => { const isLastCollectionsPageForCurrentQuery = () => { if (navigationConfig.totalItems) { return ( - Number(params.page) !== 1 && - Number(params.page) * navigationConfig.itemsPerPage > - navigationConfig.totalItems + Number(params.page) * navigationConfig.itemsPerPage >= + navigationConfig.totalItems ); } else return false; }; @@ -280,7 +304,7 @@ export const ExploreCollectionPage = ({ id }: { id: string }) => { {/* Collections List */}
{loadingCollection ? ( -
+
) : collection ? ( @@ -289,17 +313,74 @@ export const ExploreCollectionPage = ({ id }: { id: string }) => {
-
- {/* Collection Count and Sort */} -
-
-
- {getNavigationPageTextGuide()} +
+
+ {/* Collection Count and Sort */} +
+
+
+ {getNavigationPageTextGuide()} +
+ {navigationConfig.totalItems ? ( +
+ + +
+ ) : null}
- {navigationConfig.totalItems ? ( -
+
+ {/* Collections List */} +
+ {loadingCollectionMembers ? ( +
+ +
+ ) : collectionMembers ? ( + collectionMembers[ + params.page + ]?.suggestions.map((suggestion) => ( +
+
+ {suggestion.name} +
+
+ )) + ) : null} +
+
+
+ {/* Pagination */} + {navigationConfig.totalItems ? ( +
+
+ {getNavigationPageTextGuide()} +
+
- ) : null} -
-
- {/* Collections List */} -
- {loadingCollectionMembers ? ( -
-
- ) : collectionMembers ? ( - collectionMembers[params.page]?.suggestions.map( - (suggestion) => ( -
-
- {suggestion.name} -
-
- ), - ) ) : null}
- {/* Pagination */} - {navigationConfig.totalItems ? ( -
-
- {getNavigationPageTextGuide()} -
-
- - -
-
- ) : null}
-
-
-

- Interact with this collection -

-
- +
+
+ + Sample names +

+ } + > +

Sampling names means creating

+
- -
+
- {scrambledNameIdeas ? ( -
-
- Scrambled name ideas -
- {loadingScramble ? ( - + {sampledNameIdeas ? ( +
+ {loadingIdeate ? ( +
+ +
) : ( -
- {scrambledNameIdeas?.map((suggestion) => { +
+ {sampledNameIdeas?.map((suggestion) => { return (
{ )}
) : null} +
+
- {sampledNameIdeas ? ( -
-
- Sampled name ideas -
- {loadingIdeate ? ( - +
+
+ + Scramble names +

+ } + > +

Scrambling names means creating

+
+ + +
+
+ {scrambledNameIdeas ? ( +
+ {loadingScramble ? ( +
+ +
) : ( -
- {sampledNameIdeas?.map((suggestion) => { +
+ {scrambledNameIdeas?.map((suggestion) => { return (
{

Related collections

- {relatedCollections ? ( - relatedCollections.related_collections.map( - (collection) => ( -
-
-
-

- {collection.avatar_emoji} -

-
-
-
-

- {collection.title} -

-

- by {collection.owner} -

-
-
- {collection.top_names.map((tag) => ( - - {tag.name} - - ))} -
-
-
-
-
- ), - ) - ) : ( -
- No name suggestions found -
- )} + +
+ {relatedCollections ? ( + relatedCollections.related_collections.map( + (collection) => ( + + ), + ) + ) : ( +
+ No name suggestions found +
+ )} +

Other collections

- {relatedCollections ? ( - relatedCollections.other_collections.map( - (collection) => ( -
-
-
-

- {collection.avatar_emoji} -

-
-
-
-

- {collection.title} -

-

- by {collection.owner} -

-
-
- {collection.top_names.map((tag) => ( - - {tag.name} - - ))} -
-
-
-
-
- ), - ) - ) : ( -
- No name suggestions found -
- )} +
+ {relatedCollections ? ( + relatedCollections.other_collections.map( + (collection) => ( + + ), + ) + ) : ( +
No name suggestions found
+ )} +
diff --git a/apps/namegraph.dev/app/collections/page.tsx b/apps/namegraph.dev/app/collections/page.tsx index 5d3c9e0e..5b2802fb 100644 --- a/apps/namegraph.dev/app/collections/page.tsx +++ b/apps/namegraph.dev/app/collections/page.tsx @@ -5,9 +5,10 @@ import { NameGraphCollection, NameGraphSortOrderOptions, } from "@namehash/namegraph-sdk/utils"; -import { findCollectionsByString } from "@/lib/utils"; +import { findCollectionsByMember, findCollectionsByString } from "@/lib/utils"; import { DebounceInput } from "react-debounce-input"; import { Suspense, useEffect, useState } from "react"; +import { Toggle } from "@/components/ui/toggle"; import { Button } from "@/components/ui/button"; import { Select, @@ -22,10 +23,8 @@ import { CollectionsGridSkeleton, } from "@/components/collections/collections-grid-skeleton"; import { useQueryParams } from "@/components/use-query-params"; -import { Noto_Emoji } from "next/font/google"; -import { Link } from "@namehash/namekit-react"; +import { CollectionCard } from "@/components/collections/collection-card"; -const notoBlack = Noto_Emoji({ preload: false }); interface NavigationConfig { itemsPerPage: number; totalItems?: number; @@ -33,7 +32,7 @@ interface NavigationConfig { interface CollectionsData { sort_order: NameGraphSortOrderOptions; - other_collections: NameGraphCollection[]; + other_collections: NameGraphCollection[] | null; related_collections: NameGraphCollection[]; } @@ -56,6 +55,7 @@ export default function ExploreCollectionsPage() { search: "", page: DEFAULT_PAGE_NUMBER, orderBy: NameGraphSortOrderOptions.AI, + exactMatch: false, }; type DefaultDomainFiltersType = typeof DEFAULT_COLLECTIONS_PARAMS; const { params, setParams } = useQueryParams( @@ -67,14 +67,45 @@ export default function ExploreCollectionsPage() { search: searchTerm, page: DEFAULT_PAGE_NUMBER, // Resets page when search changes }); + + setNavigationConfig({ + ...navigationConfig, + totalItems: undefined, + }); + + if (!params.search) { + return; + } + + setCollections(null); + setLoadingCollections(true); + + queryCollections({ + search: params.search || "", + orderBy: params.orderBy || NameGraphSortOrderOptions.AI, + page: params.page || 1, + exactMatch: params.exactMatch, + }); }; const handleOrderBy = (orderBy: NameGraphSortOrderOptions) => { setParams({ orderBy }); + queryCollections({ + search: params.search || "", + orderBy: orderBy || NameGraphSortOrderOptions.AI, + page: params.page || 1, + exactMatch: params.exactMatch, + }); }; const handlePageChange = (page: number) => { setParams({ page }); + queryCollections({ + search: params.search || "", + orderBy: params.orderBy || NameGraphSortOrderOptions.AI, + page: page || 1, + exactMatch: params.exactMatch, + }); }; /** @@ -87,8 +118,15 @@ export default function ExploreCollectionsPage() { const [collections, setCollections] = useState< undefined | null | Record >(undefined); - const [collectionsQueriedStandFor, setCollectionsQueriedStandFor] = - useState(""); + const [lastQueryDone, setLastQueryDone] = useState<{ + search: string; + exactMatch: boolean; + }>({ search: params.search || "", exactMatch: params.exactMatch || false }); + + useEffect(() => { + console.log("New values for collections results:"); + console.log(collections, params); + }, [collections]); const [loadingCollections, setLoadingCollections] = useState(true); @@ -98,13 +136,21 @@ export default function ExploreCollectionsPage() { totalItems: undefined, }); - const loadCollections = () => { + interface QueryCollectionsParam { + exactMatch: boolean; + orderBy: NameGraphSortOrderOptions; + search: string; + page: number; + } + + const queryCollections = (params: QueryCollectionsParam) => { if (params.search) { let query = params.search; if (params.search.includes(".")) { query = params.search.split(".")[0]; } + const MAX_COLLECTIONS_FOR_EXACT_MATCH = 10; const MAX_RELATED_COLLECTIONS = 20; const OTHER_COLLECTIONS_NUMBER = 5; @@ -117,103 +163,152 @@ export default function ExploreCollectionsPage() { * * Of course this is only true if both the query and the sorting * algorithm lastly used are the same. If any of these have changes, - * we do the loadCollections query once again and update the page's results. + * we do the queryCollections query once again and update the page's results. */ if ( - !!collectionsQueriedStandFor && - collectionsQueriedStandFor === params.search && + !!lastQueryDone && + lastQueryDone.search === params.search && !!collections?.[params.page] && - params.orderBy == collections?.[params.page]?.sort_order + params.orderBy == collections?.[params.page]?.sort_order && + params.exactMatch === lastQueryDone.exactMatch ) { return; } setLoadingCollections(true); - setCollectionsQueriedStandFor(query); - findCollectionsByString(query, { - offset: params.page - 1, - sort_order: params.orderBy, - max_total_collections: - MAX_RELATED_COLLECTIONS + OTHER_COLLECTIONS_NUMBER, - /** - * Please note how the number of collections one page show is - * strategically aligned with ITEMS_PER_PAGE_OPTIONS. - */ - max_related_collections: MAX_RELATED_COLLECTIONS, - max_other_collections: OTHER_COLLECTIONS_NUMBER, - min_other_collections: OTHER_COLLECTIONS_NUMBER, - }) - .then((res) => { - if (res) { - const MAX_COLLECTIONS_NUMBER_NAME_API_CAN_DOCUMENT = 1000; - - setNavigationConfig({ - ...navigationConfig, - totalItems: - typeof res.metadata.total_number_of_matched_collections === - "number" - ? res.metadata.total_number_of_matched_collections - : /** - * NameAPI makes usage of a stringified "+1000" for - * res.metadata.total_number_of_matched_collections - * if there are more than 1000 collections this query - * is contained in. Since this is a different data type - * and we are handling number operations with totalItems, - * we are here normalizing this use case to use the number 1000 - */ - MAX_COLLECTIONS_NUMBER_NAME_API_CAN_DOCUMENT, - }); - const moreCollections = res.other_collections; - const relatedCollections = res.related_collections; + if (params.exactMatch) { + findCollectionsByMember(query, { + offset: (params.page - 1) * navigationConfig.itemsPerPage, + sort_order: params.orderBy, + limit_names: MAX_COLLECTIONS_FOR_EXACT_MATCH, + /** + * Please note how the number of collections one page show is + * strategically aligned with ITEMS_PER_PAGE_OPTIONS. + */ + max_results: MAX_COLLECTIONS_FOR_EXACT_MATCH, + }) + .then((res) => { + if (res) { + const MAX_COLLECTIONS_NUMBER_NAME_API_CAN_DOCUMENT = 1000; + + setNavigationConfig({ + ...navigationConfig, + totalItems: + typeof res.metadata.total_number_of_matched_collections === + "number" + ? res.metadata.total_number_of_matched_collections + : /** + * NameAPI makes usage of a stringified "+1000" for + * res.metadata.total_number_of_matched_collections + * if there are more than 1000 collections this query + * is contained in. Since this is a different data type + * and we are handling number operations with totalItems, + * we are here normalizing this use case to use the number 1000 + */ + MAX_COLLECTIONS_NUMBER_NAME_API_CAN_DOCUMENT, + }); + + const relatedCollections = res.collections; + setCollections({ + ...collections, + [params.page]: { + sort_order: params.orderBy, + related_collections: relatedCollections, + other_collections: null, + }, + }); + } else { + setCollections({ + ...collections, + [params.page]: null, + }); + } + }) + .catch(() => { setCollections({ ...collections, - [params.page]: { - sort_order: params.orderBy, - related_collections: relatedCollections, - other_collections: moreCollections, - }, + [params.page]: null, }); + }) + .finally(() => { setLoadingCollections(false); - } else { + }); + } else { + findCollectionsByString(query, { + offset: (params.page - 1) * navigationConfig.itemsPerPage, + sort_order: params.orderBy, + max_total_collections: + MAX_RELATED_COLLECTIONS + OTHER_COLLECTIONS_NUMBER, + /** + * Please note how the number of collections one page show is + * strategically aligned with ITEMS_PER_PAGE_OPTIONS. + */ + max_related_collections: MAX_RELATED_COLLECTIONS, + max_other_collections: OTHER_COLLECTIONS_NUMBER, + min_other_collections: OTHER_COLLECTIONS_NUMBER, + }) + .then((res) => { + if (res) { + const MAX_COLLECTIONS_NUMBER_NAME_API_CAN_DOCUMENT = 1000; + + setNavigationConfig({ + ...navigationConfig, + totalItems: + typeof res.metadata.total_number_of_matched_collections === + "number" + ? res.metadata.total_number_of_matched_collections + : /** + * NameAPI makes usage of a stringified "+1000" for + * res.metadata.total_number_of_matched_collections + * if there are more than 1000 collections this query + * is contained in. Since this is a different data type + * and we are handling number operations with totalItems, + * we are here normalizing this use case to use the number 1000 + */ + MAX_COLLECTIONS_NUMBER_NAME_API_CAN_DOCUMENT, + }); + + const moreCollections = res.other_collections; + const relatedCollections = res.related_collections; + + setCollections({ + ...collections, + [params.page]: { + sort_order: params.orderBy, + related_collections: relatedCollections, + other_collections: moreCollections, + }, + }); + } else { + setCollections({ + ...collections, + [params.page]: null, + }); + } + }) + .catch(() => { setCollections({ ...collections, [params.page]: null, }); - } - }) - .catch(() => { - setCollections({ - ...collections, - [params.page]: null, + }) + .finally(() => { + setLoadingCollections(false); }); - }); + } + + setLastQueryDone({ + search: params.search, + exactMatch: params.exactMatch, + }); } else { setCollections(null); setLoadingCollections(false); } }; - useEffect(() => { - setNavigationConfig({ - ...navigationConfig, - totalItems: undefined, - }); - - if (!params.search) { - return; - } - - setCollections(null); - setLoadingCollections(true); - loadCollections(); - }, [params.search]); - - useEffect(() => { - loadCollections(); - }, [params.page, params.orderBy]); - /** * Navigation helper functions */ @@ -223,9 +318,8 @@ export default function ExploreCollectionsPage() { const isLastCollectionsPageForCurrentQuery = () => { if (navigationConfig.totalItems) { return ( - Number(params.page) !== 1 && - Number(params.page) * navigationConfig.itemsPerPage > - navigationConfig.totalItems + Number(params.page) * navigationConfig.itemsPerPage >= + navigationConfig.totalItems ); } else return false; }; @@ -236,9 +330,15 @@ export default function ExploreCollectionsPage() { Number(params.page) * navigationConfig.itemsPerPage, navigationConfig.totalItems, )} of ${navigationConfig.totalItems} collections` - : "No collections found"; + : !loadingCollections + ? "No collections found" + : ""; }; + useEffect(() => { + handleSearch(params.search); + }, [params.search]); + return ( Loading...
}>
@@ -284,7 +384,9 @@ export default function ExploreCollectionsPage() {
{/* Collection Count and Sort */} -
+
{getNavigationPageTextGuide()} @@ -312,93 +414,86 @@ export default function ExploreCollectionsPage() {
) : null}
- {collections[params.page] ? ( -
-
- Sort by + +
+ {collections[params.page] ? ( +
+ { + setNavigationConfig({ + ...navigationConfig, + totalItems: undefined, + }); + setParams({ exactMatch: pressed }); + queryCollections({ + search: params.search || "", + orderBy: + params.orderBy || + NameGraphSortOrderOptions.AI, + page: params.page || 1, + exactMatch: pressed, + }); + }} + > + Exact Match +
- -
- ) : null} + ) : null} + {collections[params.page] ? ( +
+
+ Sort by +
+ +
+ ) : null} +
{/* Collections List */} -
+
{loadingCollections ? ( -
+
) : collections[params.page] ? ( collections[params.page]?.related_collections.map( (collection) => ( - -
-
-

- {collection.avatar_emoji} -

-
-
-
-

- {collection.title} -

-

- by {collection.owner} -

-
-
- {collection.top_names.map((tag) => ( - - {tag.name} - - ))} -
-
-
-
- + collection={collection} + /> ), ) ) : null} @@ -435,63 +530,26 @@ export default function ExploreCollectionsPage() { ) : null}
-
-

- Other collections -

- {collections[params.page] ? ( - collections[params.page]?.other_collections.map( - (collection) => ( - -
-
-

- {collection.avatar_emoji} -

-
-
-
-

- {collection.title} -

-

- by {collection.owner} -

-
-
- {collection.top_names.map((tag) => ( - - {tag.name} - - ))} -
-
-
-
- - ), - ) - ) : ( -
No collections found
- )} -
+ {collections[params.page]?.other_collections && ( +
+

+ Other collections +

+
+ {collections[params.page]?.other_collections?.map( + (collection) => ( + + ), + )} +
+
+ )}
- ) : !loadCollections && !params.search && !collections ? ( + ) : !params.search && !collections ? ( <>Error ) : null} diff --git a/apps/namegraph.dev/components/collections/collection-card.tsx b/apps/namegraph.dev/components/collections/collection-card.tsx new file mode 100644 index 00000000..684c2236 --- /dev/null +++ b/apps/namegraph.dev/components/collections/collection-card.tsx @@ -0,0 +1,51 @@ +import { NameGraphCollection } from "@namehash/namegraph-sdk/utils"; +import { Link } from "@namehash/namekit-react"; +import { Noto_Emoji } from "next/font/google"; + +const notoBlack = Noto_Emoji({ preload: false }); + +export const CollectionCard = ({ + collection, +}: { + collection: NameGraphCollection; +}) => { + return ( + +
+
+

+ {collection.avatar_emoji} +

+
+
+
+

{collection.title}

+

+ by {collection.owner} +

+
+
+ {collection.top_names.map((tag) => ( + + {tag.name} + + ))} +
+
+
+
+ + ); +}; diff --git a/apps/namegraph.dev/components/collections/collections-grid-skeleton.tsx b/apps/namegraph.dev/components/collections/collections-grid-skeleton.tsx index 104468af..18330f6c 100644 --- a/apps/namegraph.dev/components/collections/collections-grid-skeleton.tsx +++ b/apps/namegraph.dev/components/collections/collections-grid-skeleton.tsx @@ -3,7 +3,7 @@ import Skeleton from "../skeleton"; export const CollectionsGridSkeleton = () => { return (
-
+
@@ -23,10 +23,10 @@ export const CollectionsGridSkeleton = () => {
-
- +
+
-
+
@@ -39,7 +39,7 @@ const NUMBER_OF_SKELETONS_OF_COLLECTION_CARDS = 5; export const CollectionsCardsSkeleton = () => { return ( - <> +
{[...Array(NUMBER_OF_SKELETONS_OF_COLLECTION_CARDS).keys()].map( (collection) => { return ( @@ -49,17 +49,17 @@ export const CollectionsCardsSkeleton = () => { ); }, )} - +
); }; const CollectionCardSkeleton = () => { return ( -
+
- +
-
+
diff --git a/apps/namegraph.dev/components/ui/toggle.tsx b/apps/namegraph.dev/components/ui/toggle.tsx new file mode 100644 index 00000000..9c930a48 --- /dev/null +++ b/apps/namegraph.dev/components/ui/toggle.tsx @@ -0,0 +1,45 @@ +"use client" + +import * as React from "react" +import * as TogglePrimitive from "@radix-ui/react-toggle" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const toggleVariants = cva( + "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-neutral-100 hover:text-neutral-500 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-neutral-100 data-[state=on]:text-neutral-900 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:hover:bg-neutral-800 dark:hover:text-neutral-400 dark:focus-visible:ring-neutral-300 dark:data-[state=on]:bg-neutral-800 dark:data-[state=on]:text-neutral-50", + { + variants: { + variant: { + default: "bg-transparent", + outline: + "border border-neutral-200 bg-transparent shadow-sm hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + }, + size: { + default: "h-9 px-2 min-w-9", + sm: "h-8 px-1.5 min-w-8", + lg: "h-10 px-2.5 min-w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +const Toggle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, size, ...props }, ref) => ( + +)) + +Toggle.displayName = TogglePrimitive.Root.displayName + +export { Toggle, toggleVariants } diff --git a/apps/namegraph.dev/lib/utils.ts b/apps/namegraph.dev/lib/utils.ts index f06d2775..db52b383 100644 --- a/apps/namegraph.dev/lib/utils.ts +++ b/apps/namegraph.dev/lib/utils.ts @@ -3,12 +3,14 @@ import { twMerge } from "tailwind-merge"; import { DEFAULT_FULL_MODE, NameGraphCollection, + NameGraphCollectionByMemberResponse, NameGraphFetchTopCollectionMembersResponse, NameGraphFindCollectionsResponse, NameGraphGroupedByCategoryResponse, NameGraphGroupingCategory, NameGraphSortOrderOptions, NameGraphSuggestion, + ScrambleMethod, } from "@namehash/namegraph-sdk/utils"; import { createNameGraphClient } from "@namehash/namegraph-sdk"; @@ -159,6 +161,21 @@ export const fetchCollectionMembers = async ( return nameGeneratorSuggestions; }; +export const findCollectionsByMember = async ( + query: string, + options?: { + offset?: number; + max_results?: number; + limit_names?: number; + sort_order?: NameGraphSortOrderOptions; + }, +): Promise => { + const nameGeneratorSuggestions = + await NameGraphClient.findCollectionsByMember(query, options); + + return nameGeneratorSuggestions; +}; + export const getCollectionById = async ( collection_id: string, ): Promise => { @@ -183,6 +200,7 @@ export const scrambleNamesByCollectionId = async ( collectionId: string, options?: { seed?: number; + method?: ScrambleMethod; }, ): Promise => { const nameGeneratorSuggestions = diff --git a/apps/namegraph.dev/package.json b/apps/namegraph.dev/package.json index 1e63021d..780e7e7b 100644 --- a/apps/namegraph.dev/package.json +++ b/apps/namegraph.dev/package.json @@ -15,6 +15,7 @@ "@radix-ui/react-scroll-area": "^1.2.1", "@radix-ui/react-select": "2.1.4", "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-toggle": "^1.1.1", "class-variance-authority": "^0.7.1", "classcat": "5.0.5", "clsx": "^2.1.1", diff --git a/packages/namegraph-sdk/src/index.ts b/packages/namegraph-sdk/src/index.ts index 5e71dc52..69af50b1 100644 --- a/packages/namegraph-sdk/src/index.ts +++ b/packages/namegraph-sdk/src/index.ts @@ -21,6 +21,7 @@ import { DEFAULT_INSTANT_MODE, NameGraphSortOrderOptions, NameGraphCollection, + ScrambleMethod, } from "./utils"; export class NameGraph { @@ -118,7 +119,8 @@ export class NameGraph { }, ): Promise { const max_sample_size = 5; - const seed = options?.seed || 0; + const random_seed = Number(Number(Math.random() * 10).toFixed(0)); + const seed = options?.seed || random_seed; const payload = { collection_id, @@ -149,12 +151,15 @@ export class NameGraph { collection_id: string, options?: { seed?: number; + method?: ScrambleMethod; }, ): Promise { - const method = "left-right-shuffle-with-unigrams"; + const default_method = ScrambleMethod["left-right-shuffle-with-unigrams"]; + const method = options?.method || default_method; const n_top_members = 25; const max_suggestions = 10; - const seed = options?.seed || 0; + const random_seed = Number(Number(Math.random() * 10).toFixed(0)); + const seed = options?.seed || random_seed; const payload = { collection_id, @@ -276,11 +281,17 @@ export class NameGraph { public findCollectionsByMember( label: string, + options?: { + offset?: number; + max_results?: number; + limit_names?: number; + sort_order?: NameGraphSortOrderOptions; + }, ): Promise { - const limit_names = 10; - const offset = 0; - const sort_order = "AI"; - const max_results = 3; + const limit_names = options?.limit_names || 10; + const offset = options?.offset || 0; + const sort_order = options?.sort_order || NameGraphSortOrderOptions.AI; + const max_results = options?.max_results || 3; const payload = { limit_names, diff --git a/packages/namegraph-sdk/src/utils.ts b/packages/namegraph-sdk/src/utils.ts index 251232da..bfb64120 100644 --- a/packages/namegraph-sdk/src/utils.ts +++ b/packages/namegraph-sdk/src/utils.ts @@ -109,7 +109,7 @@ export type NameGraphCollectionByMemberResponse = { elasticsearch_processing_time_ms: number; elasticsearch_communication_time_ms: number; }; - collections: NameGraphSuggestion[]; + collections: NameGraphCollection[]; }; export type NameGraphCollection = { @@ -245,3 +245,12 @@ export const sampleWritersBlockSuggestions = ( return result; }; + +export const ScrambleMethod = { + "left-right-shuffle": "left-right-shuffle", + "left-right-shuffle-with-unigrams": "left-right-shuffle-with-unigrams", + "full-shuffle": "full-shuffle", +} as const; + +export type ScrambleMethod = + (typeof ScrambleMethod)[keyof typeof ScrambleMethod]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee729f1e..ac51609c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,6 +148,9 @@ importers: '@radix-ui/react-slot': specifier: ^1.1.1 version: 1.1.1(@types/react@18.3.1)(react@18.3.1) + '@radix-ui/react-toggle': + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -1986,6 +1989,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-toggle@1.1.1': + resolution: {integrity: sha512-i77tcgObYr743IonC1hrsnnPmszDRn8p+EGUsUt+5a/JFn28fxaM88Py6V2mc8J5kELMWishI0rLnuGLFD/nnQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-use-callback-ref@1.1.0': resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: @@ -6796,6 +6812,17 @@ snapshots: optionalDependencies: '@types/react': 18.3.1 + '@radix-ui/react-toggle@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.1)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.1)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.1 + '@types/react-dom': 18.3.1 + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.1)(react@18.3.1)': dependencies: react: 18.3.1