feat: feature/mvp-sprint-1

This commit is contained in:
Nikolaj Frey 2023-07-21 23:33:47 +10:00
parent e701e91e96
commit d16b022807
13 changed files with 359 additions and 94 deletions

View File

@ -37,6 +37,8 @@ module.exports = {
"error",
{ props: "never", children: "never" },
],
"direct-eval/direct-eval": "off",
"no-eval": "off",
"no-async-promise-executor": "off",
"react/prop-types": "off",
"react/display-name": "off",

View File

@ -1,5 +1,6 @@
import { extendTheme } from '@chakra-ui/react'
import { colors } from './colors'
import { extendTheme, Select, Switch } from "@chakra-ui/react"
import { colors } from "./colors"
export const theme = extendTheme({
colors,
@ -7,51 +8,100 @@ export const theme = extendTheme({
styles: {
global: {
// make all elements padding and margin animate
'*': {
transition: 'padding 0.2s ease, margin 0.2s ease'
"*": {
transition: "padding 0.2s ease, margin 0.2s ease",
},
// make all elements have a transparent focus border
'input:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
"input:focus": {
boxShadow: "none !important",
borderColor: "transparent !important",
},
// make all elements have a transparent focus border
'textarea:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
"textarea:focus": {
boxShadow: "none !important",
borderColor: "transparent !important",
},
// make all elements have a transparent focus border
'select:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
"select:focus": {
boxShadow: "none !important",
borderColor: "transparent !important",
},
// make all elements have a transparent focus border
'button:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
"button:focus": {
boxShadow: "none !important",
borderColor: "transparent !important",
},
// make all elements have a transparent focus border
'div:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
"div:focus": {
boxShadow: "none !important",
borderColor: "transparent !important",
},
// make all elements have a transparent focus border
'a:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
"a:focus": {
boxShadow: "none !important",
borderColor: "transparent !important",
},
// make all elements have a transparent focus border
'span:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
}
}
"span:focus": {
boxShadow: "none !important",
borderColor: "transparent !important",
},
},
},
components: {
Input: {
defaultProps: {
focusBorderColor: 'transparent'
}
}
}
// focusBorderColor: "transparent",
// outline: "0px solid",
// border: "0px solid",
},
baseStyle: {
// "padding-inline-start": "0px",
field: {
// "padding-inline-start": "0px",
},
},
},
Select: {
baseStyle: {
field: {
// "padding-inline-start": "0px",
},
icon: {
// height: "8px",
width: "14px",
},
},
},
Switch: {
baseStyle: {
track: {
background: "grays.medium",
_checked: {
background: "chakras.throat",
},
},
},
},
},
})
console.log("nik Select", Select)
Switch.defaultProps = {
...Switch.defaultProps,
as: "div",
}
Select.defaultProps = {
...Select.defaultProps,
focusBorderColor: "transparent",
outline: "0px solid",
border: "none",
ps: "0px",
px: "0px",
sx: {
paddingInlineStart: "0px",
paddingInlineEnd: "24px",
},
}

View File

