import { Avatar, Box, Breadcrumbs, Button, ButtonBase, Card, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, FormControlLabel, IconButton, Radio, RadioGroup, TextField, Typography, } from "@mui/material"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { IndexCategory, useIndexStore } from "../../state/indexes"; import CloseIcon from "@mui/icons-material/Close"; import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos"; import { hashWordWithoutPublicSalt } from "../../utils/encryption"; import { usePublish } from "../../hooks/usePublish"; import { dismissToast, showError, showLoading, showSuccess, } from "../../utils/toast"; import { objectToBase64 } from "../../utils/base64"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import { OptionsManager } from "../OptionsManager"; import ShortUniqueId from "short-unique-id"; import { Spacer } from "../../common/Spacer"; import { useModal } from "../useModal"; import { createAvatarLink } from "../../utils/qortal"; import { extractComponents } from "../../utils/text"; import NavigateNextIcon from "@mui/icons-material/NavigateNext"; const uid = new ShortUniqueId({ length: 10, dictionary: "alphanum" }); interface PropsMode { link: string; name: string; mode: number; setMode: (mode: number) => void; username: string; } interface PropsIndexManager { username: string | null; } const cleanString = (str: string) => str.replace(/\s{2,}/g, ' ').trim().toLocaleLowerCase(); export const IndexManager = ({ username }: PropsIndexManager) => { const open = useIndexStore((state) => state.open); const setOpen = useIndexStore((state) => state.setOpen); const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [hasMetadata, setHasMetadata] = useState(false); const [mode, setMode] = useState(1); const publish = usePublish(); const handleClose = () => { setOpen(null); setMode(1); setTitle(""); setDescription(""); setHasMetadata(false); }; const getMetadata = useCallback(async (name: string, link: string) => { try { const identifierWithoutHash = name + link; const identifier = await hashWordWithoutPublicSalt( identifierWithoutHash, 20 ); const rawData = await publish.fetchPublish( { name: name, service: "METADATA", identifier, } ); if ( rawData?.resource && rawData?.resource?.data?.title && rawData?.resource?.data?.description ) { setHasMetadata(true); setDescription(rawData?.resource?.data?.description); setTitle(rawData?.resource?.data?.title); } } catch (error) {} }, []); useEffect(() => { if (open?.name && open?.link) { setTimeout(() => { getMetadata(open?.name, open?.link); }, 100); } }, [open?.link, open?.name]); if (!open || !username) return null; return ( Index manager ({ position: "absolute", right: 8, top: 8, })} > {mode === 1 && ( )} {mode === 2 && ( )} {/* {mode === 3 && ( )} */} {mode === 4 && ( )} ); }; interface PropsEntryMode extends PropsMode { hasMetadata: boolean; } const EntryMode = ({ link, name, mode, setMode, username, hasMetadata, }: PropsEntryMode) => { return ( <> setMode(2)} > Create new index {/* Your indices */} setMode(4)} > Add metadata {hasMetadata && } ); }; interface PropsAddMetadata extends PropsMode { title: string; description: string; setTitle: (val: string) => void; setDescription: (val: string) => void; } const AddMetadata = ({ link, name, mode, setMode, username, title, description, setDescription, setTitle, }: PropsAddMetadata) => { const publish = usePublish(); const disableButton = !title.trim() || !description.trim() || !name || !link; const createMetadata = async () => { const loadId = showLoading("Publishing metadata..."); try { const identifierWithoutHash = name + link; const identifier = await hashWordWithoutPublicSalt( identifierWithoutHash, 20 ); const objectToPublish = { title, description, }; const toBase64 = await objectToBase64(objectToPublish); const res = await qortalRequest({ action: "PUBLISH_QDN_RESOURCE", service: "METADATA", identifier: identifier, data64: toBase64, }); if (res?.signature) { showSuccess("Successfully published metadata"); publish.updatePublish( { identifier, service: "METADATA", name: username, }, objectToPublish ); } } catch (error) { const message = error instanceof Error ? error.message : "Failed to publish metadata"; showError(message); } finally { dismissToast(loadId); } }; const res = extractComponents(link || ""); const appName = res?.name || ""; return ( <> setMode(1)}> Example of how it could look like: } aria-label="breadcrumb" > {res?.service} {appName} {res?.path} {title} {description} Title setTitle(e.target.value)} size="small" placeholder="Add a title for the link" slotProps={{ htmlInput: { maxLength: 50 }, }} fullWidth helperText={ = 50 ? "error" : "text.secondary"} > {title.length}/{50} characters } /> Description setDescription(e.target.value)} size="small" placeholder="Add a description for the link" slotProps={{ htmlInput: { maxLength: 120 }, }} helperText={ = 120 ? "error" : "text.secondary"} > {description.length}/{120} characters } /> ); }; interface PropsCreateIndex extends PropsMode { category: IndexCategory; rootName: string; } const CreateIndex = ({ link, name, mode, setMode, username, category, rootName, }: PropsCreateIndex) => { const [terms, setTerms] = useState([]); const publish = usePublish(); const [size, setSize] = useState(0); const [fullSize, setFullSize] = useState(0); const { isShow, onCancel, onOk, show } = useModal(); const [recommendedIndices, setRecommendedIndices] = useState([]) const [recommendedSelection, setRecommendedSelection] = useState("") const objectToPublish = useMemo(() => { if(recommendedSelection){ return [ { n: name, t: cleanString(recommendedSelection), c: category, l: link, } ] } return terms?.map((term) => { return { n: name, t: cleanString(term), c: category, l: link, }; }); }, [name, terms, category, link, recommendedSelection]); const objectToCalculateSize = useMemo(() => { return [ { n: name, t: "", c: category, l: link, }, ]; }, [name, category, link]); const shouldRecommendMax = !recommendedSelection && terms?.length === 1 && 230 - size > 50; const recommendedSize = 230 - size; useEffect(() => { const getSize = async (data: any, data2: any) => { try { const toBase64 = await objectToBase64(data); const size = toBase64?.length; setSize(size); const toBase642 = await objectToBase64(data2); const size2 = toBase642?.length; setFullSize(size2); } catch (error) {} }; getSize(objectToCalculateSize, objectToPublish); }, [objectToCalculateSize, objectToPublish]); const getRecommendedIndices = useCallback(async (nameParam: string, linkParam: string, rootNameParam: string)=> { try { const hashedRootName = await hashWordWithoutPublicSalt(rootNameParam, 20); const hashedLink = await hashWordWithoutPublicSalt(linkParam, 20); const identifier = `idx-${hashedRootName}-${hashedLink}-`; const res = await fetch(`/arbitrary/indices/${nameParam}/${identifier}`) const data = await res.json() const uniqueByTerm = data.filter( (item: any, index: number, self: any) => index === self.findIndex((t: any) => t.term === item.term) ); setRecommendedIndices(uniqueByTerm) } catch (error) { } }, []) useEffect(()=> { if(name && link && rootName){ getRecommendedIndices(name, link, rootName) } }, [name, link, rootName, getRecommendedIndices]) const disableButton = (terms.length === 0 && !recommendedSelection) || !name || !link; const createIndex = async () => { const loadId = showLoading("Publishing index..."); try { const hashedRootName = await hashWordWithoutPublicSalt(rootName, 20); const hashedLink = await hashWordWithoutPublicSalt(link, 20); const randomUid = uid.rnd(); const identifier = `idx-${hashedRootName}-${hashedLink}-${randomUid}`; const toBase64 = await objectToBase64(objectToPublish); const res = await qortalRequest({ action: "PUBLISH_QDN_RESOURCE", service: "JSON", identifier: identifier, data64: toBase64, }); if (res?.signature) { showSuccess("Successfully published index"); publish.updatePublish( { identifier, service: "JSON", name: username, }, objectToPublish ); setTerms([]); } } catch (error) { const message = error instanceof Error ? error.message : "Failed to publish index"; showError(message); } finally { dismissToast(loadId); } }; const handleChange = (event: React.ChangeEvent) => { setRecommendedSelection((event.target as HTMLInputElement).value); }; return ( <> setMode(1)}> {recommendedIndices?.length > 0 && ( <> Recommended Indices {recommendedIndices?.map((ri: any, i)=> { return } label={ri?.term} /> })} )} } label="Add search term" /> {!recommendedSelection && ( { try { if (terms?.length === 1 && termsNew?.length === 2) { await show({ message: "", }); } setTerms(termsNew); } catch (error) { //error } }} /> )} 230 ? "visible" : "hidden", }} > It is recommended to keep your term character count below{" "} {recommendedSize} characters Adding multiple indices Subsequent indices will keep your publish fees lower, but they will have less strength in future search results. ); }; const YourIndices = ({ link, name, mode, setMode, username, category, rootName, }: PropsCreateIndex) => { const [terms, setTerms] = useState([]); const publish = usePublish(); const [size, setSize] = useState(0); const [fullSize, setFullSize] = useState(0); const { isShow, onCancel, onOk, show } = useModal(); const [recommendedIndices, setRecommendedIndices] = useState([]) const [recommendedSelection, setRecommendedSelection] = useState("") const objectToPublish = useMemo(() => { if(recommendedSelection){ return [ { n: name, t: recommendedSelection.toLocaleLowerCase(), c: category, l: link, } ] } return terms?.map((term) => { return { n: name, t: term.toLocaleLowerCase(), c: category, l: link, }; }); }, [name, terms, category, link, recommendedSelection]); const objectToCalculateSize = useMemo(() => { return [ { n: name, t: "", c: category, l: link, }, ]; }, [name, category, link]); const shouldRecommendMax = !recommendedSelection && terms?.length === 1 && 230 - size > 50; const recommendedSize = 230 - size; useEffect(() => { const getSize = async (data: any, data2: any) => { try { const toBase64 = await objectToBase64(data); const size = toBase64?.length; setSize(size); const toBase642 = await objectToBase64(data2); const size2 = toBase642?.length; setFullSize(size2); } catch (error) {} }; getSize(objectToCalculateSize, objectToPublish); }, [objectToCalculateSize, objectToPublish]); const getYourIndices = useCallback(async (nameParam: string, linkParam: string, rootNameParam: string)=> { try { const hashedRootName = await hashWordWithoutPublicSalt(rootNameParam, 20); const hashedLink = await hashWordWithoutPublicSalt(linkParam, 20); const identifier = `idx-${hashedRootName}-${hashedLink}-`; const res = await fetch(`/arbitrary/indices/${nameParam}/${identifier}`) const data = await res.json() setRecommendedIndices(data) } catch (error) { } }, []) useEffect(()=> { if(username && link && rootName){ getYourIndices(username, link, rootName) } }, [username, link, rootName, getYourIndices]) const disableButton = (terms.length === 0 && !recommendedSelection) || !name || !link; const createIndex = async () => { const loadId = showLoading("Publishing index..."); try { const hashedRootName = await hashWordWithoutPublicSalt(rootName, 20); const hashedLink = await hashWordWithoutPublicSalt(link, 20); const randomUid = uid.rnd(); const identifier = `idx-${hashedRootName}-${hashedLink}-${randomUid}`; const toBase64 = await objectToBase64(objectToPublish); const res = await qortalRequest({ action: "PUBLISH_QDN_RESOURCE", service: "JSON", identifier: identifier, data64: toBase64, }); if (res?.signature) { showSuccess("Successfully published index"); publish.updatePublish( { identifier, service: "JSON", name: username, }, objectToPublish ); setTerms([]); } } catch (error) { const message = error instanceof Error ? error.message : "Failed to publish index"; showError(message); } finally { dismissToast(loadId); } }; const handleChange = (event: React.ChangeEvent) => { setRecommendedSelection((event.target as HTMLInputElement).value); }; return ( <> setMode(1)}> {recommendedIndices?.length > 0 && ( <> Recommended Indices {recommendedIndices?.map((ri: any, i)=> { return } label={ri?.term} /> })} )} } label="Add search term" /> {!recommendedSelection && ( { try { if (terms?.length === 1 && termsNew?.length === 2) { await show({ message: "", }); } setTerms(termsNew); } catch (error) { //error } }} /> )} 230 ? "visible" : "hidden", }} > It is recommended to keep your term character count below{" "} {recommendedSize} characters Adding multiple indices Subsequent indices will keep your publish fees lower, but they will have less strength in future search results. ); };