feat: main Added some very cool functionality, mainly the ctrl/cmd+p to modify thingtime state, also added new demo data and demo UGC block section so users can essentially edit the website locally for themselves.

This commit is contained in:
Nikolaj Frey 2023-07-02 00:13:27 +10:00
parent cfa05adc94
commit 34cd118437
10 changed files with 430 additions and 116 deletions

View File

@ -4,6 +4,49 @@ import { colors } from './colors'
export const theme = extendTheme({
colors,
// edit Input defaultProps
styles: {
global: {
// make all elements padding and margin animate
'*': {
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'
},
// make all elements have a transparent focus border
'textarea:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
},
// make all elements have a transparent focus border
'select:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
},
// make all elements have a transparent focus border
'button:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
},
// make all elements have a transparent focus border
'div:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
},
// make all elements have a transparent focus border
'a:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
},
// make all elements have a transparent focus border
'span:focus': {
boxShadow: 'none !important',
borderColor: 'transparent !important'
}
}
},
components: {
Input: {
defaultProps: {

View File

@ -19,15 +19,63 @@ try {
const initialThingtime = {
nav: {},
version: 4
}
const userData = {
settings: {
showCommander: true,
clearCommanderOnToggle: true,
clearCommanderContextOnToggle: true
},
'Bottom Content': {
Content: "Edit this to your heart's desire"
}
}
export const ThingtimeProvider = (props: any): JSX.Element => {
const [thingtime, set] = React.useState(initialThingtime)
const [thingtime, set] = React.useState({
...initialThingtime,
...userData
})
const thingtimeRef = React.useRef(thingtime)
// get thingtime from localstorage
React.useEffect(() => {
try {
const thingtimeFromLocalStorage = window.localStorage.getItem('thingtime')
if (thingtimeFromLocalStorage) {
const parsed = JSON.parse(thingtimeFromLocalStorage)
if (parsed) {
const invalidLocalStorage =
!parsed.version || parsed.version < initialThingtime.version
if (!invalidLocalStorage) {
set(parsed)
} else {
const newThingtime = {
...parsed,
...initialThingtime
}
set(newThingtime)
}
}
}
} catch (err) {
console.error('There was an error getting thingtime from localStorage')
}
}, [])
React.useEffect(() => {
thingtimeRef.current = thingtime
try {
window.localStorage.setItem('thingtime', JSON.stringify(thingtime))
} catch (err) {
console.error('There was an error saving thingtime to localStorage')
}
}, [thingtime])
const setThingtime = React.useCallback(
(path, value) => {
@ -41,12 +89,22 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
path = sanitise(path)
// log the path and value
console.log('nik ThingtimeProvider setThingtime path', path)
console.log('nik ThingtimeProvider setThingtime value', value)
smarts.setsmart(newThingtime, path, value)
// subtract last path part from dot delimitted path
// prop1.prop2.prop3 => prop1.prop2
const pathParts = path.split('.')
pathParts.pop()
const parentPath = pathParts.join('.')
if (parentPath?.length) {
const parent = smarts.getsmart(newThingtime, parentPath)
const newParent = Array.isArray(parent) ? [...parent] : { ...parent }
smarts.setsmart(newThingtime, parentPath, newParent)
}
set(newThingtime)
},
[thingtime]
@ -55,7 +113,7 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
const getThingtime = React.useCallback(
(...args) => {
const path = args[0]
if (path === 'thingtime' || path === 'tt' || !path) {
if (path === 'thingtime' || path === 'tt' || path === '.' || !path) {
return thingtime
}
return smarts.getsmart(thingtime, path)
@ -83,7 +141,8 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
const value = {
thingtime,
setThingtime,
getThingtime
getThingtime,
thingtimeRef
}
return (

View File

@ -5,97 +5,202 @@ import { Thingtime } from '../Thingtime/Thingtime'
import { sanitise } from '~/functions/path'
export const Commander = props => {
const { thingtime, setThingtime, getThingtime } = useThingtime()
const [contextPath, setContextPath] = React.useState()
const [showContext, setShowContext] = React.useState(false)
const contextValue = React.useMemo(() => {
console.log('thingtime updated!')
const ret = getThingtime(contextPath)
console.log('nik ret', ret)
return ret
}, [contextPath, getThingtime])
const showCommander = React.useMemo(() => {
console.log(
'nik thingtime?.settings?.showCommander',
thingtime?.settings?.showCommander
)
return thingtime?.settings?.showCommander
}, [thingtime?.settings?.showCommander])
React.useEffect(() => {
if (thingtime?.settings?.clearCommanderOnToggle) {
setValue('')
}
if (showCommander) {
inputRef?.current?.focus?.()
}
}, [showCommander, thingtime])
const { thingtime, setThingtime, getThingtime, thingtimeRef } = useThingtime()
const inputRef = React.useRef()
const [value, setValue] = React.useState('')
const [contextPath, setContextPath] = React.useState()
const [showContext, setShowContextState] = React.useState(false)
const setShowContext = React.useCallback(
(value, from) => {
setShowContextState(value)
},
[setShowContextState]
)
const [suggestions, setSuggestions] = React.useState([])
const contextValue = React.useMemo(() => {
console.log('thingtime updated!')
const ret = getThingtime(contextPath)
return ret
}, [contextPath, getThingtime])
const showCommander = React.useMemo(() => {
return thingtime?.settings?.showCommander
}, [thingtime?.settings?.showCommander])
// watch value
React.useEffect(() => {
if (!value?.length) {
setSuggestions([])
}
}, [value])
// showCommander useEffect
React.useEffect(() => {
if (showCommander) {
inputRef?.current?.focus?.()
} else {
if (thingtimeRef?.current?.settings?.clearCommanderOnToggle) {
setValue('')
}
if (thingtimeRef?.current?.settings?.clearCommanderContextOnToggle) {
setShowContext(false, 'showCommander useEffect')
}
}
}, [showCommander, thingtimeRef, setShowContext])
const onChange = React.useCallback(e => {
setValue(e.target.value)
}, [])
const validSetters = React.useMemo(() => {
return ['=', ' is ']
}, [])
const command = React.useMemo(() => {
const sanitizedCommand = sanitise(value)
if (sanitizedCommand?.includes(validSetters[0])) {
const indexOfSplitter = sanitizedCommand?.indexOf(validSetters[0])
const [pathRaw, valRaw] = [
sanitizedCommand?.slice(0, indexOfSplitter),
sanitizedCommand?.slice(indexOfSplitter + validSetters[0]?.length)
]
return [pathRaw?.trim(), valRaw?.trim()]
} else if (sanitizedCommand?.includes(validSetters[1])) {
const indexOfSplitter = sanitizedCommand?.indexOf(validSetters[1])
const [pathRaw, valRaw] = [
sanitizedCommand?.slice(0, indexOfSplitter),
sanitizedCommand?.slice(indexOfSplitter + validSetters[1]?.length)
]
return [pathRaw?.trim(), valRaw?.trim()]
}
return [sanitizedCommand]
}, [value, validSetters])
const commandContainsPath = React.useMemo(() => {
const suggestionsIncludesSubstring = suggestions?.find(suggestion => {
return command?.includes(suggestion)
})
return suggestionsIncludesSubstring
}, [suggestions, command])
const commandPath = React.useMemo(() => {
return command?.[0]
}, [command])
const commandValue = React.useMemo(() => {
return command?.[1]
}, [command])
const validQuotations = React.useMemo(() => {
return ['"', "'"]
}, [])
const escapedCommandValue = React.useMemo(() => {
// replace quotations with escaped quoations except for first and last quotation
const startingQuotation = commandValue?.[0]
const endingQuotation = commandValue?.[commandValue?.length - 1]
const isQuoted =
validQuotations?.includes(startingQuotation) &&
validQuotations?.includes(endingQuotation)
const restOfCommandValue = isQuoted
? commandValue?.slice(1, commandValue?.length - 1)
: commandValue
const escaped = restOfCommandValue
?.replace(/"/g, '\\"')
?.replace(/'/g, "\\'")
const ret = `"${escaped}"`
return ret
}, [commandValue, validQuotations])
const commandIsAction = React.useMemo(() => {
return commandPath && commandValue
}, [commandPath, commandValue])
// thingtime changes update suggestions
React.useEffect(() => {
// when thingtime changes, update suggestions
// with all flattened key path values recursively
if (value?.length) {
const suggestions = ['tt', 'thingtime', '.']
const recurse = (obj, path) => {
Object.keys(obj).forEach(key => {
const val = obj[key]
const newPath = path ? `${path}.${key}` : key
if (typeof val === 'object') {
suggestions.push(key)
recurse(val, newPath)
} else {
suggestions.push(newPath)
}
})
}
recurse(thingtime, '')
if (commandPath) {
const filteredSuggestions = suggestions.filter((suggestion, i) => {
return suggestion?.toLowerCase()?.includes(commandPath?.toLowerCase())
})
if (!filteredSuggestions?.includes(commandPath)) {
const adjustedSuggestions = [commandPath, ...filteredSuggestions]
setSuggestions(adjustedSuggestions)
} else {
setSuggestions(filteredSuggestions)
}
} else {
setSuggestions(suggestions)
}
// if (value) {
// setShowContext(true, 'Thingtime changes update suggestions')
// }
}
}, [thingtime, value, commandPath, setShowContext])
const onEnter = React.useCallback(
props => {
// if first characters of value equal tt. then run command
// or if first character is a dot then run command
try {
const isTT = value?.slice(0, 3) === 'tt.'
const isDot = value?.slice(0, 1) === '.'
const executeCommand = isTT || isDot
if (executeCommand) {
const command = isTT ? value?.slice(3) : value?.slice(1)
const sanitisedCommand = sanitise(command)
console.log('nik command', command)
console.log('setting to thingtime', thingtime)
const commandIsSetter = command?.includes('=')
if (commandIsSetter) {
// nothing
const [pathRaw, valRaw] = sanitisedCommand?.split('=')
const path = pathRaw.trim()
const val = valRaw.trim()
console.log('nik path', path)
console.log('nik val', val)
try {
const realVal = eval(val)
console.log('nik realVal', realVal)
setThingtime(path, realVal)
} catch (err) {
console.log('setThingtime errored in Commander', err)
}
// setContextPath(path)
} else {
const val = getThingtime(sanitisedCommand)
console.log('setting to val', val)
setContextPath(sanitisedCommand)
setShowContext(true)
if (commandIsAction) {
// nothing
try {
const fn = `() => { return ${escapedCommandValue} }`
const evalFn = eval(fn)
const realVal = evalFn()
setThingtime(commandPath, realVal)
} catch (err) {
console.log('setThingtime errored in Commander', err)
}
} else if (commandContainsPath) {
setContextPath(commandPath)
setShowContext(true, 'commandContainsPath check')
}
} catch (err) {
console.error('Caught error on commander onEnter', err)
}
},
[value, thingtime, getThingtime, setThingtime]
[
setShowContext,
escapedCommandValue,
setThingtime,
commandIsAction,
commandPath,
commandContainsPath
]
)
// trigger on enter
const onKeyDown = React.useCallback(
e => {
if (e.key === 'Enter') {
console.log('nik enter')
e.preventDefault()
e.stopPropagation()
onEnter({ e })
@ -105,7 +210,7 @@ export const Commander = props => {
// )
}
},
[thingtime?.settings?.showCommander, onEnter]
[onEnter]
)
const openCommander = React.useCallback(() => {
@ -116,7 +221,7 @@ export const Commander = props => {
setThingtime('settings.showCommander', false)
setShowContext(false)
setContextPath(undefined)
}, [setThingtime])
}, [setThingtime, setShowContext])
const toggleCommander = React.useCallback(() => {
if (thingtime?.settings?.showCommander) {
@ -129,7 +234,6 @@ export const Commander = props => {
React.useEffect(() => {
const keyListener = (e: any) => {
if (e?.metaKey && e?.code === 'KeyP') {
console.log('nik heard event')
e.preventDefault()
e.stopPropagation()
toggleCommander()
@ -145,44 +249,114 @@ export const Commander = props => {
return () => {
window.removeEventListener('keydown', keyListener)
}
}, [setThingtime, thingtime])
}, [setThingtime, thingtime, toggleCommander, closeCommander])
const selectSuggestion = React.useCallback(
suggestion => {
setValue(suggestion)
setContextPath(suggestion)
setShowContext(true, 'Select suggestion')
},
[setValue, setContextPath, setShowContext]
)
const excludedSuggestions = React.useMemo(() => {
return ['.']
}, [])
const renderedSuggestions = React.useMemo(() => {
return suggestions?.filter(suggestion => {
return !excludedSuggestions?.includes(suggestion)
})
}, [suggestions, excludedSuggestions])
const mobileVW = React.useMemo(() => {
return 'calc(100vw - 45px)'
}, [])
return (
<Center
<Flex
id='commander'
display={showCommander ? 'flex' : 'none'}
display={['flex', showCommander ? 'flex' : 'none']}
justifyContent={['flex-start', 'center']}
// zIndex={99999}
// position='fixed'
// top='100px'
pointerEvents={'none'}
position='absolute'
h='100%'
top={0}
left={0}
right={0}
py={1}
px={1}
maxW='100%'
>
<Center
display={showContext ? 'flex' : 'none'}
<Flex
alignItems={['flex-start', 'center']}
position='absolute'
top={'100%'}
maxH='90vh'
overflowY='scroll'
left={0}
right={0}
h='auto'
mt={2}
mx={1}
maxW='100%'
borderRadius={'12px'}
flexDir='column'
>
<Flex p={3} borderRadius={'12px'} bg='grey'>
<Flex
display={renderedSuggestions?.length ? 'flex' : 'none'}
w={['100%', '400px']}
maxW={'100%'}
bg='grey'
borderRadius={'12px'}
flexDir='column'
id='commander-suggestions'
py={3}
mb={3}
pointerEvents={'all'}
>
{renderedSuggestions.map((suggestion, i) => {
return (
<Flex
cursor='pointer'
px={4}
_hover={{
bg: 'greys.medium'
}}
key={i}
onClick={() => selectSuggestion(suggestion)}
>
{suggestion}
</Flex>
)
})}
</Flex>
<Flex
display={showContext ? 'flex' : 'none'}
maxW='100%'
py={3}
borderRadius={'12px'}
bg='grey'
pointerEvents={'all'}
>
<Thingtime thing={contextValue}></Thingtime>
</Flex>
</Center>
</Flex>
<Center
position='relative'
bg='grey'
w='400px'
w={['100%', '400px']}
maxW={[mobileVW, '100%']}
h='100%'
outline={'none'}
overflow='hidden'
p={'1px'}
borderRadius={'6px'}
pointerEvents={'all'}
>
<Box
position='absolute'
@ -218,6 +392,6 @@ export const Commander = props => {
placeholder={"What's on your mind?"}
></Input>
</Center>
</Center>
</Flex>
)
}

View File

@ -13,10 +13,18 @@ export const Nav = props => {
return (
<>
<Box position='fixed' top={0} left={0} right={0} zIndex={999}>
<Box
position='fixed'
maxW='100vw'
top={0}
left={0}
right={0}
zIndex={999}
>
<Flex
as='nav'
w='100%'
maxW='100%'
alignItems={'center'}
position='relative'
justifyContent='center'
@ -31,6 +39,7 @@ export const Nav = props => {
ml={'auto'}
w='25px'
h='25px'
cursor='pointer'
onClick={toggleProfileDrawer}
bg={'rgba(0,0,0,0.1)'}
sx={{}}

View File

@ -10,6 +10,10 @@ export const Thingtime = props => {
const [uuid, setUuid] = React.useState()
const pl = React.useMemo(() => {
return props?.pl || [4, 6]
}, [props?.pl])
// will only run on the client
React.useEffect(() => {
setUuid(Math.random().toString(36).substring(7))
@ -17,14 +21,6 @@ export const Thingtime = props => {
const { thingtime } = useThingtime()
React.useEffect(() => {
// console.log('nik thingtime?.test changed', thingtime?.test)
}, [thingtime?.test])
React.useEffect(() => {
// console.log('nik thingtime changed', thingtime)
}, [thingtime])
const depth = React.useMemo(() => {
return props?.depth || 1
}, [props?.depth])
@ -44,7 +40,6 @@ export const Thingtime = props => {
const keys = React.useMemo(() => {
if (validKeyTypes?.includes(typeof thing)) {
const keysRet = Object.keys(thing)
console.log('nik keysRet', keysRet)
return keysRet
} else {
return []
@ -57,12 +52,18 @@ export const Thingtime = props => {
const renderableValue = React.useMemo(() => {
if (type === 'string') {
if (!thing) {
return 'Empty string'
}
return thing
} else if (type === 'number') {
return thing
} else if (type === 'boolean') {
return thing ? 'true' : 'false'
} else if (type === 'object') {
if (thing === null) {
return 'null'
}
if (!keys?.length) {
return 'Empty object'
}
@ -78,7 +79,7 @@ export const Thingtime = props => {
return 'Circular reference in object.'
}
} else {
return null
return 'Undefined'
}
}, [thing, type])
@ -140,8 +141,7 @@ export const Thingtime = props => {
// w={['200px', '500px']}
maxW='100%'
py={props?.path ? 3 : 0}
pl={[4, 6]}
// pr={[4, 6]}
pl={props?.valuePl}
>
{keysToUse?.length &&
keysToUse.map((key, idx) => {
@ -162,6 +162,7 @@ export const Thingtime = props => {
path={key}
thing={nextThing}
// thing={{ infinite: { yes: true } }}
valuePl={pl}
></Thingtime>
)
})}
@ -175,6 +176,7 @@ export const Thingtime = props => {
border='none'
outline={'none'}
py={2}
pl={pl}
fontSize={'20px'}
>
{renderableValue}
@ -184,7 +186,7 @@ export const Thingtime = props => {
}
const contextMenu = (
<Flex userSelect={'none'} position='absolute' top={0} right={0}>
<Flex pr={4} userSelect={'none'} position='absolute' top={0} right={0}>
Settings
</Flex>
)
@ -193,7 +195,7 @@ export const Thingtime = props => {
const path = React.useMemo(() => {
return (
<Flex maxW='100%' wordBreak={'break-all'} fontSize='12px'>
<Flex maxW='100%' pl={pl} wordBreak={'break-all'} fontSize='12px'>
{props?.path?.human}
</Flex>
)
@ -227,7 +229,7 @@ export const Thingtime = props => {
>
{/* {uuid?.current} */}
{path}
{showContextMenu && contextMenu}
{/* {showContextMenu && contextMenu} */}
{editableValue}
{value}
</Flex>

View File

@ -4,19 +4,34 @@ import { Flex } from '@chakra-ui/react'
export const ThingtimeDemo = props => {
const thing = {
name: 'thing',
description: 'thing description',
image: 'thing image',
price: 100,
quantity: 1,
id: 1,
nested: {
Suggestion: 'Press ctrl/cmd + P',
Name: 'thingtime',
Description: `
Thing Time is a versatile online platform that empowers users to organize, customize, and manage different types of data. By generalizing the concept of social media feeds, Thing Time allows users to switch between various data schemas, essentially providing them with personalized feeds and organizational tools. It's like a combination of Facebook, Twitter, and Evernote, but with the user in control of what kind of data they want to see and manage. Whether it's social posts, marketplace listings, or personal notes, Thing Time puts the power of data organization and customization in the users hands.
`,
'Image URL': 'https://google.com/images',
Price: '$100',
QTY: 1,
ID: 123,
Nested: {
data: {
is: {
fun: "Isn't it?"
}
}
}
},
Array: ['one', 'two', 'three'],
'Array of Objects': [
{
name: 'one'
},
{
name: 'two'
},
{
name: 'three'
}
]
}
const [demoThing, setDemoThing] = React.useState(thing)

View File

@ -13,7 +13,7 @@ const getGlobal = () => {
export const useThingtime = (props?: any) => {
const value = useContext(ThingtimeContext)
const { thingtime, setThingtime, getThingtime } = value
const { thingtime, setThingtime, getThingtime, thingtimeRef } = value
return value
}

View File

@ -1,6 +1,7 @@
export const sanitise = str => {
const isTT = str?.slice(0, 3) === 'tt.'
const isThingtime = str?.slice(0, 9) === 'thingtime.'
const isDot = str?.slice(0, 1) === '.'
if (isTT) {
str = str?.slice(3)
@ -9,5 +10,9 @@ export const sanitise = str => {
str = str?.slice(9)
}
if (isDot) {
str = str?.slice(1)
}
return str
}

View File

@ -1,9 +1,13 @@
import { Flex } from '@chakra-ui/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'
export default function Index () {
const { thingtime } = useThingtime()
return (
<Flex
maxW='100%'
@ -13,6 +17,7 @@ export default function Index () {
>
<Splash></Splash>
<ThingtimeDemo></ThingtimeDemo>
<Thingtime mb={200} thing={thingtime['Bottom Content']}></Thingtime>
<ProfileDrawer></ProfileDrawer>
</Flex>
)

View File

@ -11,13 +11,13 @@ try {
// nothing
}
const babel = require('@babel/standalone')
// const babel = require('@babel/standalone')
babel.template = require('@babel/template').default
babel.t = require('@babel/types')
babel.generator = require('@babel/generator').default
babel.babylon = require('@babel/parser')
babel.prettier = require('prettier')
// babel.template = require('@babel/template').default
// babel.t = require('@babel/types')
// babel.generator = require('@babel/generator').default
// babel.babylon = require('@babel/parser')
// babel.prettier = require('prettier')
const objList = []
const stringList = []
@ -38,8 +38,10 @@ const stringList = []
// import prettier from 'prettier'
// babel.prettier = prettier
export const babel = () => {}
export const local = {}
export const t = babel?.t
export const t = () => {}
// export const t = babel?.t
export const getBabel = () => {
return babel