2023-07-04 08:05:08 +00:00
|
|
|
import React from "react"
|
|
|
|
import ClickAwayListener from "react-click-away-listener"
|
|
|
|
import { Center, Flex, Input } from "@chakra-ui/react"
|
|
|
|
|
|
|
|
import { Thingtime } from "../Thingtime/Thingtime"
|
|
|
|
import { useThingtime } from "../Thingtime/useThingtime"
|
|
|
|
|
|
|
|
import { sanitise } from "~/functions/sanitise"
|
|
|
|
import { getParentPath } from "~/smarts"
|
|
|
|
|
|
|
|
export const Commander = (props) => {
|
2023-07-01 14:13:27 +00:00
|
|
|
const { thingtime, setThingtime, getThingtime, thingtimeRef } = useThingtime()
|
|
|
|
|
|
|
|
const inputRef = React.useRef()
|
|
|
|
|
2023-07-04 08:05:08 +00:00
|
|
|
const [value, setValue] = React.useState("")
|
2023-07-01 09:46:36 +00:00
|
|
|
|
|
|
|
const [contextPath, setContextPath] = React.useState()
|
|
|
|
|
2023-07-01 14:13:27 +00:00
|
|
|
const [showContext, setShowContextState] = React.useState(false)
|
|
|
|
|
|
|
|
const setShowContext = React.useCallback(
|
2023-07-04 08:05:08 +00:00
|
|
|
(value, from?: string) => {
|
2023-07-01 14:13:27 +00:00
|
|
|
setShowContextState(value)
|
|
|
|
},
|
|
|
|
[setShowContextState]
|
|
|
|
)
|
2023-07-04 08:05:08 +00:00
|
|
|
// const [suggestions, setSuggestions] = React.useState([])
|
2023-07-01 09:46:36 +00:00
|
|
|
|
|
|
|
const contextValue = React.useMemo(() => {
|
2023-07-04 08:05:08 +00:00
|
|
|
// TODO: Figure out why this is running on every click
|
2023-07-01 09:46:36 +00:00
|
|
|
const ret = getThingtime(contextPath)
|
|
|
|
return ret
|
|
|
|
}, [contextPath, getThingtime])
|
|
|
|
|
|
|
|
const showCommander = React.useMemo(() => {
|
|
|
|
return thingtime?.settings?.showCommander
|
|
|
|
}, [thingtime?.settings?.showCommander])
|
|
|
|
|
2023-07-01 14:13:27 +00:00
|
|
|
// showCommander useEffect
|
|
|
|
React.useEffect(() => {
|
2023-07-01 09:46:36 +00:00
|
|
|
if (showCommander) {
|
|
|
|
inputRef?.current?.focus?.()
|
2023-07-01 14:13:27 +00:00
|
|
|
} else {
|
|
|
|
if (thingtimeRef?.current?.settings?.clearCommanderOnToggle) {
|
2023-07-04 08:05:08 +00:00
|
|
|
setValue("")
|
2023-07-01 14:13:27 +00:00
|
|
|
}
|
|
|
|
if (thingtimeRef?.current?.settings?.clearCommanderContextOnToggle) {
|
2023-07-04 08:05:08 +00:00
|
|
|
setShowContext(false, "showCommander useEffect")
|
2023-07-01 14:13:27 +00:00
|
|
|
}
|
2023-07-01 09:46:36 +00:00
|
|
|
}
|
2023-07-01 14:13:27 +00:00
|
|
|
}, [showCommander, thingtimeRef, setShowContext])
|
2023-07-01 09:46:36 +00:00
|
|
|
|
2023-07-04 08:05:08 +00:00
|
|
|
const onChange = React.useCallback((e) => {
|
2023-07-01 09:46:36 +00:00
|
|
|
setValue(e.target.value)
|
|
|
|
}, [])
|
|
|
|
|
2023-07-01 14:13:27 +00:00
|
|
|
const validSetters = React.useMemo(() => {
|
2023-07-04 08:05:08 +00:00
|
|
|
return ["=", " is "]
|
2023-07-01 14:13:27 +00:00
|
|
|
}, [])
|
|
|
|
|
|
|
|
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),
|
2023-07-04 08:05:08 +00:00
|
|
|
sanitizedCommand?.slice(indexOfSplitter + validSetters[0]?.length),
|
2023-07-01 14:13:27 +00:00
|
|
|
]
|
|
|
|
return [pathRaw?.trim(), valRaw?.trim()]
|
|
|
|
} else if (sanitizedCommand?.includes(validSetters[1])) {
|
|
|
|
const indexOfSplitter = sanitizedCommand?.indexOf(validSetters[1])
|
|
|
|
const [pathRaw, valRaw] = [
|
|
|
|
sanitizedCommand?.slice(0, indexOfSplitter),
|
2023-07-04 08:05:08 +00:00
|
|
|
sanitizedCommand?.slice(indexOfSplitter + validSetters[1]?.length),
|
2023-07-01 14:13:27 +00:00
|
|
|
]
|
|
|
|
return [pathRaw?.trim(), valRaw?.trim()]
|
|
|
|
}
|
2023-07-04 08:05:08 +00:00
|
|
|
console.log("nik sanitizedCommand", sanitizedCommand)
|
2023-07-01 14:13:27 +00:00
|
|
|
return [sanitizedCommand]
|
|
|
|
}, [value, validSetters])
|
|
|
|
|
|
|
|
const commandPath = React.useMemo(() => {
|
2023-07-04 08:05:08 +00:00
|
|
|
return sanitise(command?.[0])
|
2023-07-01 14:13:27 +00:00
|
|
|
}, [command])
|
|
|
|
|
|
|
|
const commandValue = React.useMemo(() => {
|
|
|
|
return command?.[1]
|
|
|
|
}, [command])
|
|
|
|
|
2023-07-04 08:05:08 +00:00
|
|
|
const commandContainsPath = React.useMemo(() => {
|
|
|
|
console.log("nik command", commandPath)
|
|
|
|
// const commandIncludesSuggestion = suggestions?.find(suggestion => {
|
|
|
|
// return commandPath?.includes(suggestion)
|
|
|
|
// })
|
|
|
|
// console.log('nik commandIncludesSuggestion', commandIncludesSuggestion)
|
|
|
|
return false
|
|
|
|
// return commandIncludesSuggestion
|
|
|
|
}, [commandPath])
|
|
|
|
|
2023-07-01 14:13:27 +00:00
|
|
|
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])
|
|
|
|
|
2023-07-04 08:05:08 +00:00
|
|
|
const suggestions = React.useMemo(() => {
|
2023-07-01 14:13:27 +00:00
|
|
|
if (value?.length) {
|
2023-07-04 08:05:08 +00:00
|
|
|
const suggestions = ["tt", "thingtime", "."]
|
|
|
|
const populateSuggestions = (obj, path) => {
|
|
|
|
Object.keys(obj).forEach((key) => {
|
2023-07-01 14:13:27 +00:00
|
|
|
const val = obj[key]
|
2023-07-04 08:05:08 +00:00
|
|
|
const newPath = path ? `${path}${path ? "." : ""}${key}` : key
|
|
|
|
if (typeof val === "object") {
|
2023-07-01 14:29:07 +00:00
|
|
|
suggestions.push(newPath)
|
2023-07-04 08:05:08 +00:00
|
|
|
populateSuggestions(val, newPath)
|
2023-07-01 14:13:27 +00:00
|
|
|
} else {
|
|
|
|
suggestions.push(newPath)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2023-07-04 08:05:08 +00:00
|
|
|
|
|
|
|
// populateSuggestions(thingtime, commandPath)
|
|
|
|
populateSuggestions(thingtime, "")
|
|
|
|
|
|
|
|
// console.log('nik suggestions', suggestions)
|
|
|
|
|
|
|
|
console.log("nik useEffect suggestions commandPath", commandPath)
|
2023-07-01 14:13:27 +00:00
|
|
|
|
|
|
|
if (commandPath) {
|
|
|
|
const filteredSuggestions = suggestions.filter((suggestion, i) => {
|
|
|
|
return suggestion?.toLowerCase()?.includes(commandPath?.toLowerCase())
|
|
|
|
})
|
|
|
|
if (!filteredSuggestions?.includes(commandPath)) {
|
|
|
|
const adjustedSuggestions = [commandPath, ...filteredSuggestions]
|
2023-07-04 08:05:08 +00:00
|
|
|
return adjustedSuggestions
|
2023-07-01 14:13:27 +00:00
|
|
|
} else {
|
2023-07-04 08:05:08 +00:00
|
|
|
return filteredSuggestions
|
2023-07-01 14:13:27 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-07-04 08:05:08 +00:00
|
|
|
return suggestions
|
2023-07-01 14:13:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// if (value) {
|
|
|
|
// setShowContext(true, 'Thingtime changes update suggestions')
|
|
|
|
// }
|
|
|
|
}
|
2023-07-04 08:05:08 +00:00
|
|
|
}, [value, thingtime, commandPath, setShowContext])
|
2023-07-01 14:13:27 +00:00
|
|
|
|
2023-07-01 09:46:36 +00:00
|
|
|
const onEnter = React.useCallback(
|
2023-07-04 08:05:08 +00:00
|
|
|
(props) => {
|
2023-07-01 09:46:36 +00:00
|
|
|
// if first characters of value equal tt. then run command
|
|
|
|
// or if first character is a dot then run command
|
|
|
|
try {
|
2023-07-04 08:05:08 +00:00
|
|
|
console.log("Commander onEnter")
|
2023-07-01 14:13:27 +00:00
|
|
|
if (commandIsAction) {
|
|
|
|
// nothing
|
|
|
|
try {
|
|
|
|
const fn = `() => { return ${escapedCommandValue} }`
|
|
|
|
const evalFn = eval(fn)
|
|
|
|
const realVal = evalFn()
|
2023-07-01 14:49:01 +00:00
|
|
|
const prevVal = getThingtime(commandPath)
|
2023-07-04 08:05:08 +00:00
|
|
|
const parentPath = getParentPath(commandPath)
|
2023-07-01 14:13:27 +00:00
|
|
|
setThingtime(commandPath, realVal)
|
2023-07-01 14:49:01 +00:00
|
|
|
if (!prevVal) {
|
2023-07-04 08:05:08 +00:00
|
|
|
setContextPath(parentPath)
|
|
|
|
setShowContext(true, "commandIsAction check")
|
2023-07-01 14:49:01 +00:00
|
|
|
}
|
2023-07-01 14:13:27 +00:00
|
|
|
} catch (err) {
|
2023-07-04 08:05:08 +00:00
|
|
|
console.log("setThingtime errored in Commander", err)
|
2023-07-01 09:46:36 +00:00
|
|
|
}
|
2023-07-01 14:13:27 +00:00
|
|
|
} else if (commandContainsPath) {
|
2023-07-04 08:05:08 +00:00
|
|
|
console.log("Setting context path", commandPath)
|
2023-07-01 14:13:27 +00:00
|
|
|
setContextPath(commandPath)
|
2023-07-04 08:05:08 +00:00
|
|
|
setShowContext(true, "commandContainsPath check")
|
2023-07-01 09:46:36 +00:00
|
|
|
}
|
|
|
|
} catch (err) {
|
2023-07-04 08:05:08 +00:00
|
|
|
console.error("Caught error on commander onEnter", err)
|
2023-07-01 09:46:36 +00:00
|
|
|
}
|
|
|
|
},
|
2023-07-01 14:13:27 +00:00
|
|
|
[
|
|
|
|
setShowContext,
|
|
|
|
escapedCommandValue,
|
|
|
|
setThingtime,
|
|
|
|
commandIsAction,
|
|
|
|
commandPath,
|
2023-07-04 08:05:08 +00:00
|
|
|
commandContainsPath,
|
2023-07-01 14:13:27 +00:00
|
|
|
]
|
2023-07-01 09:46:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// trigger on enter
|
|
|
|
const onKeyDown = React.useCallback(
|
2023-07-04 08:05:08 +00:00
|
|
|
(e) => {
|
|
|
|
if (e.key === "Enter") {
|
2023-07-01 09:46:36 +00:00
|
|
|
e.preventDefault()
|
|
|
|
e.stopPropagation()
|
|
|
|
onEnter({ e })
|
|
|
|
// setThingtime(
|
|
|
|
// 'settings.showCommander',
|
|
|
|
// !thingtime?.settings?.showCommander
|
|
|
|
// )
|
|
|
|
}
|
|
|
|
},
|
2023-07-01 14:13:27 +00:00
|
|
|
[onEnter]
|
2023-07-01 09:46:36 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const openCommander = React.useCallback(() => {
|
2023-07-04 08:05:08 +00:00
|
|
|
setThingtime("settings.showCommander", true)
|
2023-07-01 09:46:36 +00:00
|
|
|
}, [setThingtime])
|
|
|
|
|
|
|
|
const closeCommander = React.useCallback(() => {
|
2023-07-04 08:05:08 +00:00
|
|
|
if (thingtime?.settings?.showCommander) {
|
|
|
|
setThingtime("settings.showCommander", false)
|
|
|
|
}
|
|
|
|
if (value !== "") {
|
|
|
|
setValue("")
|
|
|
|
}
|
|
|
|
if (contextPath !== undefined) {
|
|
|
|
setContextPath(undefined)
|
|
|
|
}
|
|
|
|
if (showContext !== false) {
|
|
|
|
setShowContext(false)
|
|
|
|
}
|
|
|
|
}, [setThingtime, setShowContext, value, contextPath, showContext])
|
2023-07-01 09:46:36 +00:00
|
|
|
|
|
|
|
const toggleCommander = React.useCallback(() => {
|
|
|
|
if (thingtime?.settings?.showCommander) {
|
|
|
|
closeCommander()
|
|
|
|
} else {
|
|
|
|
openCommander()
|
|
|
|
}
|
|
|
|
}, [thingtime?.settings?.showCommander, closeCommander, openCommander])
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
const keyListener = (e: any) => {
|
2023-07-04 08:05:08 +00:00
|
|
|
if (e?.metaKey && e?.code === "KeyP") {
|
2023-07-01 09:46:36 +00:00
|
|
|
e.preventDefault()
|
|
|
|
e.stopPropagation()
|
|
|
|
toggleCommander()
|
|
|
|
}
|
|
|
|
// if key escape close all modals
|
2023-07-04 08:05:08 +00:00
|
|
|
console.log("commander key listener e?.code", e?.code)
|
|
|
|
if (e?.code === "Escape") {
|
2023-07-01 09:46:36 +00:00
|
|
|
closeCommander()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-04 08:05:08 +00:00
|
|
|
window.addEventListener("keydown", keyListener)
|
2023-07-01 09:46:36 +00:00
|
|
|
|
|
|
|
return () => {
|
2023-07-04 08:05:08 +00:00
|
|
|
window.removeEventListener("keydown", keyListener)
|
2023-07-01 09:46:36 +00:00
|
|
|
}
|
2023-07-01 14:13:27 +00:00
|
|
|
}, [setThingtime, thingtime, toggleCommander, closeCommander])
|
|
|
|
|
|
|
|
const selectSuggestion = React.useCallback(
|
2023-07-04 08:05:08 +00:00
|
|
|
(suggestion) => {
|
2023-07-01 14:13:27 +00:00
|
|
|
setValue(suggestion)
|
|
|
|
setContextPath(suggestion)
|
2023-07-04 08:05:08 +00:00
|
|
|
setShowContext(true, "Select suggestion")
|
2023-07-01 14:13:27 +00:00
|
|
|
},
|
|
|
|
[setValue, setContextPath, setShowContext]
|
|
|
|
)
|
|
|
|
|
|
|
|
const excludedSuggestions = React.useMemo(() => {
|
2023-07-04 08:05:08 +00:00
|
|
|
return ["."]
|
2023-07-01 14:13:27 +00:00
|
|
|
}, [])
|
|
|
|
|
|
|
|
const renderedSuggestions = React.useMemo(() => {
|
2023-07-04 08:05:08 +00:00
|
|
|
return suggestions?.filter((suggestion) => {
|
2023-07-01 14:13:27 +00:00
|
|
|
return !excludedSuggestions?.includes(suggestion)
|
|
|
|
})
|
|
|
|
}, [suggestions, excludedSuggestions])
|
|
|
|
|
|
|
|
const mobileVW = React.useMemo(() => {
|
2023-07-04 08:05:08 +00:00
|
|
|
return "calc(100vw - 45px)"
|
2023-07-01 14:13:27 +00:00
|
|
|
}, [])
|
2023-07-01 09:46:36 +00:00
|
|
|
|
|
|
|
return (
|
2023-07-01 14:49:01 +00:00
|
|
|
<ClickAwayListener onClickAway={closeCommander}>
|
2023-07-01 14:13:27 +00:00
|
|
|
<Flex
|
2023-07-04 08:05:08 +00:00
|
|
|
position="absolute"
|
2023-07-01 14:49:01 +00:00
|
|
|
// display={['flex', showCommander ? 'flex' : 'none']}
|
2023-07-04 08:05:08 +00:00
|
|
|
top={0}
|
2023-07-01 14:49:01 +00:00
|
|
|
// zIndex={99999}
|
|
|
|
// position='fixed'
|
|
|
|
// top='100px'
|
2023-07-01 09:46:36 +00:00
|
|
|
right={0}
|
2023-07-04 08:05:08 +00:00
|
|
|
left={0}
|
|
|
|
justifyContent={["flex-start", "center"]}
|
|
|
|
maxWidth="100%"
|
|
|
|
height="100%"
|
|
|
|
pointerEvents="none"
|
|
|
|
id="commander"
|
|
|
|
paddingX={1}
|
|
|
|
paddingY={1}
|
2023-07-01 09:46:36 +00:00
|
|
|
>
|
2023-07-01 14:13:27 +00:00
|
|
|
<Flex
|
2023-07-04 08:05:08 +00:00
|
|
|
position="absolute"
|
|
|
|
top="100%"
|
2023-07-01 14:49:01 +00:00
|
|
|
right={0}
|
2023-07-04 08:05:08 +00:00
|
|
|
left={0}
|
|
|
|
alignItems={["flex-start", "center"]}
|
|
|
|
flexDirection="column"
|
|
|
|
overflowY="scroll"
|
|
|
|
maxWidth="100%"
|
|
|
|
height="auto"
|
|
|
|
maxHeight="90vh"
|
|
|
|
marginTop={2}
|
|
|
|
borderRadius="12px"
|
|
|
|
marginX={1}
|
2023-07-01 14:13:27 +00:00
|
|
|
>
|
2023-07-01 14:49:01 +00:00
|
|
|
<Flex
|
2023-07-04 08:05:08 +00:00
|
|
|
flexDirection="column"
|
|
|
|
display={renderedSuggestions?.length ? "flex" : "none"}
|
|
|
|
width={["100%", "400px"]}
|
|
|
|
maxWidth="100%"
|
|
|
|
marginBottom={3}
|
|
|
|
background="grey"
|
|
|
|
borderRadius="12px"
|
|
|
|
pointerEvents="all"
|
|
|
|
id="commander-suggestions"
|
|
|
|
paddingY={3}
|
2023-07-01 14:49:01 +00:00
|
|
|
>
|
2023-07-04 08:05:08 +00:00
|
|
|
{renderedSuggestions?.map((suggestion, i) => {
|
2023-07-01 14:49:01 +00:00
|
|
|
return (
|
|
|
|
<Flex
|
2023-07-04 08:05:08 +00:00
|
|
|
key={i}
|
2023-07-01 14:49:01 +00:00
|
|
|
_hover={{
|
2023-07-04 08:05:08 +00:00
|
|
|
bg: "greys.medium",
|
2023-07-01 14:49:01 +00:00
|
|
|
}}
|
2023-07-04 08:05:08 +00:00
|
|
|
cursor="pointer"
|
2023-07-01 14:49:01 +00:00
|
|
|
onClick={() => selectSuggestion(suggestion)}
|
2023-07-04 08:05:08 +00:00
|
|
|
paddingX={4}
|
2023-07-01 14:49:01 +00:00
|
|
|
>
|
|
|
|
{suggestion}
|
|
|
|
</Flex>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</Flex>
|
|
|
|
<Flex
|
2023-07-04 08:05:08 +00:00
|
|
|
display={showContext ? "flex" : "none"}
|
|
|
|
maxWidth="100%"
|
|
|
|
background="grey"
|
|
|
|
borderRadius="12px"
|
|
|
|
pointerEvents="all"
|
|
|
|
paddingY={3}
|
2023-07-01 14:49:01 +00:00
|
|
|
>
|
|
|
|
<Thingtime thing={contextValue}></Thingtime>
|
|
|
|
</Flex>
|
2023-07-01 14:13:27 +00:00
|
|
|
</Flex>
|
2023-07-01 14:49:01 +00:00
|
|
|
<Center
|
2023-07-04 08:05:08 +00:00
|
|
|
position="relative"
|
|
|
|
overflow="hidden"
|
|
|
|
width={["100%", "400px"]}
|
|
|
|
maxWidth={[mobileVW, "100%"]}
|
|
|
|
height="100%"
|
|
|
|
padding="1px"
|
|
|
|
background="grey"
|
|
|
|
borderRadius="6px"
|
|
|
|
pointerEvents="all"
|
|
|
|
outline="none"
|
2023-07-01 14:49:01 +00:00
|
|
|
>
|
2023-07-04 08:05:08 +00:00
|
|
|
<Input
|
|
|
|
// display='none'
|
|
|
|
// opacity={0}
|
|
|
|
ref={inputRef}
|
2023-07-01 14:49:01 +00:00
|
|
|
sx={{
|
2023-07-04 08:05:08 +00:00
|
|
|
"&::placeholder": {
|
|
|
|
color: "greys.dark",
|
2023-07-01 14:49:01 +00:00
|
|
|
},
|
|
|
|
}}
|
2023-07-04 08:05:08 +00:00
|
|
|
width="100%"
|
|
|
|
height="100%"
|
|
|
|
border="none"
|
|
|
|
borderRadius="5px"
|
|
|
|
outline="none"
|
2023-07-01 14:49:01 +00:00
|
|
|
onChange={onChange}
|
|
|
|
onKeyDown={onKeyDown}
|
|
|
|
placeholder={"What's on your mind?"}
|
2023-07-04 08:05:08 +00:00
|
|
|
value={value}
|
2023-07-01 14:49:01 +00:00
|
|
|
></Input>
|
|
|
|
</Center>
|
|
|
|
</Flex>
|
|
|
|
</ClickAwayListener>
|
2023-07-01 09:46:36 +00:00
|
|
|
)
|
|
|
|
}
|