feat: feature/mvp-sprint-1 Added undo/redo

This commit is contained in:
Nikolaj Frey 2023-08-14 10:32:42 +10:00
parent 7eecc21173
commit efcda254eb
3 changed files with 279 additions and 83 deletions

View File

@ -114,23 +114,137 @@ initialThingtime.thingtime = initialThingtime
initialThingtime.tt = initialThingtime initialThingtime.tt = initialThingtime
export const ThingtimeProvider = (props: any): JSX.Element => { export const ThingtimeProvider = (props: any): JSX.Element => {
const [thingtime, rawSet] = React.useState() const [thingtimeReference, rawSet] = React.useState()
const thingtimeRef = React.useRef(thingtime) const thingtimeRef = React.useRef(thingtimeReference)
const stateRef = React.useRef({ const stateRef = React.useRef({
c: 1, c: 1,
}) })
const [loading, setLoading] = React.useState(true) const [loading, setLoading] = React.useState(true)
const set = React.useCallback((newThingtime) => { const set = React.useCallback((newThingtime, ignoreUndoRedo?: any) => {
const thingtimeReference = { const newThingtimeReference = {
...newThingtime, ...newThingtime,
} }
thingtimeReference.tt = thingtimeReference newThingtimeReference.tt = newThingtimeReference
thingtimeReference.thingtime = thingtimeReference newThingtimeReference.thingtime = newThingtimeReference
rawSet(thingtimeReference)
// store undo/redo history
if (!ignoreUndoRedo) {
try {
console.log(
"ThingtimeProvider setting thingtime to localStorage",
newThingtimeReference
)
// setTimeout(() => {
const stringified = stringify(newThingtimeReference)
let undoHistory = []
try {
const undoHistoryString = window.localStorage.getItem("undoHistory")
const parsedUndoHistory = JSON.parse(undoHistoryString)
if (parsedUndoHistory instanceof Array) {
undoHistory = parsedUndoHistory
}
} catch {
// nothing
}
// if last undoHistory does not equal new undo history
console.log(
"ThingtimeProvider saving to undo history undoHistory[undoHistory.length - 1]?.value",
undoHistory[undoHistory.length - 1]?.value
)
console.log(
"ThingtimeProvider saving to undo history stringified",
stringified
)
if (undoHistory[undoHistory.length - 1]?.value !== stringified) {
try {
console.log(
"ThingtimeProvider saving to undo history undoHistory",
undoHistory
)
const limit = newThingtimeReference?.settings?.undoLimit || 999
if (undoHistory?.length > limit) {
undoHistory = undoHistory.slice(undoHistory.length - limit)
}
undoHistory.push({
timestamp: Date.now(),
value: stringify(newThingtimeReference),
})
const undoHistoryNewString = JSON.stringify(undoHistory)
window.localStorage.setItem("undoHistory", undoHistoryNewString)
} catch {
// nothing
}
}
// window.localStorage.setItem("thingtime", stringified)
// }, 600)
} catch (err) {
console.error("There was an error saving thingtime to localStorage")
}
const saveRedo = false
if (saveRedo) {
try {
console.log(
"ThingtimeProvider setting thingtime to localStorage",
newThingtimeReference
)
// setTimeout(() => {
const stringified = stringify(newThingtimeReference)
let redoHistory = []
try {
const redoHistoryString = window.localStorage.getItem("redoHistory")
const parsedRedoHistory = JSON.parse(redoHistoryString)
if (parsedRedoHistory instanceof Array) {
redoHistory = parsedRedoHistory
}
} catch {
// nothing
}
// if last redoHistory does not equal new redo history
console.log(
"ThingtimeProvider saving to redo history redoHistory[redoHistory.length - 1]?.value",
redoHistory[redoHistory.length - 1]?.value
)
console.log(
"ThingtimeProvider saving to redo history stringified",
stringified
)
if (redoHistory[redoHistory.length - 1]?.value !== stringified) {
try {
console.log(
"ThingtimeProvider saving to redo history redoHistory",
redoHistory
)
const limit = newThingtimeReference?.settings?.redoLimit || 999
if (redoHistory?.length > limit) {
redoHistory = redoHistory.slice(redoHistory.length - limit)
}
redoHistory.push({
timestamp: Date.now(),
value: stringify(newThingtimeReference),
})
const redoHistoryNewString = JSON.stringify(redoHistory)
window.localStorage.setItem("redoHistory", redoHistoryNewString)
} catch {
// nothing
}
}
// window.localStorage.setItem("thingtime", stringified)
// }, 600)
} catch (err) {
console.error("There was an error saving thingtime to localStorage")
}
}
}
rawSet(newThingtimeReference)
}, []) }, [])
const setThingtime = React.useCallback( const setThingtime = React.useCallback(
@ -143,7 +257,7 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
} }
} }
const newThingtime = thingtime const newThingtime = thingtimeReference
const paths = smarts.parsePropertyPath(path) const paths = smarts.parsePropertyPath(path)
@ -193,11 +307,9 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
smarts.setsmart(newThingtime, path, value) smarts.setsmart(newThingtime, path, value)
console.log("nik set(newThingtime)", newThingtime)
set(newThingtime) set(newThingtime)
}, },
[thingtime, set] [thingtimeReference, set]
) )
const getThingtime = React.useCallback( const getThingtime = React.useCallback(
@ -206,16 +318,16 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
const path = rawPath const path = rawPath
if (!path) { if (!path) {
return thingtime return thingtimeReference
} }
// do we need to sanitise? // do we need to sanitise?
// const path = sanitise(rawPath) // const path = sanitise(rawPath)
console.log("ThingtimeProvider getting thingtime at path", path) console.log("ThingtimeProvider getting thingtime at path", path)
// console.trace("Getting thingtime at path", path) // console.trace("Getting thingtime at path", path)
return smarts.getsmart(thingtime, path) return smarts.getsmart(thingtimeReference, path)
}, },
[thingtime] [thingtimeReference]
) )
const populatePaths = React.useCallback((obj, path, paths, seen = []) => { const populatePaths = React.useCallback((obj, path, paths, seen = []) => {
@ -243,10 +355,10 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
const paths = [] const paths = []
// populatePaths(thingtime, commandPath) // populatePaths(thingtime, commandPath)
populatePaths(thingtime, "", paths) populatePaths(thingtimeReference, "", paths)
return paths return paths
}, [populatePaths, thingtime]) }, [populatePaths, thingtimeReference])
// get thingtime from localstorage // get thingtime from localstorage
React.useEffect(() => { React.useEffect(() => {
@ -288,86 +400,173 @@ export const ThingtimeProvider = (props: any): JSX.Element => {
React.useEffect(() => { React.useEffect(() => {
try { try {
window.setThingtime = setThingtime window.setThingtime = setThingtime
window.thingtime = thingtime window.thingtime = thingtimeReference
window.tt = thingtime window.tt = thingtimeReference
} catch { } catch {
// nothing // nothing
} }
if (stateRef.current.initialized) { 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 { try {
console.log( console.log(
"ThingtimeProvider setting thingtime to localStorage", "ThingtimeProvider setting thingtime to localStorage",
thingtime thingtimeReference
) )
// setTimeout(() => { // setTimeout(() => {
const stringified = stringify(thingtime) const stringified = stringify(thingtimeReference)
let thingtimeHistory = []
try {
const thingtimeHistoryString =
window.localStorage.getItem("thingtimeHistory")
const parsedThingtimeHistory = JSON.parse(thingtimeHistoryString)
if (parsedThingtimeHistory instanceof Array) {
thingtimeHistory = parsedThingtimeHistory
}
} catch {
// nothing
}
try {
const limit = thingtime?.settings?.undoLimit || 999
if (thingtimeHistory?.length > limit) {
thingtimeHistory = thingtimeHistory.slice(
thingtimeHistory.length - limit
)
}
thingtimeHistory.push({
timestamp: Date.now(),
value: stringify(thingtime),
})
const thingtimeHistoryNewString = JSON.stringify(thingtimeHistory)
window.localStorage.setItem(
"thingtimeHistory",
thingtimeHistoryNewString
)
} catch {
// nothing
}
window.localStorage.setItem("thingtime", stringified) window.localStorage.setItem("thingtime", stringified)
// }, 600) // }, 600)
} catch (err) { } catch (err) {
console.error("There was an error saving thingtime to localStorage") console.error("There was an error saving thingtime to localStorage")
} }
}
} else { } else {
stateRef.current.initialized = true stateRef.current.initialized = true
} }
thingtimeRef.current = thingtime thingtimeRef.current = thingtimeReference
const keyListener = (e) => {} const keyListener = (e) => {
// if ctrl + z, restore thingtime from localstorage history
console.log("ThingtimeProvider listened to key event e?.key", e?.key)
console.log(
"ThingtimeProvider listened to key event e?.ctrlKey",
e?.ctrlKey
)
console.log(
"ThingtimeProvider listened to key event e?.shiftKey",
e?.shiftKey
)
console.log(
"ThingtimeProvider listened to key event e?.metaKey",
e?.metaKey
)
if ((e?.ctrlKey || e?.metaKey) && e?.key === "z") {
e?.preventDefault()
console.log("ThingtimeProvider detected undo/redo request")
if (e.shiftKey) {
// redo
console.log("ThingtimeProvider redo")
const redoHistoryString = window.localStorage.getItem("redoHistory")
const parsedRedoHistory = JSON.parse(redoHistoryString)
if (parsedRedoHistory instanceof Array) {
const last = parsedRedoHistory[parsedRedoHistory.length - 1]
if (last) {
const parsed = parse(last.value)
if (parsed) {
// remove restored state from history
// const currentHistory = parsedRedoHistory.pop()
parsedRedoHistory.pop()
// parsedRedoHistory.push(currentHistory)
const newRedoHistoryString = JSON.stringify(parsedRedoHistory)
window.localStorage.setItem("redoHistory", newRedoHistoryString)
// save old/current state to undo history
let undoHistory = []
try {
const undoHistoryString =
window.localStorage.getItem("undoHistory")
const parsedUndoHistory = JSON.parse(undoHistoryString)
if (parsedUndoHistory instanceof Array) {
undoHistory = parsedUndoHistory
}
} catch {
// nothing
}
try {
undoHistory.push({
timestamp: Date.now(),
value: stringify(thingtimeReference),
})
const undoHistoryNewString = JSON.stringify(undoHistory)
window.localStorage.setItem(
"undoHistory",
undoHistoryNewString
)
} catch {
// nothing
}
const newThingtime = parsed
set(newThingtime, true)
}
}
}
} else {
// undo
console.log("ThingtimeProvider undo")
try {
const undoHistoryString = window.localStorage.getItem("undoHistory")
const parsedUndoHistory = JSON.parse(undoHistoryString)
if (parsedUndoHistory instanceof Array) {
const last = parsedUndoHistory[parsedUndoHistory.length - 2]
if (last) {
const parsed = parse(last.value)
if (parsed) {
// remove restored state from history
const currentHistory = parsedUndoHistory.pop()
parsedUndoHistory.pop()
parsedUndoHistory.push(currentHistory)
const newUndoHistoryString = JSON.stringify(parsedUndoHistory)
window.localStorage.setItem(
"undoHistory",
newUndoHistoryString
)
// save old/current state to redo history
let redoHistory = []
try {
const redoHistoryString =
window.localStorage.getItem("redoHistory")
const parsedRedoHistory = JSON.parse(redoHistoryString)
if (parsedRedoHistory instanceof Array) {
redoHistory = parsedRedoHistory
}
} catch {
// nothing
}
try {
const newValue = stringify(thingtimeReference)
// if last history is not the same as new history
redoHistory.push({
timestamp: Date.now(),
value: newValue,
})
const redoHistoryNewString = JSON.stringify(redoHistory)
window.localStorage.setItem(
"redoHistory",
redoHistoryNewString
)
} catch {
// nothing
}
const newThingtime = parsed
set(newThingtime, true)
}
}
}
} catch {
// nothing
}
}
}
}
window.addEventListener("keydown", keyListener) window.addEventListener("keydown", keyListener)
return () => { return () => {
window.removeEventListener("keydown", keyListener) window.removeEventListener("keydown", keyListener)
} }
}, [setThingtime, thingtime, set]) }, [setThingtime, thingtimeReference, set])
const value = { const value = {
thingtime, thingtime: thingtimeReference,
setThingtime, setThingtime,
getThingtime, getThingtime,
thingtimeRef, thingtimeRef,

View File

@ -84,7 +84,7 @@ export const Nav = (props) => {
<CommanderV1 global id="nav" rainbow></CommanderV1> <CommanderV1 global id="nav" rainbow></CommanderV1>
<Center <Center
className="nav-right-section" className="nav-right-section"
columnGap={[3, 5]} columnGap={[3, 8]}
height="100%" height="100%"
marginLeft="auto" marginLeft="auto"
> >

View File

@ -243,9 +243,9 @@ export const Thingtime = (props) => {
) )
} }
} else if (type === "undefined") { } else if (type === "undefined") {
return "undefined" return "Imagine.."
} else { } else {
return "Something!" return "Something.."
} }
}, [thing, thingDep, type, keys]) }, [thing, thingDep, type, keys])
@ -376,9 +376,6 @@ export const Thingtime = (props) => {
delete clone[path] delete clone[path]
console.log("nik parentPath", parentPath)
console.log("nik clone", clone)
setThingtime(parentPath, clone) setThingtime(parentPath, clone)
}, [path, parent, parentPath, setThingtime]) }, [path, parent, parentPath, setThingtime])