import React from "react" import ClickAwayListener from "react-click-away-listener" import { Center, Flex, Input } from "@chakra-ui/react" import Fuse from "fuse.js" import { Rainbow } from "../Rainbow/Rainbow" import { Thingtime } from "../Thingtime/Thingtime" import { useThingtime } from "../Thingtime/useThingtime" import { sanitise } from "~/functions/sanitise" import { getParentPath } from "~/smarts" export const Commander = (props) => { const { thingtime, setThingtime, getThingtime, thingtimeRef, paths } = useThingtime() const inputRef = React.useRef() const [inputValue, setInputValue] = React.useState("") const [virtualValue, setVirtualValue] = React.useState("") const [hoveredSuggestion, setHoveredSuggestion] = React.useState() const [active, setActive] = React.useState(false) const [contextPath, setContextPath] = React.useState() const [showContext, setShowContextState] = React.useState(false) const mobileVW = React.useMemo(() => { return "calc(100vw - 55px)" }, []) const rainbowRepeats = 2 const setShowContext = React.useCallback( (value, from?: string) => { setShowContextState(value) }, [setShowContextState] ) // const [suggestions, setSuggestions] = React.useState([]) const contextValue = React.useMemo(() => { // TODO: Figure out why this is running on every click const ret = getThingtime(contextPath) return ret }, [contextPath, getThingtime]) const commanderActive = React.useMemo(() => { return thingtime?.settings?.commanderActive }, [thingtime?.settings?.commanderActive]) console.log("nik commanderActive", commanderActive) // commanderActive useEffect React.useEffect(() => { if (commanderActive) { inputRef?.current?.focus?.() } else { document.activeElement.blur() if (thingtimeRef?.current?.settings?.clearCommanderOnToggle) { setInputValue("") setHoveredSuggestion(null) } if (thingtimeRef?.current?.settings?.clearCommanderContextOnToggle) { setShowContext(false, "commanderActive useEffect") } if (contextPath !== undefined) { setContextPath(undefined) } if (showContext !== false) { setShowContext(false) } } }, [ commanderActive, thingtimeRef, setShowContext, inputValue, contextPath, showContext, ]) const onInputChange = React.useCallback((e) => { setInputValue(e.target.value) setHoveredSuggestion(null) }, []) const validSetters = React.useMemo(() => { return ["=", " is "] }, []) const command = React.useMemo(() => { // const sanitizedCommand = sanitise(value) // const sanitizedCommand = inputValue const sanitizedCommand = virtualValue if (sanitizedCommand?.includes(validSetters[0])) { const indexOfSplitter = sanitizedCommand?.indexOf(validSetters[0]) const [pathRaw, valRaw] = [ sanitizedCommand?.slice(0, indexOfSplitter), sanitizedCommand?.slice(indexOfSplitter + validSetters[0]?.length), ] return [pathRaw?.trim(), valRaw?.trim()] } else if (sanitizedCommand?.includes(validSetters[1])) { const indexOfSplitter = sanitizedCommand?.indexOf(validSetters[1]) const [pathRaw, valRaw] = [ sanitizedCommand?.slice(0, indexOfSplitter), sanitizedCommand?.slice(indexOfSplitter + validSetters[1]?.length), ] return [pathRaw?.trim(), valRaw?.trim()] } console.log("nik sanitizedCommand", sanitizedCommand) return [sanitizedCommand] }, [ // inputValue, virtualValue, validSetters, ]) const commandPath = React.useMemo(() => { return command?.[0] // return sanitise(command?.[0]) }, [command]) const commandValue = React.useMemo(() => { return command?.[1] }, [command]) const validQuotations = React.useMemo(() => { return ['"', "'"] }, []) const escapedCommandValue = React.useMemo(() => { // replace quotations with escaped quoations except for first and last quotation const startingQuotation = commandValue?.[0] const endingQuotation = commandValue?.[commandValue?.length - 1] const isQuoted = validQuotations?.includes(startingQuotation) && validQuotations?.includes(endingQuotation) const restOfCommandValue = isQuoted ? commandValue?.slice(1, commandValue?.length - 1) : commandValue const escaped = restOfCommandValue ?.replace(/"/g, '\\"') ?.replace(/'/g, "\\'") const ret = `"${escaped}"` return ret }, [commandValue, validQuotations]) const commandIsAction = React.useMemo(() => { return commandPath && commandValue }, [commandPath, commandValue]) const showSuggestions = React.useMemo(() => { return inputValue?.length }, [inputValue]) const suggestions = React.useMemo(() => { const fuse = new Fuse(paths) const results = fuse.search(inputValue) const mappedResults = results?.map((result) => { return result?.item }) return mappedResults }, [inputValue, paths]) const selectSuggestion = React.useCallback( (suggestionIdx) => { const suggestion = suggestions?.[suggestionIdx] setInputValue(suggestion) setHoveredSuggestion(null) setContextPath(suggestion) setShowContext(true, "Select suggestion") }, [setInputValue, setContextPath, setShowContext, suggestions] ) const commandContainsPath = React.useMemo(() => { console.log("nik command", commandPath) console.log("nik suggestions", suggestions) const commandIncludesSuggestion = suggestions?.find((suggestion) => { return commandPath?.includes(suggestion) }) console.log("nik commandIncludesSuggestion", commandIncludesSuggestion) // return false return commandIncludesSuggestion }, [commandPath, suggestions]) const openCommander = React.useCallback(() => { console.log("nik commander opening commander") setThingtime("settings.commanderActive", true) }, [setThingtime]) const closeCommander = React.useCallback( (e?: any) => { console.log("nik e", e) if (thingtime?.settings?.commanderActive) { console.log("nik commander closing commander") console.log("nik setting commanderActive to false") setThingtime("settings.commanderActive", false) } }, [setThingtime, thingtime?.settings?.commanderActive] ) const toggleCommander = React.useCallback(() => { if (thingtime?.settings?.commanderActive) { closeCommander() } else { openCommander() } }, [thingtime?.settings?.commanderActive, closeCommander, openCommander]) const allCommanderKeyListener = React.useCallback( (e: any) => { console.log("commander key listener e?.code", e?.code) if (e?.metaKey && e?.code === "KeyP") { e.preventDefault() e.stopPropagation() toggleCommander() } // if key escape close all modals else if (e?.code === "Escape") { closeCommander() } // if arrow keys then move selection else if (e?.code === "ArrowUp") { // move selection up const curSuggestionIdx = typeof hoveredSuggestion === "number" ? hoveredSuggestion : suggestions?.length const newSuggestionIdx = curSuggestionIdx - 1 if (newSuggestionIdx >= 0) { setHoveredSuggestion(newSuggestionIdx) } else { setHoveredSuggestion(suggestions?.length - 1) } } else if (e?.code === "ArrowDown") { // move selection down const curSuggestionIdx = typeof hoveredSuggestion === "number" ? hoveredSuggestion : -1 const newSuggestionIdx = curSuggestionIdx + 1 if (newSuggestionIdx < suggestions?.length) { setHoveredSuggestion(newSuggestionIdx) } else { setHoveredSuggestion(0) } } else if (e?.code === "Enter") { // if selection is active then select it const curSuggestionIdx = hoveredSuggestion if (curSuggestionIdx !== null) { selectSuggestion(curSuggestionIdx) } if (commanderActive) { try { console.log("nik Commander onEnter") console.log("nik commandIsAction", commandIsAction) console.log("nik commandContainsPath", commandContainsPath) console.log("nik onEnter commandPath", commandPath) if (commandIsAction) { // nothing try { const fn = `() => { return ${escapedCommandValue} }` const evalFn = eval(fn) const realVal = evalFn() const prevVal = getThingtime(commandPath) const parentPath = getParentPath(commandPath) setThingtime(commandPath, realVal) if (!prevVal) { setContextPath(parentPath) setShowContext(true, "commandIsAction check") } } catch (err) { console.log("setThingtime errored in Commander", err) } } else if (commandContainsPath) { // const prevValue = getThingtime(commandPath) // const newValue = setThingtime(commandPath, prevValue) console.log("Setting context path", commandPath) setContextPath(commandPath) setShowContext(true, "commandContainsPath check") } } catch (err) { console.error("Caught error on commander onEnter", err) } } } }, [ closeCommander, toggleCommander, hoveredSuggestion, selectSuggestion, suggestions, commanderActive, commandIsAction, commandContainsPath, commandPath, escapedCommandValue, getThingtime, setThingtime, setShowContext, ] ) React.useEffect(() => { window.addEventListener("keydown", allCommanderKeyListener) return () => { window.removeEventListener("keydown", allCommanderKeyListener) } }, [allCommanderKeyListener]) React.useEffect(() => { if (typeof hoveredSuggestion === "number") { setVirtualValue(suggestions?.[hoveredSuggestion]) } else { setVirtualValue(inputValue) } }, [hoveredSuggestion, inputValue, suggestions]) React.useEffect(() => { setVirtualValue(inputValue) }, [inputValue]) return ( setHoveredSuggestion(null)} paddingY={3} > {suggestions?.map((suggestion, i) => { return ( selectSuggestion(i)} onMouseEnter={() => setHoveredSuggestion(i)} paddingX={4} > {suggestion} ) })}
) }