2023-07-07 07:42:14 +00:00
|
|
|
import React from "react"
|
2023-08-03 10:56:42 +00:00
|
|
|
import ContentEditable from "react-contenteditable"
|
2023-08-15 23:10:13 +00:00
|
|
|
import * as Chakras from "@chakra-ui/react"
|
2023-07-21 14:32:08 +00:00
|
|
|
import {
|
|
|
|
Box,
|
2023-08-07 22:05:07 +00:00
|
|
|
Center,
|
2023-07-21 14:32:08 +00:00
|
|
|
Flex,
|
|
|
|
Input,
|
|
|
|
NumberDecrementStepper,
|
|
|
|
NumberIncrementStepper,
|
|
|
|
NumberInput,
|
|
|
|
NumberInputField,
|
|
|
|
NumberInputStepper,
|
|
|
|
Select,
|
2023-08-07 22:05:07 +00:00
|
|
|
Spinner,
|
2023-07-21 14:32:08 +00:00
|
|
|
Switch,
|
2023-08-03 13:48:56 +00:00
|
|
|
Textarea,
|
2023-07-21 14:32:08 +00:00
|
|
|
} from "@chakra-ui/react"
|
2023-06-28 08:25:17 +00:00
|
|
|
|
2023-08-12 12:22:52 +00:00
|
|
|
import { Commander } from "../Commander/Commander"
|
2023-08-13 05:53:47 +00:00
|
|
|
// import { Magic } from "../Commander/Magic"
|
2023-07-21 01:13:02 +00:00
|
|
|
import { Icon } from "../Icon/Icon"
|
2023-08-13 05:53:47 +00:00
|
|
|
import { MagicInput } from "../MagicInput/MagicInput"
|
2023-07-07 07:42:14 +00:00
|
|
|
import { Safe } from "../Safety/Safe"
|
2023-08-14 02:58:50 +00:00
|
|
|
import { SettingsMenu } from "./SettingsMenu"
|
2023-07-07 07:42:14 +00:00
|
|
|
import { useThingtime } from "./useThingtime"
|
|
|
|
|
2023-08-13 08:30:34 +00:00
|
|
|
import { getThing } from "~/smarts"
|
|
|
|
|
2023-07-07 07:42:14 +00:00
|
|
|
export const Thingtime = (props) => {
|
2023-07-01 14:22:57 +00:00
|
|
|
// TODO: Add a circular reference seen prop check
|
|
|
|
// and add button to expand circular reference
|
|
|
|
// up to 1 level deep
|
2023-06-29 23:41:06 +00:00
|
|
|
|
2023-08-13 08:05:51 +00:00
|
|
|
const { thingtime, setThingtime, getThingtime, loading } = useThingtime()
|
2023-07-21 01:13:02 +00:00
|
|
|
|
2023-08-09 03:13:46 +00:00
|
|
|
const [uuid, setUuid] = React.useState(undefined)
|
2023-06-30 01:46:42 +00:00
|
|
|
|
2023-08-07 22:05:07 +00:00
|
|
|
const [root, setRoot] = React.useState(props?.notRoot ? false : true)
|
|
|
|
|
2023-07-21 01:13:02 +00:00
|
|
|
const [circular, setCircular] = React.useState(props?.circular)
|
|
|
|
|
2023-08-17 14:21:38 +00:00
|
|
|
const thingtimeRef = React.useRef()
|
|
|
|
|
2023-08-03 13:48:56 +00:00
|
|
|
const editValueRef = React.useRef({})
|
2023-08-03 10:56:42 +00:00
|
|
|
|
2023-08-09 03:10:00 +00:00
|
|
|
const depth = React.useMemo(() => {
|
2023-08-15 23:10:13 +00:00
|
|
|
return typeof props?.depth === "number" ? props?.depth : 0
|
2023-08-09 03:10:00 +00:00
|
|
|
}, [props?.depth])
|
|
|
|
|
2023-08-15 23:10:13 +00:00
|
|
|
const render = React.useMemo(() => {
|
2023-08-17 14:23:06 +00:00
|
|
|
return !props?.edit || props?.render || false
|
|
|
|
}, [props?.render, props?.edit])
|
2023-08-15 23:10:13 +00:00
|
|
|
|
|
|
|
const width = React.useMemo(() => {
|
|
|
|
if (props?.width) {
|
|
|
|
return props?.width
|
|
|
|
}
|
|
|
|
if (props?.w) {
|
|
|
|
return props?.w
|
|
|
|
}
|
|
|
|
if (render) {
|
|
|
|
return "auto"
|
|
|
|
}
|
|
|
|
return "100%"
|
|
|
|
}, [props?.width, props?.w, render])
|
|
|
|
|
|
|
|
const chakras = React.useMemo(() => {
|
|
|
|
if (!props?.edit && props?.chakra) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}, [props?.edit, props?.chakra])
|
|
|
|
|
2023-08-09 03:10:00 +00:00
|
|
|
const pl = React.useMemo(() => {
|
2023-08-15 23:10:13 +00:00
|
|
|
if (!props.edit && chakras) {
|
|
|
|
return [0]
|
|
|
|
}
|
|
|
|
|
2023-08-09 03:10:00 +00:00
|
|
|
return props?.pl || [4, 6]
|
2023-08-15 23:10:13 +00:00
|
|
|
}, [props?.pl, props?.edit, chakras])
|
2023-08-09 03:10:00 +00:00
|
|
|
|
|
|
|
const pr = React.useMemo(() => {
|
2023-08-15 23:10:13 +00:00
|
|
|
return props?.pr || (depth === 0 ? [4, 6] : 0)
|
2023-08-09 03:10:00 +00:00
|
|
|
}, [props?.pr, depth])
|
|
|
|
|
2023-08-12 12:22:52 +00:00
|
|
|
const multiplyPl = React.useCallback(
|
|
|
|
(num) => {
|
|
|
|
return pl.map((p) => p * num)
|
|
|
|
},
|
|
|
|
[pl]
|
|
|
|
)
|
|
|
|
|
2023-08-12 14:06:33 +00:00
|
|
|
const propsRef = React.useRef(props)
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
propsRef.current = props
|
|
|
|
}, [props])
|
|
|
|
|
2023-08-09 03:10:00 +00:00
|
|
|
// will only run on the client
|
|
|
|
React.useEffect(() => {
|
|
|
|
setUuid(Math.random().toString(36).substring(7))
|
|
|
|
}, [])
|
|
|
|
|
2023-08-09 02:19:57 +00:00
|
|
|
const childrenRef = React.useRef([])
|
|
|
|
|
|
|
|
const [thingDep, setThingDep] = React.useState(childrenRef.current)
|
|
|
|
|
|
|
|
const createDependancies = () => {
|
|
|
|
// push all children into childrenRef.current
|
2023-08-09 03:10:00 +00:00
|
|
|
|
|
|
|
try {
|
2023-08-09 03:17:19 +00:00
|
|
|
window.meta.db["createDependancies"] =
|
|
|
|
window.meta.db["createDependancies"] || 0
|
|
|
|
window.meta.db["createDependancies"]++
|
2023-08-09 03:10:00 +00:00
|
|
|
} catch {}
|
|
|
|
|
2023-08-09 02:19:57 +00:00
|
|
|
try {
|
|
|
|
const values = Object.values(props?.thing)
|
|
|
|
// if childrenRef.current does not shallow equal values then replace with array of values
|
|
|
|
const valuesNotEqual =
|
|
|
|
values?.length !== childrenRef.current?.length ||
|
|
|
|
!values?.every?.((value, idx) => {
|
|
|
|
return childrenRef.current[idx] === value
|
|
|
|
})
|
|
|
|
if (valuesNotEqual) {
|
|
|
|
childrenRef.current = values
|
|
|
|
setThingDep(childrenRef.current)
|
|
|
|
}
|
|
|
|
} catch {
|
|
|
|
// nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-30 01:46:42 +00:00
|
|
|
React.useEffect(() => {
|
2023-08-09 03:10:00 +00:00
|
|
|
createDependancies()
|
2023-07-01 09:46:36 +00:00
|
|
|
}, [])
|
|
|
|
|
2023-06-28 08:25:17 +00:00
|
|
|
const thing = React.useMemo(() => {
|
|
|
|
return props.thing
|
2023-08-09 02:19:57 +00:00
|
|
|
}, [props.thing, childrenRef.current])
|
|
|
|
|
2023-08-15 23:10:13 +00:00
|
|
|
const chakra = React.useMemo(() => {
|
|
|
|
return !props?.edit && typeof thing?.chakra === "string" && thing?.chakra
|
|
|
|
}, [thing?.chakra, props?.edit])
|
|
|
|
|
2023-08-13 08:05:51 +00:00
|
|
|
const path = React.useMemo(() => {
|
|
|
|
return props?.path?.key || props?.path || ""
|
|
|
|
}, [props?.path])
|
|
|
|
|
2023-08-12 14:06:33 +00:00
|
|
|
const fullPath = React.useMemo(() => {
|
2023-08-13 08:05:51 +00:00
|
|
|
const ret = props?.fullPath || props?.path?.key || props?.path
|
2023-08-12 14:06:33 +00:00
|
|
|
|
|
|
|
// store this thing in the global db
|
|
|
|
try {
|
|
|
|
window.meta.things[ret] = props?.thing
|
|
|
|
} catch {
|
|
|
|
// nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}, [props?.fullPath, props?.path, props?.thing])
|
|
|
|
|
2023-08-13 08:30:34 +00:00
|
|
|
const parentPath = React.useMemo(() => {
|
2023-08-13 10:41:55 +00:00
|
|
|
const parentPath = fullPath?.split(".")?.slice(0, -1)?.join(".")
|
2023-08-13 08:30:34 +00:00
|
|
|
|
|
|
|
if (!parentPath) {
|
|
|
|
return "thingtime"
|
|
|
|
}
|
|
|
|
|
|
|
|
return parentPath
|
|
|
|
}, [fullPath])
|
|
|
|
|
|
|
|
const parent = React.useMemo(() => {
|
|
|
|
return getThingtime(parentPath)
|
|
|
|
}, [parentPath, getThingtime])
|
|
|
|
|
2023-08-09 02:19:57 +00:00
|
|
|
React.useEffect(() => {
|
|
|
|
console.log("thingtime changed in path", props?.fullPath)
|
|
|
|
createDependancies()
|
|
|
|
}, [thingtime, props?.fullPath, childrenRef])
|
2023-06-28 08:25:17 +00:00
|
|
|
|
2023-07-15 08:08:36 +00:00
|
|
|
const seen = React.useMemo(() => {
|
|
|
|
if (props?.seen instanceof Array) {
|
|
|
|
if (props?.seen?.includes(thing)) {
|
|
|
|
return props?.seen
|
|
|
|
} else if (typeof thing === "object") {
|
|
|
|
return [...props.seen, thing]
|
|
|
|
}
|
|
|
|
return props?.seen || []
|
|
|
|
}
|
|
|
|
if (typeof thing === "object") {
|
|
|
|
return [thing]
|
|
|
|
}
|
|
|
|
return []
|
|
|
|
}, [props?.seen, thing])
|
|
|
|
|
2023-06-29 11:06:58 +00:00
|
|
|
const mode = React.useMemo(() => {
|
2023-07-07 07:42:14 +00:00
|
|
|
return "view"
|
2023-06-29 11:06:58 +00:00
|
|
|
}, [])
|
|
|
|
|
|
|
|
const validKeyTypes = React.useMemo(() => {
|
2023-07-07 07:42:14 +00:00
|
|
|
return ["object", "array"]
|
2023-06-29 11:06:58 +00:00
|
|
|
}, [])
|
|
|
|
|
|
|
|
const keys = React.useMemo(() => {
|
|
|
|
if (validKeyTypes?.includes(typeof thing)) {
|
2023-07-24 01:36:25 +00:00
|
|
|
try {
|
|
|
|
const keysRet = Object.keys(thing)
|
|
|
|
return keysRet
|
|
|
|
} catch {
|
|
|
|
// nothing
|
|
|
|
}
|
2023-06-29 11:06:58 +00:00
|
|
|
} else {
|
|
|
|
return []
|
|
|
|
}
|
2023-08-09 02:19:57 +00:00
|
|
|
}, [thing, thingDep, validKeyTypes])
|
2023-06-29 11:06:58 +00:00
|
|
|
|
|
|
|
const type = React.useMemo(() => {
|
2023-08-12 12:22:52 +00:00
|
|
|
if (thing === null) {
|
|
|
|
return "undefined"
|
|
|
|
}
|
2023-06-29 11:06:58 +00:00
|
|
|
return typeof thing
|
|
|
|
}, [thing])
|
|
|
|
|
2023-07-21 13:33:47 +00:00
|
|
|
const typeIcon = React.useMemo(() => {
|
|
|
|
const size = 7
|
|
|
|
if (thing instanceof Array) {
|
|
|
|
return <Icon name="array" size={size}></Icon>
|
|
|
|
} else if (type === "object") {
|
|
|
|
return <Icon name="object" size={size}></Icon>
|
|
|
|
} else if (type === "string") {
|
|
|
|
return <Icon name="string" size={size}></Icon>
|
|
|
|
} else if (type === "number") {
|
|
|
|
return <Icon name="number" size={size}></Icon>
|
|
|
|
} else if (type === "boolean") {
|
|
|
|
return <Icon name="boolean" size={size}></Icon>
|
2023-08-12 12:22:52 +00:00
|
|
|
} else if (type === "undefined") {
|
|
|
|
return <Icon name="undefined" size={size}></Icon>
|
2023-07-21 13:33:47 +00:00
|
|
|
} else {
|
|
|
|
return <Icon name="box" size={size}></Icon>
|
|
|
|
}
|
|
|
|
}, [type, thing])
|
|
|
|
|
2023-07-01 15:05:55 +00:00
|
|
|
const valuePl = React.useMemo(() => {
|
2023-07-07 07:42:14 +00:00
|
|
|
if (typeof props?.valuePl === "number") {
|
2023-07-01 15:05:55 +00:00
|
|
|
return props?.valuePl
|
|
|
|
}
|
|
|
|
return props?.path ? [4, 6] : [0, 0]
|
|
|
|
}, [props?.valuePl, props?.path])
|
|
|
|
|
2023-06-29 11:06:58 +00:00
|
|
|
const renderableValue = React.useMemo(() => {
|
2023-08-15 23:10:13 +00:00
|
|
|
if (chakras) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2023-07-07 07:42:14 +00:00
|
|
|
if (type === "string") {
|
2023-08-13 06:14:40 +00:00
|
|
|
return <MagicInput value={thing} readonly></MagicInput>
|
2023-07-07 07:42:14 +00:00
|
|
|
} else if (type === "number") {
|
2023-06-29 11:06:58 +00:00
|
|
|
return thing
|
2023-07-07 07:42:14 +00:00
|
|
|
} else if (type === "boolean") {
|
|
|
|
return thing ? "true" : "false"
|
|
|
|
} else if (type === "object") {
|
2023-07-01 14:13:27 +00:00
|
|
|
if (thing === null) {
|
2023-07-07 07:42:14 +00:00
|
|
|
return "null"
|
2023-07-01 14:13:27 +00:00
|
|
|
}
|
2023-07-01 09:46:36 +00:00
|
|
|
if (!keys?.length) {
|
2023-07-07 07:42:14 +00:00
|
|
|
return "Something!"
|
2023-07-01 09:46:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
return JSON.stringify(thing, null, 2)
|
|
|
|
} catch (err) {
|
2023-07-15 08:08:36 +00:00
|
|
|
// console.error(
|
|
|
|
// "Caught error making renderableValue of thing",
|
|
|
|
// err,
|
|
|
|
// thing
|
|
|
|
// )
|
2023-07-24 01:36:25 +00:00
|
|
|
return (
|
|
|
|
<Box cursor="pointer" onClick={() => setCircular(false)}>
|
|
|
|
Click to Expand
|
|
|
|
</Box>
|
|
|
|
)
|
2023-07-01 09:46:36 +00:00
|
|
|
}
|
2023-08-12 12:22:52 +00:00
|
|
|
} else if (type === "undefined") {
|
2023-08-14 00:32:42 +00:00
|
|
|
return "Imagine.."
|
2023-06-29 11:06:58 +00:00
|
|
|
} else {
|
2023-08-14 00:32:42 +00:00
|
|
|
return "Something.."
|
2023-06-29 11:06:58 +00:00
|
|
|
}
|
2023-08-15 23:10:13 +00:00
|
|
|
}, [thing, thingDep, type, chakras, keys])
|
2023-06-29 11:06:58 +00:00
|
|
|
|
2023-08-17 10:41:40 +00:00
|
|
|
const hasChakraChildren = React.useMemo(() => {
|
|
|
|
return !props?.edit && chakra && render && thing?.children
|
|
|
|
}, [chakra, props?.edit, render, thing?.children])
|
|
|
|
|
|
|
|
const renderChakra = React.useMemo(() => {
|
|
|
|
if (!props?.edit && chakra && render) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}, [chakra, props?.edit, render])
|
|
|
|
|
2023-07-21 01:13:02 +00:00
|
|
|
const keysToUse = React.useMemo(() => {
|
2023-08-17 10:41:40 +00:00
|
|
|
if (renderChakra) {
|
2023-08-15 23:10:13 +00:00
|
|
|
return ["children"]
|
|
|
|
}
|
|
|
|
|
2023-07-21 01:13:02 +00:00
|
|
|
return keys
|
2023-08-17 14:21:38 +00:00
|
|
|
}, [keys, renderChakra])
|
2023-07-21 01:13:02 +00:00
|
|
|
// const keysToUse = flattenedKeys
|
2023-06-29 11:06:58 +00:00
|
|
|
|
2023-08-13 05:53:47 +00:00
|
|
|
const AtomicWrapper = React.useCallback((args) => {
|
2023-08-07 22:05:07 +00:00
|
|
|
return (
|
|
|
|
<Flex
|
2023-08-15 23:10:13 +00:00
|
|
|
className="atomic-wrapper"
|
2023-08-12 12:22:52 +00:00
|
|
|
position="relative"
|
2023-08-07 22:05:07 +00:00
|
|
|
flexDirection="row"
|
|
|
|
flexShrink={1}
|
|
|
|
width="100%"
|
2023-08-13 05:53:47 +00:00
|
|
|
paddingLeft={args?.pl || args?.paddingLeft}
|
2023-08-07 22:05:07 +00:00
|
|
|
fontSize="20px"
|
2023-08-09 03:34:18 +00:00
|
|
|
// whiteSpace="pre-line"
|
2023-08-15 23:10:13 +00:00
|
|
|
border="none"
|
2023-08-09 03:34:18 +00:00
|
|
|
whiteSpace="pre-wrap"
|
2023-08-13 05:53:47 +00:00
|
|
|
wordBreak={args?.wordBreak || "break-word"}
|
|
|
|
// paddingY={2}
|
2023-08-07 22:05:07 +00:00
|
|
|
// dangerouslySetInnerHTML={{ __html: renderableValue }}
|
2023-08-15 23:10:13 +00:00
|
|
|
outline="none"
|
2023-08-07 22:05:07 +00:00
|
|
|
>
|
2023-08-13 05:53:47 +00:00
|
|
|
{args?.children}
|
2023-08-07 22:05:07 +00:00
|
|
|
</Flex>
|
|
|
|
)
|
|
|
|
}, [])
|
2023-07-21 13:33:47 +00:00
|
|
|
|
2023-08-13 05:53:47 +00:00
|
|
|
const thingtimeChildren = React.useMemo(() => {
|
|
|
|
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 nextSeen = [...seen]
|
|
|
|
|
|
|
|
if (typeof nextThing === "object") {
|
|
|
|
nextSeen.push(nextThing)
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Thingtime
|
|
|
|
key={idx}
|
|
|
|
seen={nextSeen}
|
|
|
|
edit={props?.edit}
|
2023-08-15 23:10:13 +00:00
|
|
|
render={render}
|
2023-08-13 05:53:47 +00:00
|
|
|
circular={seen?.includes?.(nextThing)}
|
|
|
|
depth={depth + 1}
|
|
|
|
parent={thing}
|
|
|
|
notRoot
|
|
|
|
fullPath={fullPath + "." + key?.key}
|
|
|
|
path={key}
|
2023-08-17 10:41:40 +00:00
|
|
|
chakraChildren={chakra}
|
2023-08-13 05:53:47 +00:00
|
|
|
thing={nextThing}
|
|
|
|
// thing={{ infinite: { yes: true } }}
|
|
|
|
valuePl={pl}
|
|
|
|
></Thingtime>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (type === "object" && !circular) {
|
2023-08-15 23:10:13 +00:00
|
|
|
if (chakra) {
|
|
|
|
const ChakraComponent = Chakras[chakra]
|
|
|
|
|
|
|
|
console.log("Thingtime is chakra", fullPath, chakra)
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (ChakraComponent) {
|
|
|
|
console.log(
|
|
|
|
"Thingtime found ChakraComponent",
|
|
|
|
fullPath,
|
|
|
|
ChakraComponent
|
|
|
|
)
|
|
|
|
console.log("Thingtime found thing?.props", fullPath, thing?.props)
|
|
|
|
|
|
|
|
const ret = (
|
2023-08-17 10:41:40 +00:00
|
|
|
<ChakraComponent {...(thing?.props || {})}>
|
|
|
|
{inner}
|
|
|
|
</ChakraComponent>
|
2023-08-15 23:10:13 +00:00
|
|
|
)
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
} catch {
|
|
|
|
// chakra error
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-17 10:41:40 +00:00
|
|
|
// TODO: Is it safe to spread props
|
|
|
|
// because having props as a dependency will cause a re-render every time
|
|
|
|
|
|
|
|
if (props?.chakraChildren) {
|
|
|
|
return inner
|
|
|
|
}
|
|
|
|
|
2023-08-13 05:53:47 +00:00
|
|
|
return (
|
2023-08-17 10:41:40 +00:00
|
|
|
<Flex
|
|
|
|
className="nested-things"
|
|
|
|
position="relative"
|
|
|
|
flexDirection="column"
|
|
|
|
rowGap={!chakra ? 9 : 0}
|
|
|
|
// w={'500px'}
|
|
|
|
// w={['200px', '500px']}
|
|
|
|
maxWidth="100%"
|
|
|
|
paddingLeft={valuePl}
|
|
|
|
paddingY={!chakra && props?.path ? 4 : 0}
|
|
|
|
>
|
|
|
|
{inner}
|
|
|
|
</Flex>
|
2023-08-13 05:53:47 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}, [
|
2023-08-13 06:14:40 +00:00
|
|
|
AtomicWrapper,
|
2023-08-13 05:53:47 +00:00
|
|
|
keysToUse,
|
|
|
|
circular,
|
|
|
|
seen,
|
|
|
|
type,
|
2023-08-17 10:41:40 +00:00
|
|
|
props?.chakraChildren,
|
|
|
|
props?.path,
|
|
|
|
props?.edit,
|
2023-08-15 23:10:13 +00:00
|
|
|
chakra,
|
2023-08-13 05:53:47 +00:00
|
|
|
fullPath,
|
2023-08-15 23:10:13 +00:00
|
|
|
render,
|
2023-08-13 05:53:47 +00:00
|
|
|
depth,
|
|
|
|
thing,
|
|
|
|
thingDep,
|
|
|
|
valuePl,
|
|
|
|
pl,
|
|
|
|
keys,
|
|
|
|
])
|
|
|
|
|
2023-07-21 13:33:47 +00:00
|
|
|
const updateValue = React.useCallback(
|
|
|
|
(args) => {
|
|
|
|
const { value } = args
|
|
|
|
|
2023-07-24 02:41:52 +00:00
|
|
|
setThingtime(fullPath, value)
|
2023-07-21 13:33:47 +00:00
|
|
|
},
|
2023-07-24 02:41:52 +00:00
|
|
|
[fullPath, setThingtime]
|
2023-07-21 13:33:47 +00:00
|
|
|
)
|
|
|
|
|
2023-08-13 08:05:51 +00:00
|
|
|
const resetValue = React.useCallback(() => {
|
|
|
|
updateValue({ value: null })
|
|
|
|
}, [updateValue])
|
|
|
|
|
2023-08-14 02:58:50 +00:00
|
|
|
const onChangeType = React.useCallback(
|
|
|
|
(type) => {
|
2023-08-14 05:10:46 +00:00
|
|
|
const newType =
|
|
|
|
type?.key || type?.value || type?.label || type?.icon || type
|
2023-08-14 02:58:50 +00:00
|
|
|
|
|
|
|
if (newType === "object") {
|
|
|
|
updateValue({ value: {} })
|
|
|
|
} else if (newType === "array") {
|
|
|
|
updateValue({ value: [] })
|
|
|
|
} else if (newType === "string") {
|
|
|
|
updateValue({ value: "" })
|
|
|
|
} else if (newType === "number") {
|
|
|
|
updateValue({ value: 0 })
|
|
|
|
} else if (newType === "boolean") {
|
|
|
|
updateValue({ value: false })
|
|
|
|
} else if (newType === "undefined") {
|
|
|
|
updateValue({ value: undefined })
|
|
|
|
} else if (newType === "null") {
|
|
|
|
updateValue({ value: null })
|
|
|
|
} else if (newType === "any") {
|
|
|
|
updateValue({ value: null })
|
|
|
|
} else {
|
|
|
|
console.error("Unknown type", newType)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[updateValue]
|
|
|
|
)
|
|
|
|
|
2023-08-13 08:05:51 +00:00
|
|
|
const deleteValue = React.useCallback(() => {
|
|
|
|
// use parent path to clone parent object but without this key
|
|
|
|
const clone = { ...parent }
|
|
|
|
|
|
|
|
delete clone[path]
|
|
|
|
|
|
|
|
setThingtime(parentPath, clone)
|
2023-08-13 08:30:34 +00:00
|
|
|
}, [path, parent, parentPath, setThingtime])
|
2023-08-13 08:05:51 +00:00
|
|
|
|
2023-07-21 13:33:47 +00:00
|
|
|
const atomicValue = React.useMemo(() => {
|
|
|
|
if (props?.edit) {
|
|
|
|
if (type === "boolean") {
|
|
|
|
return (
|
2023-08-07 22:05:07 +00:00
|
|
|
<AtomicWrapper paddingLeft={pl} className="boolean-atomic-wrapper">
|
2023-07-21 13:33:47 +00:00
|
|
|
<Box
|
|
|
|
onClick={(e) => {
|
|
|
|
e?.preventDefault?.()
|
|
|
|
e?.stopPropagation?.()
|
|
|
|
// cancel bubble
|
|
|
|
e?.nativeEvent?.stopImmediatePropagation?.()
|
|
|
|
setTimeout(() => {
|
|
|
|
updateValue({ value: !thing })
|
|
|
|
}, 1)
|
|
|
|
}}
|
|
|
|
>
|
2023-07-24 01:36:25 +00:00
|
|
|
<Switch isChecked={thing}></Switch>
|
2023-07-21 13:33:47 +00:00
|
|
|
</Box>
|
2023-07-21 14:32:08 +00:00
|
|
|
</AtomicWrapper>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (type === "number") {
|
|
|
|
const numberPxLength = thing?.toString()?.length * 13 + 30
|
|
|
|
return (
|
2023-08-07 22:05:07 +00:00
|
|
|
<AtomicWrapper paddingLeft={pl} className="number-atomic-wrapper">
|
2023-07-21 14:32:08 +00:00
|
|
|
<Flex>
|
|
|
|
<NumberInput
|
|
|
|
alignItems="center"
|
|
|
|
justifyContent="center"
|
|
|
|
onChange={(value) => {
|
|
|
|
setTimeout(() => {
|
|
|
|
try {
|
|
|
|
const number = Number(value)
|
|
|
|
console.log("typeof number", typeof number)
|
|
|
|
updateValue({ value: number })
|
|
|
|
} catch {
|
|
|
|
// something went wrong casting to number
|
|
|
|
}
|
|
|
|
}, 1)
|
|
|
|
}}
|
|
|
|
value={thing}
|
|
|
|
>
|
|
|
|
<NumberInputField width={numberPxLength + "px"} />
|
|
|
|
<NumberInputStepper transform="scale(0.9)">
|
|
|
|
<NumberIncrementStepper
|
|
|
|
// transform="scale(0.7)"
|
|
|
|
/>
|
|
|
|
<NumberDecrementStepper
|
|
|
|
// transform="scale(0.7)"
|
|
|
|
/>
|
|
|
|
</NumberInputStepper>
|
|
|
|
</NumberInput>
|
|
|
|
</Flex>
|
2023-07-21 13:33:47 +00:00
|
|
|
</AtomicWrapper>
|
|
|
|
)
|
|
|
|
}
|
2023-08-13 05:53:47 +00:00
|
|
|
if (type === "string") {
|
2023-08-03 10:56:42 +00:00
|
|
|
return (
|
2023-08-07 22:05:07 +00:00
|
|
|
<AtomicWrapper paddingLeft={pl} className="string-atomic-wrapper">
|
2023-08-13 05:53:47 +00:00
|
|
|
<MagicInput
|
|
|
|
value={thing}
|
|
|
|
placeholder="Imagine.."
|
|
|
|
onValueChange={updateValue}
|
|
|
|
></MagicInput>
|
|
|
|
{/* <Box
|
2023-08-03 10:56:42 +00:00
|
|
|
ref={contentEditableRef}
|
|
|
|
width="100%"
|
|
|
|
border="none"
|
|
|
|
outline="none"
|
|
|
|
contentEditable={true}
|
2023-08-03 13:48:56 +00:00
|
|
|
dangerouslySetInnerHTML={{ __html: contentEditableThing }}
|
2023-08-03 10:56:42 +00:00
|
|
|
onInput={(value) => {
|
|
|
|
const innerText = value?.target?.innerText
|
|
|
|
if (typeof innerText === "string") {
|
2023-08-03 13:48:56 +00:00
|
|
|
const time = Date.now()
|
|
|
|
editValueRef.current[time] = innerText
|
2023-08-03 10:56:42 +00:00
|
|
|
updateValue({ value: innerText })
|
|
|
|
}
|
|
|
|
}}
|
2023-08-13 05:53:47 +00:00
|
|
|
></Box> */}
|
2023-08-03 10:56:42 +00:00
|
|
|
</AtomicWrapper>
|
|
|
|
)
|
|
|
|
}
|
2023-08-12 12:22:52 +00:00
|
|
|
if (type === "undefined") {
|
|
|
|
return (
|
2023-08-13 05:53:47 +00:00
|
|
|
<AtomicWrapper paddingLeft={pl}>
|
2023-08-12 12:22:52 +00:00
|
|
|
{/* TODO: Implement UI-less commander */}
|
2023-08-13 05:53:47 +00:00
|
|
|
<Commander
|
|
|
|
// rainbow
|
|
|
|
id={uuid}
|
|
|
|
pathPrefix={fullPath}
|
|
|
|
placeholder="Imagine.."
|
|
|
|
// onValueChange={updateValue}
|
|
|
|
></Commander>
|
2023-08-12 12:22:52 +00:00
|
|
|
</AtomicWrapper>
|
|
|
|
)
|
|
|
|
}
|
2023-06-29 11:06:58 +00:00
|
|
|
}
|
2023-08-07 22:05:07 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<AtomicWrapper paddingLeft={pl} className="default-atomic-wrapper">
|
|
|
|
{renderableValue}
|
|
|
|
</AtomicWrapper>
|
|
|
|
)
|
2023-08-03 10:56:42 +00:00
|
|
|
}, [
|
2023-08-07 22:05:07 +00:00
|
|
|
renderableValue,
|
|
|
|
pl,
|
2023-08-03 10:56:42 +00:00
|
|
|
type,
|
2023-08-12 14:06:33 +00:00
|
|
|
fullPath,
|
|
|
|
uuid,
|
2023-08-09 02:19:57 +00:00
|
|
|
AtomicWrapper,
|
2023-08-03 10:56:42 +00:00
|
|
|
props?.edit,
|
|
|
|
thing,
|
2023-08-09 02:19:57 +00:00
|
|
|
thingDep,
|
2023-08-03 10:56:42 +00:00
|
|
|
updateValue,
|
|
|
|
])
|
2023-06-29 11:06:58 +00:00
|
|
|
|
|
|
|
const contextMenu = (
|
2023-07-07 07:42:14 +00:00
|
|
|
<Flex
|
|
|
|
position="absolute"
|
|
|
|
top={0}
|
|
|
|
right={0}
|
|
|
|
paddingRight={4}
|
|
|
|
userSelect="none"
|
|
|
|
>
|
2023-06-29 11:06:58 +00:00
|
|
|
Settings
|
|
|
|
</Flex>
|
|
|
|
)
|
|
|
|
|
|
|
|
const [showContextMenu, setShowContextMenu] = React.useState(false)
|
|
|
|
|
2023-07-01 15:05:55 +00:00
|
|
|
const humanPath = React.useMemo(() => {
|
2023-07-07 07:42:14 +00:00
|
|
|
if (typeof props?.path === "string") {
|
2023-07-01 15:05:55 +00:00
|
|
|
return props?.path
|
|
|
|
}
|
2023-07-07 07:42:14 +00:00
|
|
|
return props?.path?.human || ""
|
2023-07-01 15:05:55 +00:00
|
|
|
}, [props?.path])
|
|
|
|
|
2023-07-10 00:49:30 +00:00
|
|
|
const renderedPath = React.useMemo(() => {
|
2023-07-21 13:33:47 +00:00
|
|
|
if (props?.edit) {
|
|
|
|
return humanPath
|
|
|
|
}
|
|
|
|
|
2023-07-07 07:42:14 +00:00
|
|
|
if (humanPath?.includes?.("hidden")) {
|
2023-07-01 14:22:04 +00:00
|
|
|
return null
|
|
|
|
}
|
2023-07-07 07:42:14 +00:00
|
|
|
if (humanPath?.includes?.("unique")) {
|
2023-07-01 14:39:02 +00:00
|
|
|
// take only path from before the string unique
|
2023-07-07 07:42:14 +00:00
|
|
|
return humanPath.split?.("unique")?.[0]
|
2023-07-01 14:39:02 +00:00
|
|
|
}
|
2023-07-10 00:49:30 +00:00
|
|
|
|
|
|
|
return humanPath
|
2023-07-21 13:33:47 +00:00
|
|
|
}, [humanPath, props?.edit])
|
2023-07-10 00:49:30 +00:00
|
|
|
|
2023-08-17 14:21:38 +00:00
|
|
|
const updatePath = React.useCallback(
|
|
|
|
(args) => {
|
|
|
|
if (typeof args?.value === "string") {
|
|
|
|
try {
|
|
|
|
const parentKeys = Object.keys(parent)
|
|
|
|
// create new object with new key order
|
|
|
|
const newObject = {}
|
|
|
|
|
|
|
|
parentKeys.forEach((key) => {
|
|
|
|
if (key === path) {
|
|
|
|
newObject[args.value] = parent[key]
|
|
|
|
return
|
|
|
|
}
|
|
|
|
newObject[key] = parent[key]
|
|
|
|
})
|
|
|
|
// set new object
|
|
|
|
setThingtime(parentPath, newObject)
|
|
|
|
|
|
|
|
// focus next input
|
|
|
|
const focusableNodeList = thingtimeRef?.current?.querySelectorAll?.(
|
|
|
|
".magic-input-focusable"
|
|
|
|
)
|
|
|
|
|
|
|
|
// convert focusable to array
|
|
|
|
const focusable = Array.from(focusableNodeList)
|
|
|
|
|
|
|
|
const pathMagicInputFocusable = pathRef?.current?.querySelector?.(
|
|
|
|
".magic-input-focusable"
|
|
|
|
)
|
|
|
|
|
|
|
|
const nearestMagicInput = focusable?.find((input) => {
|
|
|
|
if (input !== pathMagicInputFocusable) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
|
|
|
|
if (nearestMagicInput && nearestMagicInput?.focus) {
|
|
|
|
nearestMagicInput.focus()
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.error(
|
|
|
|
"Thingtime:657 Something went wrong reassigning path",
|
|
|
|
err
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[parent, path, parentPath, setThingtime]
|
|
|
|
)
|
|
|
|
|
|
|
|
const pathRef = React.useRef(null)
|
|
|
|
|
2023-07-21 13:33:47 +00:00
|
|
|
const pathDom = React.useMemo(() => {
|
2023-08-15 23:10:13 +00:00
|
|
|
if (chakras) {
|
|
|
|
return <></>
|
|
|
|
}
|
|
|
|
|
2023-07-21 13:33:47 +00:00
|
|
|
if (renderedPath) {
|
|
|
|
return (
|
2023-08-14 03:13:23 +00:00
|
|
|
<>
|
|
|
|
<MagicInput
|
2023-08-17 14:21:38 +00:00
|
|
|
ref={pathRef}
|
2023-08-14 03:13:23 +00:00
|
|
|
value={renderedPath}
|
|
|
|
readonly={!props?.edit}
|
2023-08-17 14:21:38 +00:00
|
|
|
onEnter={updatePath}
|
2023-08-14 03:13:23 +00:00
|
|
|
chakras={{
|
|
|
|
maxWidth: "100%",
|
|
|
|
paddingLeft: props?.pathPl || pl,
|
|
|
|
fontSize: "12px",
|
|
|
|
wordBreak: "break-all",
|
|
|
|
}}
|
|
|
|
></MagicInput>
|
|
|
|
</>
|
2023-07-21 13:33:47 +00:00
|
|
|
)
|
|
|
|
}
|
2023-08-15 23:10:13 +00:00
|
|
|
}, [renderedPath, pl, chakras, props?.edit, props?.pathPl])
|
2023-06-29 11:39:40 +00:00
|
|
|
|
|
|
|
const handleMouseEvent = React.useCallback(
|
2023-07-07 07:42:14 +00:00
|
|
|
(e) => {
|
2023-06-29 11:39:40 +00:00
|
|
|
const target = e?.target
|
2023-06-29 23:41:06 +00:00
|
|
|
// extract uuid from className
|
2023-06-29 11:39:40 +00:00
|
|
|
const className = target?.className
|
2023-06-29 23:41:06 +00:00
|
|
|
if (className?.includes(uuid?.current)) {
|
2023-07-07 07:42:14 +00:00
|
|
|
setShowContextMenu(e?.type === "mouseenter")
|
2023-06-29 11:39:40 +00:00
|
|
|
}
|
|
|
|
},
|
2023-06-29 23:41:06 +00:00
|
|
|
[uuid]
|
2023-06-29 11:39:40 +00:00
|
|
|
)
|
|
|
|
|
2023-08-12 12:22:52 +00:00
|
|
|
const addNewChild = React.useCallback(() => {
|
|
|
|
const newChild = null
|
|
|
|
|
|
|
|
if (thing instanceof Array) {
|
|
|
|
// add new child to array
|
|
|
|
const newValue = [...thing, newChild]
|
|
|
|
setThingtime(fullPath, newValue)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const newChildBasePath = "New Value"
|
|
|
|
// find increment that thing doesn't already have New Value N+1
|
|
|
|
let increment = 0
|
|
|
|
let newPath = newChildBasePath
|
|
|
|
while (Object.hasOwnProperty.call(thing, newPath) && increment <= 999) {
|
|
|
|
increment++
|
|
|
|
newPath = newChildBasePath + " " + increment
|
|
|
|
}
|
|
|
|
const newChildPath = newPath
|
|
|
|
const newChildFullPath = fullPath + "." + newChildPath
|
|
|
|
// create new child on thing using setThingtime
|
|
|
|
setThingtime(newChildFullPath, newChild)
|
|
|
|
}, [fullPath, setThingtime, thing])
|
|
|
|
|
2023-07-21 13:33:47 +00:00
|
|
|
const [showContextIcon, setShowContextIcon] = React.useState(false)
|
|
|
|
|
2023-08-17 10:41:40 +00:00
|
|
|
if (props?.chakraChildren) {
|
|
|
|
return thingtimeChildren
|
|
|
|
}
|
2023-08-15 23:10:13 +00:00
|
|
|
|
2023-06-29 23:41:06 +00:00
|
|
|
return (
|
2023-08-09 03:17:19 +00:00
|
|
|
<Safe {...props} depth={depth} uuid={uuid}>
|
2023-06-29 23:41:06 +00:00
|
|
|
<Flex
|
2023-08-17 14:21:38 +00:00
|
|
|
ref={thingtimeRef}
|
2023-07-07 07:42:14 +00:00
|
|
|
position="relative"
|
2023-07-10 00:37:50 +00:00
|
|
|
// width="500px"
|
2023-08-17 14:21:38 +00:00
|
|
|
flexDirection="column"
|
2023-08-13 05:53:47 +00:00
|
|
|
rowGap={2}
|
2023-08-15 23:10:13 +00:00
|
|
|
width={width}
|
2023-07-07 07:42:14 +00:00
|
|
|
maxWidth="100%"
|
2023-08-13 05:53:47 +00:00
|
|
|
// marginTop={3}
|
2023-07-07 07:42:14 +00:00
|
|
|
paddingRight={pr}
|
2023-08-13 05:53:47 +00:00
|
|
|
// minW={depth === 1 ? '120px' : null}
|
2023-06-29 23:41:06 +00:00
|
|
|
onMouseEnter={handleMouseEvent}
|
|
|
|
onMouseLeave={handleMouseEvent}
|
2023-07-15 08:08:36 +00:00
|
|
|
{...(props.chakras || {})}
|
2023-08-15 23:10:13 +00:00
|
|
|
className={`thing uuid-${uuid} edit-${props?.edit ? "true" : "false"}`}
|
2023-08-07 22:05:07 +00:00
|
|
|
data-path={props?.path}
|
2023-06-29 23:41:06 +00:00
|
|
|
>
|
|
|
|
{/* {uuid?.current} */}
|
2023-08-17 10:41:40 +00:00
|
|
|
{!chakras && !chakra && (
|
2023-08-15 23:10:13 +00:00
|
|
|
<Flex position="relative" flexDirection="row">
|
|
|
|
<Flex
|
|
|
|
alignItems="center"
|
|
|
|
flexDirection="row"
|
|
|
|
marginRight="auto"
|
|
|
|
onMouseEnter={() => setShowContextIcon(true)}
|
|
|
|
onMouseLeave={() => setShowContextIcon(false)}
|
|
|
|
>
|
|
|
|
<Flex>{pathDom}</Flex>
|
|
|
|
{props?.edit && (
|
|
|
|
<Box
|
|
|
|
// marginTop={-3}
|
|
|
|
marginTop={-1}
|
|
|
|
paddingLeft={1}
|
|
|
|
opacity={0.5}
|
|
|
|
cursor="pointer"
|
|
|
|
>
|
|
|
|
{typeIcon}
|
|
|
|
</Box>
|
|
|
|
)}
|
|
|
|
{pathDom && (
|
|
|
|
<Flex
|
|
|
|
flexDirection="row"
|
|
|
|
columnGap={1}
|
|
|
|
marginTop={-1}
|
|
|
|
paddingLeft={1}
|
|
|
|
>
|
|
|
|
<SettingsMenu
|
|
|
|
transition="all 0.2s ease-in-out"
|
|
|
|
opacity={showContextIcon ? 1 : 0}
|
|
|
|
uuid={uuid}
|
|
|
|
fullPath={fullPath}
|
|
|
|
readonly={!props?.edit}
|
|
|
|
onChangeType={onChangeType}
|
|
|
|
onDelete={deleteValue}
|
|
|
|
></SettingsMenu>
|
|
|
|
</Flex>
|
|
|
|
)}
|
|
|
|
</Flex>
|
2023-07-21 01:13:02 +00:00
|
|
|
</Flex>
|
2023-08-15 23:10:13 +00:00
|
|
|
)}
|
2023-07-01 14:13:27 +00:00
|
|
|
{/* {showContextMenu && contextMenu} */}
|
2023-08-07 22:05:07 +00:00
|
|
|
{!loading && !thingtimeChildren && atomicValue && (
|
2023-08-15 23:10:13 +00:00
|
|
|
<Box className="atomicValue" width={render ? "auto" : ""}>
|
|
|
|
{atomicValue}
|
|
|
|
</Box>
|
2023-08-07 22:05:07 +00:00
|
|
|
)}
|
|
|
|
{!loading && thingtimeChildren && (
|
2023-08-15 23:10:13 +00:00
|
|
|
<Box
|
|
|
|
className="thingtimeChildren"
|
|
|
|
flexGrow={0}
|
|
|
|
flexShrink={1}
|
|
|
|
width={render ? "auto" : ""}
|
|
|
|
>
|
2023-08-13 05:53:47 +00:00
|
|
|
{thingtimeChildren}
|
2023-08-15 23:10:13 +00:00
|
|
|
{!render && type === "object" && (
|
2023-08-14 03:06:26 +00:00
|
|
|
<Flex
|
|
|
|
width="100%"
|
|
|
|
paddingLeft={multiplyPl(2)}
|
|
|
|
opacity={props?.edit ? 1 : 0}
|
|
|
|
cursor="pointer"
|
|
|
|
transition="all 0.2s ease-out"
|
|
|
|
onClick={addNewChild}
|
|
|
|
paddingY={2}
|
|
|
|
>
|
2023-08-14 10:32:14 +00:00
|
|
|
<Icon
|
|
|
|
_focus={{
|
|
|
|
outline: "none !important",
|
|
|
|
textShadow: "0px 0px 10px green",
|
|
|
|
}}
|
|
|
|
onKeyDown={(e) => {
|
|
|
|
if (e?.key === "Enter") {
|
|
|
|
addNewChild()
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
tabIndex={0}
|
|
|
|
size={10}
|
|
|
|
name="seedling"
|
|
|
|
></Icon>
|
2023-08-14 03:06:26 +00:00
|
|
|
{/* <Icon size={7} name="plus"></Icon>
|
|
|
|
<Icon size={7} name="plus"></Icon> */}
|
|
|
|
</Flex>
|
|
|
|
)}
|
2023-08-13 05:53:47 +00:00
|
|
|
</Box>
|
2023-08-07 22:05:07 +00:00
|
|
|
)}
|
2023-06-29 23:41:06 +00:00
|
|
|
</Flex>
|
|
|
|
</Safe>
|
2023-06-29 11:06:58 +00:00
|
|
|
)
|
|
|
|
}
|