import React from 'react' import { Center, Box, Flex, Input } from '@chakra-ui/react' import { useThingtime } from '../Thingtime/useThingtime' import { Thingtime } from '../Thingtime/Thingtime' import { sanitise } from '~/functions/path' export const Commander = props => { const { thingtime, setThingtime, getThingtime, thingtimeRef } = useThingtime() const inputRef = React.useRef() const [value, setValue] = React.useState('') const [contextPath, setContextPath] = React.useState() const [showContext, setShowContextState] = React.useState(false) const setShowContext = React.useCallback( (value, from) => { setShowContextState(value) }, [setShowContextState] ) const [suggestions, setSuggestions] = React.useState([]) const contextValue = React.useMemo(() => { console.log('thingtime updated!') const ret = getThingtime(contextPath) return ret }, [contextPath, getThingtime]) const showCommander = React.useMemo(() => { return thingtime?.settings?.showCommander }, [thingtime?.settings?.showCommander]) // watch value React.useEffect(() => { if (!value?.length) { setSuggestions([]) } }, [value]) // showCommander useEffect React.useEffect(() => { if (showCommander) { inputRef?.current?.focus?.() } else { if (thingtimeRef?.current?.settings?.clearCommanderOnToggle) { setValue('') } if (thingtimeRef?.current?.settings?.clearCommanderContextOnToggle) { setShowContext(false, 'showCommander useEffect') } } }, [showCommander, thingtimeRef, setShowContext]) const onChange = React.useCallback(e => { setValue(e.target.value) }, []) const validSetters = React.useMemo(() => { return ['=', ' is '] }, []) const command = React.useMemo(() => { const sanitizedCommand = sanitise(value) 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()] } return [sanitizedCommand] }, [value, validSetters]) const commandContainsPath = React.useMemo(() => { const suggestionsIncludesSubstring = suggestions?.find(suggestion => { return command?.includes(suggestion) }) return suggestionsIncludesSubstring }, [suggestions, command]) const commandPath = React.useMemo(() => { return 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]) // thingtime changes update suggestions React.useEffect(() => { // when thingtime changes, update suggestions // with all flattened key path values recursively if (value?.length) { const suggestions = ['tt', 'thingtime', '.'] const recurse = (obj, path) => { Object.keys(obj).forEach(key => { const val = obj[key] const newPath = path ? `${path}.${key}` : key if (typeof val === 'object') { suggestions.push(newPath) recurse(val, newPath) } else { suggestions.push(newPath) } }) } recurse(thingtime, '') if (commandPath) { const filteredSuggestions = suggestions.filter((suggestion, i) => { return suggestion?.toLowerCase()?.includes(commandPath?.toLowerCase()) }) if (!filteredSuggestions?.includes(commandPath)) { const adjustedSuggestions = [commandPath, ...filteredSuggestions] setSuggestions(adjustedSuggestions) } else { setSuggestions(filteredSuggestions) } } else { setSuggestions(suggestions) } // if (value) { // setShowContext(true, 'Thingtime changes update suggestions') // } } }, [thingtime, value, commandPath, setShowContext]) const onEnter = React.useCallback( props => { // if first characters of value equal tt. then run command // or if first character is a dot then run command try { if (commandIsAction) { // nothing try { const fn = `() => { return ${escapedCommandValue} }` const evalFn = eval(fn) const realVal = evalFn() setThingtime(commandPath, realVal) } catch (err) { console.log('setThingtime errored in Commander', err) } } else if (commandContainsPath) { setContextPath(commandPath) setShowContext(true, 'commandContainsPath check') } } catch (err) { console.error('Caught error on commander onEnter', err) } }, [ setShowContext, escapedCommandValue, setThingtime, commandIsAction, commandPath, commandContainsPath ] ) // trigger on enter const onKeyDown = React.useCallback( e => { if (e.key === 'Enter') { e.preventDefault() e.stopPropagation() onEnter({ e }) // setThingtime( // 'settings.showCommander', // !thingtime?.settings?.showCommander // ) } }, [onEnter] ) const openCommander = React.useCallback(() => { setThingtime('settings.showCommander', true) }, [setThingtime]) const closeCommander = React.useCallback(() => { setThingtime('settings.showCommander', false) setValue('') setShowContext(false) setContextPath(undefined) }, [setThingtime, setShowContext]) const toggleCommander = React.useCallback(() => { if (thingtime?.settings?.showCommander) { closeCommander() } else { openCommander() } }, [thingtime?.settings?.showCommander, closeCommander, openCommander]) React.useEffect(() => { const keyListener = (e: any) => { if (e?.metaKey && e?.code === 'KeyP') { e.preventDefault() e.stopPropagation() toggleCommander() } // if key escape close all modals console.log('commander key listener e?.code', e?.code) if (e?.code === 'Escape') { closeCommander() } } window.addEventListener('keydown', keyListener) return () => { window.removeEventListener('keydown', keyListener) } }, [setThingtime, thingtime, toggleCommander, closeCommander]) const selectSuggestion = React.useCallback( suggestion => { setValue(suggestion) setContextPath(suggestion) setShowContext(true, 'Select suggestion') }, [setValue, setContextPath, setShowContext] ) const excludedSuggestions = React.useMemo(() => { return ['.'] }, []) const renderedSuggestions = React.useMemo(() => { return suggestions?.filter(suggestion => { return !excludedSuggestions?.includes(suggestion) }) }, [suggestions, excludedSuggestions]) const mobileVW = React.useMemo(() => { return 'calc(100vw - 45px)' }, []) return ( {renderedSuggestions.map((suggestion, i) => { return ( selectSuggestion(suggestion)} > {suggestion} ) })}
) }