feat: feature/mvp-sprint-1 Made commander re-usable for undefined values

This commit is contained in:
Nikolaj Frey 2023-08-13 15:53:47 +10:00
parent 9223138d12
commit 6663e12ebb
8 changed files with 1073 additions and 202 deletions

View File

@ -9,11 +9,12 @@ export interface ThingtimeContextInterface {
setThingtime: any
getThingtime: any
thingtimeRef: any
loading: boolean
}
export const ThingtimeContext = createContext<
ThingtimeContextInterface[] | null
>(null)
export const ThingtimeContext = createContext<ThingtimeContextInterface | null>(
null
)
try {
window.smarts = smarts

View File

@ -1,8 +1,9 @@
import React from "react"
import ClickAwayListener from "react-click-away-listener"
import { Center, Flex, Input } from "@chakra-ui/react"
import { Box, Center, Flex, Input } from "@chakra-ui/react"
import Fuse from "fuse.js"
import { MagicInput } from "../MagicInput/MagicInput"
import { Rainbow } from "../Rainbow/Rainbow"
import { Thingtime } from "../Thingtime/Thingtime"
import { useThingtime } from "../Thingtime/useThingtime"
@ -69,9 +70,13 @@ export const Commander = (props) => {
// commanderActive useEffect
React.useEffect(() => {
if (commanderActive) {
inputRef?.current?.focus?.()
if (props?.global) {
inputRef?.current?.focus?.()
}
} else {
document.activeElement.blur()
if (props?.global) {
document.activeElement.blur()
}
if (
thingtimeRef?.current?.settings?.commander?.[commanderId]
@ -98,6 +103,7 @@ export const Commander = (props) => {
commanderActive,
thingtimeRef,
setShowContext,
props?.global,
commanderId,
inputValue,
contextPath,
@ -116,27 +122,43 @@ export const Commander = (props) => {
const command = React.useMemo(() => {
// const sanitizedCommand = sanitise(value)
// const sanitizedCommand = inputValue
const sanitizedCommand = virtualValue
const sanitizedInput = virtualValue
const validSetter = validSetters?.find((setter) => {
if (sanitizedCommand?.includes(setter)) {
if (sanitizedInput?.includes(setter)) {
return setter
}
return false
})
if (typeof validSetter === "string") {
const indexOfSplitter = sanitizedCommand?.indexOf(validSetter)
const indexOfSplitter = sanitizedInput?.indexOf(validSetter)
const [pathRaw, valRaw] = [
sanitizedCommand?.slice(0, indexOfSplitter),
sanitizedCommand?.slice(indexOfSplitter + validSetter?.length),
sanitizedInput?.slice(0, indexOfSplitter),
sanitizedInput?.slice(indexOfSplitter + validSetter?.length),
]
return [pathRaw?.trim(), valRaw?.trim()]
const pathTrimmed = pathRaw?.trim()
let path = pathTrimmed
if (pathTrimmed && props?.pathPrefix) {
path = props?.pathPrefix + "." + pathTrimmed
} else if (props?.pathPrefix) {
path = props?.pathPrefix
}
return [path, valRaw?.trim()]
}
return [sanitizedCommand]
if (props?.pathPrefix) {
return [props?.pathPrefix, sanitizedInput]
}
return [sanitizedInput]
}, [
// inputValue,
props?.pathPrefix,
virtualValue,
validSetters,
])
@ -167,7 +189,9 @@ export const Commander = (props) => {
const escaped = restOfCommandValue
?.replace(/"/g, '\\"')
?.replace(/'/g, "\\'")
const ret = `"${escaped}"`
?.replace(/`/g, "\\`")
const ret = `\`${escaped}\``
return ret
}, [commandValue, validQuotations])
@ -270,6 +294,8 @@ export const Commander = (props) => {
if (curSuggestionIdx !== null) {
selectSuggestion(curSuggestionIdx)
}
console.log("nik commanderActive", commanderActive)
console.log("nik commandIsAction", commandIsAction)
if (commanderActive) {
try {
if (commandIsAction) {
@ -378,7 +404,10 @@ export const Commander = (props) => {
setHoveredSuggestion(0)
}
} else if (e?.code === "Enter") {
executeCommand()
// if not shift enter then execute command
if (!e?.shiftKey) {
executeCommand()
}
}
}
},
@ -414,89 +443,140 @@ export const Commander = (props) => {
setVirtualValue(inputValue)
}, [inputValue])
const InputJSX = React.useMemo(() => {
return (
<Input
// display='none'
// opacity={0}
ref={inputRef}
sx={{
"&::placeholder": {
color: "greys.dark",
},
}}
width="100%"
height="100%"
background="grey"
border="none"
borderRadius="5px"
outline="none"
onChange={onInputChange}
onFocus={openCommander}
placeholder="Imagine.."
value={inputValue}
></Input>
)
}, [inputRef, inputValue, onInputChange, openCommander])
const onMagicInput = React.useCallback((args) => {
// props?.onValueChange?.(args)
setInputValue(args?.value)
setHoveredSuggestion(null)
}, [])
const InputPartWrapper = React.useCallback(
(props) => {
return <Box paddingX={commanderActive ? 1 : 0}>{props?.children}</Box>
},
[commanderActive]
)
const InputPart = React.useMemo(() => {
if (props?.simple) {
return (
<Input
// display='none'
// opacity={0}
ref={inputRef}
sx={{
"&::placeholder": {
color: "greys.dark",
},
}}
width="100%"
height="100%"
background="grey"
border="none"
borderRadius="5px"
outline="none"
onChange={onInputChange}
onFocus={openCommander}
placeholder="Imagine.."
value={inputValue}
></Input>
)
}
const MainInput = React.useMemo(() => {
return (
<Center
position="relative"
overflow="hidden"
width={["100%", "400px"]}
maxWidth={[mobileVW, "100%"]}
height="100%"
padding="1px"
borderRadius="6px"
pointerEvents="all"
outline="none"
>
{props?.rainbow && (
<Rainbow
opacity={commanderActive ? 0.6 : 0}
position="absolute"
repeats={rainbowRepeats}
opacityTransition="all 2500ms ease"
thickness={10}
></Rainbow>
)}
{InputJSX}
</Center>
<MagicInput
placeholder={props?.placeholder || "Imagine.."}
onValueChange={onMagicInput}
onFocus={openCommander}
chakras={{
marginX: commanderActive && props?.rainbow ? 4 : 0,
}}
transition="all 0.5s ease-in-out"
></MagicInput>
)
}, [InputJSX, commanderActive, rainbowRepeats, props?.rainbow, mobileVW])
}, [
inputRef,
onInputChange,
commanderActive,
props?.rainbow,
props?.placeholder,
openCommander,
onMagicInput,
props?.simple,
inputValue,
])
return (
<ClickAwayListener onClickAway={closeCommander}>
<Flex
position="absolute"
top={0}
right={0}
// position="absolute"
// top={0}
// right={0}
// left={0}
// zIndex={99999}
// position='fixed'
// top='100px'
left={0}
justifyContent={["flex-start", "center"]}
className={"commander-uuid-" + commanderId}
// display={["flex", commanderActive ? "flex" : "none"]}
justifyContent="flex-start"
maxWidth="100%"
height={12}
// height="100%"
pointerEvents="none"
id="commander"
paddingX={1}
// paddingX={1}
>
<Center
position="relative"
flexDirection="column"
width={["100%", "400px"]}
maxWidth={[mobileVW, "100%"]}
height="100%"
>
{props?.rainbow && (
<Rainbow
filter="blur(15px)"
opacity={commanderActive ? 0.25 : 0}
repeats={rainbowRepeats}
thickness={10}
opacityTransition="all 1000ms ease"
overflow="visible"
>
<Center
position="relative"
overflow="hidden"
width={["100%", "400px"]}
maxWidth={[mobileVW, "100%"]}
height="100%"
padding="1px"
borderRadius="6px"
pointerEvents="all"
outline="none"
>
<Rainbow
opacity={commanderActive ? 0.6 : 0}
position="absolute"
repeats={rainbowRepeats}
opacityTransition="all 2500ms ease"
thickness={1}
>
{/* <InputPartWrapper>{InputPart}</InputPartWrapper> */}
{InputPart}
</Rainbow>
</Center>
</Rainbow>
)}
{!props?.rainbow && InputPart}
</Center>
<Flex
position="absolute"
top="100%"
right={0}
left={0}
alignItems={["flex-start", "center"]}
// position="absolute"
// top="100%"
// right={0}
// left={0}
alignItems="flex-start"
flexDirection="column"
maxWidth="100%"
height="auto"
marginTop={2}
// marginTop={2}
borderRadius="12px"
marginX={1}
// marginX={1}
>
<Flex
alignItems={["flex-start", "center"]}
@ -541,7 +621,7 @@ export const Commander = (props) => {
)
})}
</Flex>
{showContext && (
{showContext && props?.context && (
<Flex
display={showContext ? "flex" : "none"}
maxWidth="100%"
@ -559,26 +639,6 @@ export const Commander = (props) => {
)}
</Flex>
</Flex>
<Center
position="relative"
width={["100%", "400px"]}
maxWidth={[mobileVW, "100%"]}
height="100%"
>
{props?.rainbow && (
<Rainbow
filter="blur(15px)"
opacity={commanderActive ? 0.25 : 0}
repeats={rainbowRepeats}
thickness={8}
opacityTransition="all 1000ms ease"
overflow="visible"
>
{MainInput}
</Rainbow>
)}
{!props?.rainbow && MainInput}
</Center>
</Flex>
</ClickAwayListener>
)

View File

@ -0,0 +1,568 @@
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 CommanderV1 = (props) => {
const { thingtime, setThingtime, getThingtime, thingtimeRef, paths } =
useThingtime()
const commanderId = React.useMemo(() => {
return props?.id || "global"
}, [props?.id])
const inputRef = React.useRef()
const global = props?.global
const commanderSettings = React.useMemo(() => {
return thingtime?.settings?.commander?.[commanderId] || {}
}, [
thingtime?.settings?.commander,
thingtime?.settings?.commander?.[commanderId],
commanderId,
])
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 mode = React.useMemo(() => {
return props?.mode || "value"
}, [props?.mode])
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?.commander?.[commanderId]?.commanderActive
}, [commanderSettings, commanderId])
// commanderActive useEffect
React.useEffect(() => {
if (commanderActive) {
inputRef?.current?.focus?.()
} else {
document.activeElement.blur()
if (
thingtimeRef?.current?.settings?.commander?.[commanderId]
?.clearCommanderOnToggle
) {
setInputValue("")
setHoveredSuggestion(null)
}
if (
thingtimeRef?.current?.settings?.commander?.[commanderId]?.commander?.[
commanderId
]?.clearCommanderContextOnToggle
) {
setShowContext(false, "commanderActive useEffect")
}
if (contextPath !== undefined && !inputValue) {
setContextPath(undefined)
}
if (showContext !== false) {
setShowContext(false)
}
}
}, [
commanderActive,
thingtimeRef,
setShowContext,
commanderId,
inputValue,
contextPath,
showContext,
])
const onInputChange = React.useCallback((e) => {
setInputValue(e.target.value)
setHoveredSuggestion(null)
}, [])
const validSetters = React.useMemo(() => {
return ["=", " is ", " IS ", " Is ", " iS "]
}, [])
const command = React.useMemo(() => {
// const sanitizedCommand = sanitise(value)
// const sanitizedCommand = inputValue
const sanitizedCommand = virtualValue
const validSetter = validSetters?.find((setter) => {
if (sanitizedCommand?.includes(setter)) {
return setter
}
return false
})
if (typeof validSetter === "string") {
const indexOfSplitter = sanitizedCommand?.indexOf(validSetter)
const [pathRaw, valRaw] = [
sanitizedCommand?.slice(0, indexOfSplitter),
sanitizedCommand?.slice(indexOfSplitter + validSetter?.length),
]
return [pathRaw?.trim(), valRaw?.trim()]
}
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 suggestions = React.useMemo(() => {
try {
const fuse = new Fuse(paths)
const results = fuse.search(inputValue)
const mappedResults = results?.map((result) => {
return result?.item
})
return mappedResults
} catch (err) {
console.error("fuse error", err)
}
}, [inputValue, paths])
const showSuggestions = React.useMemo(() => {
return (
inputValue?.length &&
suggestions?.length &&
commanderActive &&
thingtime?.settings?.commander?.[commanderId]?.hideSuggestionsOnToggle
)
}, [
inputValue,
suggestions,
commanderActive,
commanderId,
thingtime?.settings?.commander,
commanderSettings,
])
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(() => {
const commandIncludesSuggestion = suggestions?.find((suggestion) => {
return commandPath?.includes(suggestion)
})
// return false
return commandIncludesSuggestion
}, [commandPath, suggestions])
const openCommander = React.useCallback(() => {
setThingtime(`settings.commander.${commanderId}.commanderActive`, true)
}, [setThingtime, commanderId])
const closeCommander = React.useCallback(
(e?: any) => {
if (!e?.defaultPrevented) {
if (thingtime?.settings?.commander?.[commanderId]?.commanderActive) {
setThingtime(
`settings.commander.${commanderId}.commanderActive`,
false
)
}
}
},
[
setThingtime,
commanderId,
commanderSettings,
thingtime?.settings?.commander,
]
)
const toggleCommander = React.useCallback(() => {
if (thingtime?.settings?.commander?.[commanderId]?.commanderActive) {
closeCommander()
} else {
openCommander()
}
}, [
thingtime?.settings?.commander,
commanderSettings,
commanderId,
closeCommander,
openCommander,
])
const executeCommand = React.useCallback(() => {
// if selection is active then select it
const curSuggestionIdx = hoveredSuggestion
if (curSuggestionIdx !== null) {
selectSuggestion(curSuggestionIdx)
}
if (commanderActive) {
try {
if (commandIsAction) {
// nothing
const prevVal = getThingtime(commandPath)
const parentPath = getParentPath(commandPath) || "thingtime"
try {
// first try to execute literal javscript
const fn = `() => { return ${commandValue} }`
const evalFn = eval(fn)
const realVal = evalFn()
setThingtime(commandPath, realVal)
} catch (err) {
console.log(
"Caught error after trying to execute literal javascript",
err
)
// likely literaly javascript wasn't valid
try {
const fn = `() => { return ${escapedCommandValue} }`
const evalFn = eval(fn)
const realVal = evalFn()
const prevVal = getThingtime(commandPath)
const parentPath = getParentPath(commandPath)
setThingtime(commandPath, realVal)
} catch {
// something very bad went wrong
console.log(
"Caught error after trying to execute escaped literal javascript",
err
)
}
}
// if (!prevVal) {
setContextPath(commandPath)
setShowContext(true, "commandIsAction check")
// }
}
// if (commandContainsPath)
else {
// 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)
}
}
}, [
hoveredSuggestion,
selectSuggestion,
commanderActive,
commandIsAction,
commandPath,
commandValue,
escapedCommandValue,
getThingtime,
setThingtime,
setContextPath,
setShowContext,
])
const allCommanderKeyListener = React.useCallback(
(e: any) => {
console.log("commander key listener e?.code", e?.code)
thingtimeRef.current = thingtime
if (e?.metaKey && e?.code === "KeyP") {
e.preventDefault()
e.stopPropagation()
toggleCommander()
}
// if key escape close all modals
else if (e?.code === "Escape") {
closeCommander()
}
// only run these if commander active
if (commanderActive) {
// if arrow keys then move selection
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") {
executeCommand()
}
}
},
[
closeCommander,
toggleCommander,
hoveredSuggestion,
suggestions,
thingtime,
thingtimeRef,
commanderActive,
executeCommand,
]
)
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 (
<ClickAwayListener onClickAway={closeCommander}>
<Flex
position="absolute"
top={0}
right={0}
// zIndex={99999}
// position='fixed'
// top='100px'
left={0}
justifyContent={["flex-start", "center"]}
// display={["flex", commanderActive ? "flex" : "none"]}
maxWidth="100%"
height={12}
// height="100%"
pointerEvents="none"
id="commander"
paddingX={1}
>
<Flex
position="absolute"
top="100%"
right={0}
left={0}
alignItems={["flex-start", "center"]}
flexDirection="column"
maxWidth="100%"
height="auto"
marginTop={2}
borderRadius="12px"
marginX={1}
>
<Flex
alignItems={["flex-start", "center"]}
flexDirection="column"
overflowY="scroll"
width="auto"
maxWidth="100%"
maxHeight="90vh"
borderRadius="12px"
>
<Flex
flexDirection="column"
flexShrink={0}
display={showSuggestions ? "flex" : "none"}
overflowY="scroll"
width={["100%", "400px"]}
maxWidth="100%"
maxHeight="300px"
marginBottom={3}
background="grey"
borderRadius="12px"
pointerEvents="all"
id="commander-suggestions"
onMouseLeave={() => setHoveredSuggestion(null)}
paddingY={3}
>
{suggestions?.map((suggestion, i) => {
return (
<Flex
key={i}
background={hoveredSuggestion === i ? "greys.lightt" : null}
_hover={{
background: "greys.lightt",
}}
cursor="pointer"
onClick={() => selectSuggestion(i)}
onMouseEnter={() => setHoveredSuggestion(i)}
paddingX={4}
>
{suggestion}
</Flex>
)
})}
</Flex>
{showContext && (
<Flex
display={showContext ? "flex" : "none"}
maxWidth="100%"
background="grey"
borderRadius="12px"
pointerEvents="all"
paddingY={3}
>
<Thingtime
width="600px"
path={contextPath}
thing={contextValue}
></Thingtime>
</Flex>
)}
</Flex>
</Flex>
<Center
position="relative"
width={["100%", "400px"]}
maxWidth={[mobileVW, "100%"]}
height="100%"
>
<Rainbow
filter="blur(15px)"
opacity={commanderActive ? 0.25 : 0}
repeats={rainbowRepeats}
thickness={8}
opacityTransition="all 1000ms ease"
overflow="visible"
>
<Center
position="relative"
overflow="hidden"
width={["100%", "400px"]}
maxWidth={[mobileVW, "100%"]}
height="100%"
padding="1px"
borderRadius="6px"
pointerEvents="all"
outline="none"
>
<Rainbow
opacity={commanderActive ? 0.6 : 0}
position="absolute"
repeats={rainbowRepeats}
opacityTransition="all 2500ms ease"
thickness={10}
></Rainbow>
<Input
// display='none'
// opacity={0}
ref={inputRef}
sx={{
"&::placeholder": {
color: "greys.dark",
},
}}
width="100%"
height="100%"
background="grey"
border="none"
borderRadius="5px"
outline="none"
onChange={onInputChange}
onFocus={openCommander}
placeholder="Imagine.."
value={inputValue}
></Input>
</Center>
</Rainbow>
</Center>
</Flex>
</ClickAwayListener>
)
}

View File

@ -12,12 +12,21 @@ export const Icon = (props) => {
if (["crystal"]?.includes(name)) {
return "🔮"
}
if (["flower", "hibiscus"]?.includes(name)) {
return "🌺"
}
if (["sparke", "magic"]?.includes(name)) {
return "✨"
}
if (["box", "thing", "object"]?.includes(name)) {
return "📦"
}
if (["pencil"]?.includes(name)) {
return "✏️"
}
if (["edit", "paint", "create"]?.includes(name)) {
return "🎨"
}
if (["book", "books"]?.includes(name)) {
return "📚"
}
@ -82,6 +91,9 @@ export const Icon = (props) => {
if (["star", "favorite"]?.includes(name)) {
return "⭐"
}
if (["glowing star", "glowing favorite"]?.includes(name)) {
return "🌟"
}
if (["question", "help"]?.includes(name)) {
return "❓"
}
@ -152,7 +164,11 @@ export const Icon = (props) => {
}, [name])
return (
<Center {...props?.chakras} fontSize={props?.size}>
<Center
transition="all 0.2s ease-out"
{...props?.chakras}
fontSize={props?.size}
>
{icon}
</Center>
)

View File

@ -0,0 +1,150 @@
import React from "react"
import { Box } from "@chakra-ui/react"
import { useThingtime } from "../Thingtime/useThingtime"
export const MagicInput = (props) => {
const { thingtime, setThingtime, loading } = useThingtime()
const [inputValue, setInputValue] = React.useState()
const contentEditableRef = React.useRef(null)
const editValueRef = React.useRef({})
const fullPath = React.useMemo(() => {
const ret = props?.fullPath || props?.path
// store this thing in the global db
try {
// window.meta.things[ret] = props?.value
} catch {
// nothing
}
return ret
}, [props?.fullPath, props?.path, props?.value])
const [contentEditableValue, setContentEditableValue] = React.useState(
props?.value || props?.placeholder
)
const updateContentEditableValue = React.useCallback((value) => {
// replace all new line occurences in value with <div><br></div>
console.log("MagicInput updating contentEditableValue with value", value)
// extract all series of new lines
const newlines = value?.split?.(/[^\n]/)?.filter((v) => v !== "")
let newValue = value
// replace all new lines groups with <div><br></div>
newlines?.forEach?.((newline) => {
const baseLength = "\n"?.length
const newlineClone = newline
const newlineClonePart1 = newlineClone?.replace(
"\n\n\n",
"<div><br /></div>"
)
const newlineClonePart2 = newlineClonePart1?.replace(
/\n\n/g,
"<div><br /></div>"
)
const newlineClonePart3 = newlineClonePart2?.replace(/\n/g, "<br />")
newValue = newValue?.replace(newline, newlineClonePart3)
})
setContentEditableValue(newValue)
}, [])
React.useEffect(() => {
setInputValue(contentEditableValue)
}, [contentEditableValue])
React.useEffect(() => {
const entries = Object.entries(editValueRef.current)
const propsValueInEntries = entries?.find?.(
(entry) => entry[1] === props?.value
)
if (!propsValueInEntries) {
// const valueToUse = props?.value || props?.placeholder
const valueToUse = props?.value
updateContentEditableValue(valueToUse)
// setContentEditableValue(props?.value)
} else {
const [time, value] = propsValueInEntries
if (time && value) {
delete editValueRef.current[time]
}
}
}, [props?.value, props?.placeholder, updateContentEditableValue])
const onValueChange = React.useCallback(
(args) => {
const { value } = args
props?.onValueChange?.({ value })
// updateContentEditableValue(value)
},
[props?.onValueChange]
)
const updateValue = React.useCallback(
(args) => {
const { value } = args
onValueChange({ value })
setInputValue(value)
// setThingtime(fullPath, value)
},
[fullPath, setThingtime, onValueChange]
)
const onFocus = React.useCallback(
(e) => {
props?.onFocus?.(e)
},
[props?.onFocus]
)
return (
<Box
position="relative"
width="100%"
transition={props?.transition}
{...(props?.chakras || {})}
>
<Box
ref={contentEditableRef}
width="100%"
border="none"
outline="none"
contentEditable={true}
dangerouslySetInnerHTML={{ __html: contentEditableValue }}
onFocus={onFocus}
onInput={(value) => {
const innerText = value?.target?.innerText
console.log("MagicInput got onInput event value", value)
console.log("MagicInput got onInput event innerText", innerText)
if (typeof innerText === "string") {
const time = Date.now()
editValueRef.current[time] = innerText
updateValue({ value: innerText })
}
}}
></Box>
<Box
position="absolute"
top={0}
left={0}
display={inputValue ? "none" : "block"}
color="greys.dark"
pointerEvents="none"
>
{props?.placeholder || "Imagine.."}
</Box>
</Box>
)
}

View File

@ -1,8 +1,9 @@
import React from "react"
import { Box, Center, Flex } from "@chakra-ui/react"
import { Link } from "@remix-run/react"
import { Link, useLocation, useNavigate } from "@remix-run/react"
import { Commander } from "../Commander/Commander"
import { CommanderV1 } from "../Commander/CommanderV1"
import { Icon } from "../Icon/Icon"
import { RainbowSkeleton } from "../Skeleton/RainbowSkeleton"
import { ProfileDrawer } from "./ProfileDrawer"
@ -10,10 +11,47 @@ import { ProfileDrawer } from "./ProfileDrawer"
export const Nav = (props) => {
const [profileDrawerOpen, setProfileDrawerOpen] = React.useState(false)
const { pathname } = useLocation()
const navigate = useNavigate()
const toggleProfileDrawer = React.useCallback(() => {
setProfileDrawerOpen(!profileDrawerOpen)
}, [profileDrawerOpen])
const inEditMode = React.useMemo(() => {
if (pathname.slice(0, 5) === "/edit") {
return true
}
return false
}, [pathname])
const editorToggleable = React.useMemo(() => {
if (pathname.slice(0, 7) === "/things") {
return true
} else if (pathname.slice(0, 5) === "/edit") {
return true
}
return false
}, [pathname])
const toggleEditor = React.useCallback(
(e) => {
// if first characters of pathname are /things replace with /edit
// or if first characters of pathname are /edit replace with /things
if (pathname.slice(0, 7) === "/things") {
const newPathname = pathname.replace("/things", "/edit")
e?.preventDefault?.()
navigate(newPathname)
} else if (pathname.slice(0, 5) === "/edit") {
const newPathname = pathname.replace("/edit", "/things")
e?.preventDefault?.()
navigate(newPathname)
}
},
[pathname, navigate]
)
return (
<>
<Box
@ -33,31 +71,47 @@ export const Nav = (props) => {
width="100%"
maxWidth="100%"
marginY={1}
paddingX="12px"
paddingY="10px"
paddingX="18px"
paddingY="14px"
// bg='white'
// boxShadow={'0px 0px 10px rgba(0,0,0,0.1)'}
>
<Center
width="25px"
height="25px"
marginRight="auto"
transform="scaleX(-100%)"
cursor="pointer"
>
<Link to="/">
<Icon size="12px" name="unicorn"></Icon>
</Link>
<Center className="nav-left-section" height="100%" marginRight="auto">
<Center transform="scaleX(-100%)" cursor="pointer">
<Link to="/">
<Icon size="12px" name="unicorn"></Icon>
</Link>
</Center>
</Center>
<Commander global id="nav" rainbow></Commander>
<CommanderV1 global id="nav" rainbow></CommanderV1>
<Center
width="25px"
height="25px"
className="nav-right-section"
columnGap={[3, 5]}
height="100%"
marginLeft="auto"
transform="scaleX(-100%)"
cursor="pointer"
>
<Icon size="12px" name="rainbow"></Icon>
{editorToggleable && (
<Center
transform="scaleX(-100%)"
cursor="pointer"
onClick={toggleEditor}
>
<Icon
chakras={{
opacity: inEditMode ? 1 : 0.3,
}}
size="12px"
name="paint"
></Icon>
{/* <Icon
size="12px"
name={inEditMode ? "glowing star" : "star"}
></Icon> */}
</Center>
)}
<Center transform="scaleX(-100%)" cursor="pointer">
<Icon size="12px" name="rainbow"></Icon>
</Center>
</Center>
{/* <RainbowSkeleton
marginLeft="auto"

View File

@ -210,6 +210,7 @@ export const Rainbow = (allProps: any): JSX.Element => {
width="100%"
height="100%"
opacity={hidden ? "0" : opacity}
// pointerEvents="none"
transition={opacityTransition}
>
{/* debug svg */}

View File

@ -17,7 +17,9 @@ import {
} from "@chakra-ui/react"
import { Commander } from "../Commander/Commander"
// import { Magic } from "../Commander/Magic"
import { Icon } from "../Icon/Icon"
import { MagicInput } from "../MagicInput/MagicInput"
import { Safe } from "../Safety/Safe"
import { useThingtime } from "./useThingtime"
@ -242,64 +244,91 @@ export const Thingtime = (props) => {
return ["view", "edit"]
}, [])
const AtomicWrapper = React.useCallback((args) => {
return (
<Flex
position="relative"
flexDirection="row"
flexShrink={1}
width="100%"
paddingLeft={args?.pl || args?.paddingLeft}
fontSize="20px"
border="none"
// whiteSpace="pre-line"
whiteSpace="pre-wrap"
wordBreak={args?.wordBreak || "break-word"}
outline="none"
// paddingY={2}
// dangerouslySetInnerHTML={{ __html: renderableValue }}
>
{args?.children}
</Flex>
)
}, [])
const thingtimeChildren = React.useMemo(() => {
if (template1Modes?.includes(mode)) {
if (keys?.length && !circular) {
const ret = (
<Safe {...props}>
<Flex
className="nested-things"
position="relative"
flexDirection="column"
// w={'500px'}
// w={['200px', '500px']}
maxWidth="100%"
paddingLeft={valuePl}
paddingY={props?.path ? 3 : 0}
>
{keysToUse?.length &&
keysToUse.map((key, idx) => {
if (!key?.human) {
key = {
human: key,
key: key,
}
}
let inner = <AtomicWrapper paddingLeft={pl}>Imagine..</AtomicWrapper>
if (keys?.length && !circular) {
inner = (
<>
{keysToUse?.length &&
keysToUse.map((key, idx) => {
if (!key?.human) {
key = {
human: key,
key: key,
}
}
const nextThing = thing[key?.key]
const nextThing = thing[key?.key]
const nextSeen = [...seen]
const nextSeen = [...seen]
if (typeof nextThing === "object") {
nextSeen.push(nextThing)
}
if (typeof nextThing === "object") {
nextSeen.push(nextThing)
}
return (
<Thingtime
key={idx}
seen={nextSeen}
edit={props?.edit}
circular={seen?.includes?.(nextThing)}
depth={depth + 1}
parent={thing}
notRoot
fullPath={fullPath + "." + key?.key}
path={key}
thing={nextThing}
// thing={{ infinite: { yes: true } }}
valuePl={pl}
></Thingtime>
)
})}
</Flex>
</Safe>
)
return ret
}
return (
<Thingtime
key={idx}
seen={nextSeen}
edit={props?.edit}
circular={seen?.includes?.(nextThing)}
depth={depth + 1}
parent={thing}
notRoot
fullPath={fullPath + "." + key?.key}
path={key}
thing={nextThing}
// thing={{ infinite: { yes: true } }}
valuePl={pl}
></Thingtime>
)
})}
</>
)
}
if (type === "object" && !circular) {
return (
<Safe {...props}>
<Flex
className="nested-things"
position="relative"
flexDirection="column"
rowGap={9}
// w={'500px'}
// w={['200px', '500px']}
maxWidth="100%"
paddingLeft={valuePl}
paddingY={props?.path ? 4 : 0}
>
{inner}
</Flex>
</Safe>
)
}
}, [
keysToUse,
mode,
circular,
seen,
type,
@ -311,31 +340,8 @@ export const Thingtime = (props) => {
valuePl,
pl,
keys,
template1Modes,
])
const AtomicWrapper = React.useCallback((props) => {
return (
<Flex
position="relative"
flexDirection="row"
flexShrink={1}
width="100%"
paddingLeft={props?.pl || props?.paddingLeft}
fontSize="20px"
border="none"
// whiteSpace="pre-line"
whiteSpace="pre-wrap"
wordBreak={props?.wordBreak || "break-word"}
outline="none"
paddingY={2}
// dangerouslySetInnerHTML={{ __html: renderableValue }}
>
{props?.children}
</Flex>
)
}, [])
const [contentEditableThing, setContentEditableThing] = React.useState(thing)
const updateContentEditableThing = React.useCallback((value) => {
@ -388,6 +394,7 @@ export const Thingtime = (props) => {
(args) => {
const { value } = args
console.log("nik running updateValue", value)
setThingtime(fullPath, value)
},
[fullPath, setThingtime]
@ -449,10 +456,15 @@ export const Thingtime = (props) => {
</AtomicWrapper>
)
}
if (type === "string" && typeof contentEditableThing === "string") {
if (type === "string") {
return (
<AtomicWrapper paddingLeft={pl} className="string-atomic-wrapper">
<Box
<MagicInput
value={thing}
placeholder="Imagine.."
onValueChange={updateValue}
></MagicInput>
{/* <Box
ref={contentEditableRef}
width="100%"
border="none"
@ -467,15 +479,21 @@ export const Thingtime = (props) => {
updateValue({ value: innerText })
}
}}
></Box>
></Box> */}
</AtomicWrapper>
)
}
if (type === "undefined") {
return (
<AtomicWrapper>
<AtomicWrapper paddingLeft={pl}>
{/* TODO: Implement UI-less commander */}
<Commander path={fullPath} id={uuid}></Commander>
<Commander
// rainbow
id={uuid}
pathPrefix={fullPath}
placeholder="Imagine.."
// onValueChange={updateValue}
></Commander>
</AtomicWrapper>
)
}
@ -614,13 +632,14 @@ export const Thingtime = (props) => {
position="relative"
flexDirection="column"
// width="500px"
rowGap={2}
width={props?.width || props?.w || "100%"}
maxWidth="100%"
// marginTop={3}
paddingRight={pr}
// minW={depth === 1 ? '120px' : null}
onMouseEnter={handleMouseEvent}
onMouseLeave={handleMouseEvent}
// minW={depth === 1 ? '120px' : null}
paddingY={3}
{...(props.chakras || {})}
className={`thing uuid-${uuid}`}
data-path={props?.path}
@ -663,9 +682,11 @@ export const Thingtime = (props) => {
<Box className="atomicValue">{atomicValue}</Box>
)}
{!loading && thingtimeChildren && (
<Box className="thingtimeChildren">{thingtimeChildren}</Box>
<Box className="thingtimeChildren">
{thingtimeChildren}
{addChildUi}
</Box>
)}
{addChildUi}
</Flex>
</Safe>
)