@ -105,10 +105,15 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
...prevThingtime,
}
newThingtime.tt = newThingtime
newThingtime.thingtime = newThingtime
// check if first characters of path starts with thingtime or tt and strip from path
// path = sanitise(path)
console.log("nik setting newThingtime value at path", path, value)
smarts.setsmart(newThingtime, path, value)
// subtract last path part from dot delimitted path
@ -118,6 +123,7 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
const parentPath = pathParts.join(".")
if (parentPath?.length) {
console.log("nik updating parentPath dependancies", parentPath)
const parent = smarts.getsmart(newThingtime, parentPath)
const newParent = Array.isArray(parent) ? [...parent] : { ...parent }
@ -127,7 +133,7 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
set(newThingtime)
},
[thingtime]
[thingtime, set]
)
const getThingtime = React.useCallback(
@ -137,6 +143,7 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
// do we need to sanitise?
// const path = sanitise(rawPath)
console.log("Getting thingtime at path", path)
// console.trace("Getting thingtime at path", path)
return smarts.getsmart(thingtime, path)
},
[thingtime]
@ -221,10 +228,10 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
} else {
try {
console.log("Setting thingtime to localStorage", thingtime)
setTimeout(() => {
const stringified = stringify(thingtime)
window.localStorage.setItem("thingtime", stringified)
}, 600)
// setTimeout(() => {
const stringified = stringify(thingtime)
window.localStorage.setItem("thingtime", stringified)
// }, 600)
} catch (err) {
console.error("There was an error saving thingtime to localStorage")
}

View File

@ -197,11 +197,13 @@ export const Commander = (props) => {
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)
if (!e?.defaultPrevented) {
console.log("nik 123123 commander event closeCommander ", 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]
@ -267,18 +269,46 @@ export const Commander = (props) => {
if (commandIsAction) {
// nothing
try {
const fn = `() => { return ${escapedCommandValue} }`
// first try to execute literal javscript
const fn = `() => { return ${commandValue} }`
const evalFn = eval(fn)
const realVal = evalFn()
const prevVal = getThingtime(commandPath)
const parentPath = getParentPath(commandPath)
console.log("nik realVal", realVal)
console.log("nik prevVal", prevVal)
console.log("nik parentPath", parentPath)
console.log("nik commandPath", commandPath)
setThingtime(commandPath, realVal)
if (!prevVal) {
setContextPath(parentPath)
setShowContext(true, "commandIsAction check")
}
} catch (err) {
console.log("setThingtime errored in Commander", 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)
if (!prevVal) {
setContextPath(parentPath)
setShowContext(true, "commandIsAction check")
}
} catch {
// something very bad went wrong
console.log(
"Caught error after trying to execute escaped literal javascript",
err
)
}
}
} else if (commandContainsPath) {
// const prevValue = getThingtime(commandPath)
@ -305,6 +335,7 @@ export const Commander = (props) => {
commandIsAction,
commandContainsPath,
commandPath,
commandValue,
escapedCommandValue,
getThingtime,
setThingtime,

View File

@ -1,22 +1,59 @@
import React from "react"
import { Center } from "@chakra-ui/react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
export const Icon = (props) => {
const icon = React.useMemo(() => {
if (props?.name === "gear") {
return {
prefix: "fas",
iconName: props?.name,
}
}
const name = props?.name
return props?.name
}, [props?.name])
const icon = React.useMemo(() => {
// nothing
if (["gear", "cog"]?.includes(name)) {
return "⚙️"
}
if (["crystal"]?.includes(name)) {
return "🔮"
}
if (["sparke", "magic"]?.includes(name)) {
return "✨"
}
if (["box", "thing", "object"]?.includes(name)) {
return "📦"
}
if (["book", "books"]?.includes(name)) {
return "📚"
}
if (["book-open", "books-open"]?.includes(name)) {
return "📖"
}
if (["book-reader", "books-reader"]?.includes(name)) {
return "👩‍🏫"
}
if (["number", "hundred"]?.includes(name)) {
return "💯"
}
if (["heart"]?.includes(name)) {
return "❤️"
}
if (["heart-broken"]?.includes(name)) {
return "💔"
}
if (["heart-pulse"]?.includes(name)) {
return "💗"
}
if (["string", "text"]?.includes(name)) {
return "💬"
}
if (["array", "list"]?.includes(name)) {
return "📚"
}
if (["boolean", "bool"]?.includes(name)) {
return "🌗"
// return "⚖️"
}
}, [name])
return (
<Center {...props?.chakras}>
<FontAwesomeIcon icon={icon}></FontAwesomeIcon>
<Center {...props?.chakras} fontSize={props?.size}>
{icon}
</Center>
)
}

View File

@ -1,5 +1,5 @@
import React from "react"
import { Box, Flex } from "@chakra-ui/react"
import { Box, Flex, Select, Switch } from "@chakra-ui/react"
import { Icon } from "../Icon/Icon"
import { Safe } from "../Safety/Safe"
@ -10,7 +10,7 @@ export const Thingtime = (props) => {
// and add button to expand circular reference
// up to 1 level deep
const { thingtime } = useThingtime()
const { thingtime, setThingtime } = useThingtime()
const [uuid, setUuid] = React.useState()
@ -73,6 +73,23 @@ export const Thingtime = (props) => {
return typeof thing
}, [thing])
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>
} else {
return <Icon name="box" size={size}></Icon>
}
}, [type, thing])
const valuePl = React.useMemo(() => {
if (typeof props?.valuePl === "number") {
return props?.valuePl
@ -124,7 +141,7 @@ export const Thingtime = (props) => {
return ["view", "edit"]
}, [])
const value = React.useMemo(() => {
const thingtimeChildren = React.useMemo(() => {
if (template1Modes?.includes(mode)) {
if (keys?.length && !circular) {
return (
@ -155,13 +172,17 @@ export const Thingtime = (props) => {
nextSeen.push(nextThing)
}
const fullPath = props?.fullPath || props?.path
return (
<Thingtime
key={idx}
seen={nextSeen}
edit={props?.edit}
circular={seen?.includes?.(nextThing)}
depth={depth + 1}
parent={thing}
fullPath={fullPath + "." + key?.key}
path={key}
thing={nextThing}
// thing={{ infinite: { yes: true } }}
@ -188,24 +209,74 @@ export const Thingtime = (props) => {
template1Modes,
])
const editableValue = React.useMemo(() => {
if (template1Modes?.includes(mode)) {
const AtomicWrapper = React.useCallback(
(props) => {
return (
<Box
<Flex
flexDirection="row"
paddingLeft={pl}
fontSize="20px"
border="none"
whiteSpace="pre-line"
outline="none"
contentEditable={mode === "edit"}
paddingY={2}
// dangerouslySetInnerHTML={{ __html: renderableValue }}
>
{renderableValue}
</Box>
{props?.children}
</Flex>
)
},
[pl]
)
const updateValue = React.useCallback(
(args) => {
const { value } = args
console.log("nik value", value)
console.log("nik props?.fullPath", props?.fullPath)
setThingtime(props?.fullPath, value)
},
[props?.fullPath, setThingtime]
)
const atomicValue = React.useMemo(() => {
if (props?.edit) {
if (type === "boolean") {
return (
<AtomicWrapper>
<Box
onClick={(e) => {
e?.preventDefault?.()
e?.stopPropagation?.()
// cancel bubble
e?.nativeEvent?.stopImmediatePropagation?.()
console.log("nik 123123 clicked", !thing)
setTimeout(() => {
updateValue({ value: !thing })
console.log("nik 123123 changed", e)
}, 1)
}}
>
<Switch colorScheme="red" isChecked={thing}></Switch>
</Box>
{/* <Select
width="auto"
marginRight="auto"
paddingStart={0}
paddingX={0}
>
{renderableValue}
<option value="true">true</option>
<option value="false">false</option>
</Select> */}
</AtomicWrapper>
)
}
}
}, [renderableValue, mode, template1Modes, pl])
return <AtomicWrapper>{renderableValue}</AtomicWrapper>
}, [renderableValue, AtomicWrapper, type, props?.edit, thing, updateValue])
const contextMenu = (
<Flex
@ -229,6 +300,10 @@ export const Thingtime = (props) => {
}, [props?.path])
const renderedPath = React.useMemo(() => {
if (props?.edit) {
return humanPath
}
if (humanPath?.includes?.("hidden")) {
return null
}
@ -238,19 +313,21 @@ export const Thingtime = (props) => {
}
return humanPath
}, [humanPath])
}, [humanPath, props?.edit])
const path = React.useMemo(() => {
return (
<Flex
maxWidth="100%"
paddingLeft={props?.pathPl || pl}
fontSize="12px"
wordBreak="break-all"
>
{renderedPath}
</Flex>
)
const pathDom = React.useMemo(() => {
if (renderedPath) {
return (
<Flex
maxWidth="100%"
paddingLeft={props?.pathPl || pl}
fontSize="12px"
wordBreak="break-all"
>
{renderedPath}
</Flex>
)
}
}, [renderedPath, pl, props?.pathPl])
const handleMouseEvent = React.useCallback(
@ -265,6 +342,8 @@ export const Thingtime = (props) => {
[uuid]
)
const [showContextIcon, setShowContextIcon] = React.useState(false)
return (
<Safe {...props} depth={depth} uuid={uuid?.current}>
<Flex
@ -283,14 +362,39 @@ export const Thingtime = (props) => {
>
{/* {uuid?.current} */}
<Flex position="relative" flexDirection="row">
{path}
<Flex position="absolute" top={0} right={0}>
<Icon name="gear"></Icon>
<Flex
alignItems="center"
flexDirection="row"
marginRight="auto"
onMouseEnter={() => setShowContextIcon(true)}
onMouseLeave={() => setShowContextIcon(false)}
>
<Flex>{pathDom}</Flex>
{props?.edit && (
<Box
marginTop={-3}
paddingLeft={1}
opacity={0.5}
cursor="pointer"
>
{typeIcon}
</Box>
)}
{pathDom && (
<Flex
paddingLeft={1}
opacity={showContextIcon ? 1 : 0}
cursor="pointer"
transition="all 0.2s ease-in-out"
>
<Icon name="magic" size={10}></Icon>
</Flex>
)}
</Flex>
</Flex>
{/* {showContextMenu && contextMenu} */}
{!value && editableValue}
{value}
{!thingtimeChildren && atomicValue}
{thingtimeChildren}
</Flex>
</Safe>
)

View File

@ -1,15 +1,11 @@
import React from "react"
import { Box, Flex } from "@chakra-ui/react"
import { Flex } from "@chakra-ui/react"
import { useMatches } from "@remix-run/react"
import { ProfileDrawer } from "~/components/Nav/ProfileDrawer"
import { Splash } from "~/components/Splash/Splash"
import { Thingtime } from "~/components/Thingtime/Thingtime"
import { ThingtimeDemo } from "~/components/Thingtime/ThingtimeDemo"
import { useThingtime } from "~/components/Thingtime/useThingtime"
import { GradientPath } from "~/gp/GradientPath"
import { Thingtime } from "./Thingtime"
import { useThingtime } from "./useThingtime"
export default function Index() {
export const ThingtimeURL = (props) => {
const { getThingtime } = useThingtime()
const matches = useMatches()
@ -18,11 +14,12 @@ export default function Index() {
}, [matches])
const path = React.useMemo(() => {
const pathStepOne = location?.pathname?.replace("/things/", "")
const pathPartOne = location?.params?.["*"]
const path = pathPartOne?.replace(/\//g, ".")
const path = pathStepOne?.replace(/\//g, ".")
return path
}, [location?.pathname])
}, [location])
const thing = React.useMemo(() => {
// remove /things/ from path
@ -40,12 +37,12 @@ export default function Index() {
maxWidth="100%"
>
<Thingtime
edit={props?.edit}
path={path}
thing={thing}
chakras={{ marginY: 200 }}
width="600px"
></Thingtime>
<ProfileDrawer></ProfileDrawer>
</Flex>
)
}

View File

@ -0,0 +1,8 @@
import { library } from "@fortawesome/fontawesome-svg-core"
import { fas } from "@fortawesome/free-solid-svg-icons"
library.add(fas)
export const useIcons = () => {
return null
}

View File

@ -10,6 +10,7 @@ import {
import { Analytics } from "@vercel/analytics/react"
import { Main } from "./components/Layout/Main"
import { useIcons } from "./hooks/useIcons"
import { ChakraWrapper } from "./Providers/Chakra/ChakraWrapper"
import { ThingtimeProvider } from "./Providers/ThingtimeProvider"
@ -41,6 +42,8 @@ function Document({
}
export default function App() {
useIcons()
return (
<Document>
<ChakraWrapper>

View File

@ -0,0 +1,7 @@
import React from "react"
import { ThingtimeURL } from "~/components/Thingtime/ThingtimeURL"
export default function Index() {
return <ThingtimeURL edit></ThingtimeURL>
}

View File

@ -0,0 +1,7 @@
import React from "react"
import { ThingtimeURL } from "~/components/Thingtime/ThingtimeURL"
export default function Index() {
return <ThingtimeURL></ThingtimeURL>
}

View File

@ -26,6 +26,7 @@
"react": "^18.2.0",
"react-click-away-listener": "^2.2.3",
"react-dom": "^18.2.0",
"react-icons": "^4.10.1",
"tinygradient": "^1.1.5",
"uuid": "^9.0.0"
},

View File

@ -59,6 +59,9 @@ dependencies:
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
react-icons:
specifier: ^4.10.1
version: 4.10.1(react@18.2.0)
tinygradient:
specifier: ^1.1.5
version: 1.1.5
@ -8566,6 +8569,14 @@ packages:
use-sidecar: 1.1.2(@types/react@18.0.28)(react@18.2.0)
dev: false
/react-icons@4.10.1(react@18.2.0):
resolution: {integrity: sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw==}
peerDependencies:
react: '*'
dependencies:
react: 18.2.0
dev: false
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}