310 lines
8.5 KiB
TypeScript
Raw Normal View History

import React, { createContext } from "react"
2023-07-15 08:08:36 +00:00
import { parse, stringify } from "flatted"
import { sanitise } from "~/functions/sanitise"
import { smarts } from "~/smarts"
export interface ThingtimeContextInterface {
thingtime: any
setThingtime: any
getThingtime: any
thingtimeRef: any
}
export const ThingtimeContext = createContext<
ThingtimeContextInterface[] | null
>(null)
try {
window.smarts = smarts
} catch (err) {
// nothing
}
const force = {
settings: {
2023-07-15 08:08:36 +00:00
// commanderActive: false,
},
version: 22,
}
const newVersionData = {
Content: {
hidden1: "Edit this to your heart's desire.",
"How?": "Just search for Content and edit the value to whatever you want.",
"Example:": `Content = New Content!
Content.Nested Content = New Nested Content!
`,
},
}
const initialValues = {
settings: {
2023-07-15 08:08:36 +00:00
commanderActive: false,
clearCommanderOnToggle: true,
clearCommanderContextOnToggle: true,
},
Content: {
hidden1: "Edit this to your heart's desire.",
"How?": "Just search for Content and edit the value to whatever you want.",
"Example:": `Content = New Content!
Content.Nested Content = New Nested Content!
`,
},
}
const initialThingtime = smarts.merge(initialValues, force)
2023-07-15 08:08:36 +00:00
// TODO: Make localStorage be loaded first before initialValues if local version exists
// and is valid
// Issue seems to be server id is different to client hydration
// let thingtimeToUse = initialThingtime
// try {
// const thingtimeFromLocalStorage = window.localStorage.getItem("thingtime")
// if (thingtimeFromLocalStorage) {
// const parsed = parse(thingtimeFromLocalStorage)
// if (parsed) {
// const localIsValid = !parsed.version || parsed.version >= force.version
// if (localIsValid) {
// const newThingtime = smarts.merge(force, parsed)
// console.log("nik comm newThingtime", newThingtime)
// thingtimeToUse = newThingtime
// } else {
// const withVersionUpdates = smarts.merge(newVersionData, parsed)
// const newThingtime = smarts.merge(force, withVersionUpdates)
// thingtimeToUse = newThingtime
// }
// }
// }
// } catch (err) {
// console.error("Caught error restoring thingtime from localstorage", err)
// }
initialThingtime.thingtime = initialThingtime
initialThingtime.tt = initialThingtime
export const ThingtimeProvider = (props: any): JSX.Element => {
2023-07-21 01:13:02 +00:00
const [thingtime, rawSet] = React.useState(initialThingtime)
const thingtimeRef = React.useRef(thingtime)
2023-07-15 08:08:36 +00:00
const stateRef = React.useRef({
c: 1,
})
const set = React.useCallback((newThingtime) => {
const thingtimeReference = {
...newThingtime,
}
2023-07-21 13:33:47 +00:00
thingtimeReference.tt = thingtimeReference
thingtimeReference.thingtime = thingtimeReference
rawSet(thingtimeReference)
}, [])
const setThingtime = React.useCallback(
(path, value) => {
const newThingtime = thingtime
const paths = smarts.parsePropertyPath(path)
console.log("nik paths", paths)
// find first parent where a path is undefined
// paths is array of path parts such as ["path1", "path2", "path3"]
// we want to create a new reference at the first object which has an undefined part of the path
// and is an object itself
// so that react will detect the change and re-render
// "path1" = { ...thingtime["path1"] } if path1.path2 undefined
// "path1.path2" = { ...thingtime["path1"]["path2"] } if path1.path2.path3 undefined
// "path1.path2.path3" = { ...thingtime["path1"]["path2"]["path3"] }
// etc
let done = false
paths.forEach((pathPart, index) => {
if (!done) {
const pathParts = paths.slice(0, index + 1)
const tmpPath = pathParts.join(".")
const parentPath = pathParts.slice(0, -1).join(".")
const valAtPath = smarts.getsmart(newThingtime, tmpPath)
if (parentPath) {
if (typeof valAtPath !== "object" || valAtPath === null) {
const parentVal = smarts.getsmart(newThingtime, parentPath)
if (typeof parentVal === "object") {
const newParent = Array.isArray(parentVal)
? [...parentVal]
: { ...parentVal }
smarts.setsmart(newThingtime, parentPath, newParent)
}
done = true
}
}
}
})
newThingtime.thingtime = newThingtime
newThingtime.tt = newThingtime
console.log(
"nik setting newThingtime value at path",
'"' + path + '"',
"value: ",
value
)
2023-07-21 13:33:47 +00:00
smarts.setsmart(newThingtime, path, value)
console.log("nik set the newThingtime", newThingtime)
set(newThingtime)
},
2023-07-21 13:33:47 +00:00
[thingtime, set]
)
const getThingtime = React.useCallback(
(...args) => {
const rawPath = args[0]
2023-07-15 08:08:36 +00:00
const path = rawPath
if (!path) {
return thingtime
}
2023-07-15 08:08:36 +00:00
// do we need to sanitise?
// const path = sanitise(rawPath)
console.log("Getting thingtime at path", path)
2023-07-21 13:33:47 +00:00
// console.trace("Getting thingtime at path", path)
return smarts.getsmart(thingtime, path)
},
[thingtime]
)
2023-07-15 08:08:36 +00:00
const populatePaths = React.useCallback((obj, path, paths, seen = []) => {
try {
Object.keys(obj).forEach((key) => {
const val = obj[key]
const newPath = path ? `${path}${path ? "." : ""}${key}` : key
if (typeof val === "object") {
paths.push(newPath)
if (!seen?.includes(val)) {
seen.push(val)
populatePaths(val, newPath, paths, seen)
}
} else {
paths.push(newPath)
2023-07-15 08:08:36 +00:00
}
})
} catch {
// nothing
}
2023-07-15 08:08:36 +00:00
}, [])
const paths = React.useMemo(() => {
// const paths = ["tt", "thingtime", "."]
const paths = []
// populatePaths(thingtime, commandPath)
populatePaths(thingtime, "", paths)
return paths
}, [populatePaths, thingtime])
// get thingtime from localstorage
React.useEffect(() => {
try {
const thingtimeFromLocalStorage = window.localStorage.getItem("thingtime")
console.log("nik thingtimeFromLocalStorage", thingtimeFromLocalStorage)
2023-07-15 08:08:36 +00:00
if (thingtimeFromLocalStorage) {
const parsed = parse(thingtimeFromLocalStorage)
if (parsed) {
const localIsValid =
!parsed.version || parsed.version >= force.version
let newThingtime = null
if (localIsValid) {
newThingtime = smarts.merge(force, parsed)
} else {
const withVersionUpdates = smarts.merge(newVersionData, parsed)
newThingtime = smarts.merge(force, withVersionUpdates)
}
console.log(
"nik setting new thingtime from localStorage",
newThingtime
)
2023-07-15 08:08:36 +00:00
console.log("nik localIsValid", localIsValid)
set(newThingtime)
}
}
} catch (err) {
console.error("There was an error getting thingtime from localStorage")
}
}, [])
// thingtime change listener
React.useEffect(() => {
try {
window.setThingtime = setThingtime
window.thingtime = thingtime
window.tt = thingtime
} catch {
// nothing
}
2023-07-15 08:08:36 +00:00
console.log("nik detected thingtime change", thingtime)
if (stateRef.current.initialized) {
if (thingtime.thingtime !== thingtime || thingtime.tt !== thingtime) {
if (!(stateRef?.current?.c >= 10)) {
stateRef.current.c++
const newThingtime = {
...thingtime,
}
newThingtime.thingtime = newThingtime
newThingtime.tt = newThingtime
set(newThingtime)
}
} else {
try {
console.log("Setting thingtime to localStorage", thingtime)
2023-07-21 13:33:47 +00:00
// setTimeout(() => {
const stringified = stringify(thingtime)
window.localStorage.setItem("thingtime", stringified)
// }, 600)
2023-07-15 08:08:36 +00:00
} catch (err) {
console.error("There was an error saving thingtime to localStorage")
}
}
} else {
stateRef.current.initialized = true
}
thingtimeRef.current = thingtime
const keyListener = (e) => {}
window.addEventListener("keydown", keyListener)
return () => {
window.removeEventListener("keydown", keyListener)
}
}, [setThingtime, thingtime, set])
const value = {
thingtime,
setThingtime,
getThingtime,
thingtimeRef,
2023-07-15 08:08:36 +00:00
paths,
}
return (
<ThingtimeContext.Provider value={value}>
{props?.children}
</ThingtimeContext.Provider>
)
}