New marks and settings refactor #323
20
src/components/MarkTypeStrategy/FullName/Input.tsx
Normal file
20
src/components/MarkTypeStrategy/FullName/Input.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useDidMount } from '../../../hooks'
|
||||||
|
import { useLocalStorage } from '../../../hooks/useLocalStorage'
|
||||||
|
import { MarkInputProps } from '../MarkStrategy'
|
||||||
|
import { MarkInputText } from '../Text/Input'
|
||||||
|
|
||||||
|
export const MarkInputFullName = (props: MarkInputProps) => {
|
||||||
|
const [fullName, setFullName] = useLocalStorage('mark-fullname', '')
|
||||||
|
useDidMount(() => {
|
||||||
|
props.handler(fullName)
|
||||||
|
})
|
||||||
|
return MarkInputText({
|
||||||
|
...props,
|
||||||
|
placeholder: 'Full Name',
|
||||||
|
value: fullName,
|
||||||
|
handler: (value) => {
|
||||||
|
setFullName(value)
|
||||||
|
props.handler(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
7
src/components/MarkTypeStrategy/FullName/index.tsx
Normal file
7
src/components/MarkTypeStrategy/FullName/index.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { MarkStrategy } from '../MarkStrategy'
|
||||||
|
import { MarkInputFullName } from './Input'
|
||||||
|
|
||||||
|
export const FullNameStrategy: MarkStrategy = {
|
||||||
|
input: MarkInputFullName,
|
||||||
|
render: ({ value }) => <>{value}</>
|
||||||
|
}
|
@ -2,6 +2,7 @@ import { MarkType } from '../../types/drawing'
|
|||||||
import { CurrentUserMark, Mark } from '../../types/mark'
|
import { CurrentUserMark, Mark } from '../../types/mark'
|
||||||
import { TextStrategy } from './Text'
|
import { TextStrategy } from './Text'
|
||||||
import { SignatureStrategy } from './Signature'
|
import { SignatureStrategy } from './Signature'
|
||||||
|
import { FullNameStrategy } from './FullName'
|
||||||
|
|
||||||
export interface MarkInputProps {
|
export interface MarkInputProps {
|
||||||
value: string
|
value: string
|
||||||
@ -28,5 +29,6 @@ export type MarkStrategies = {
|
|||||||
|
|
||||||
export const MARK_TYPE_CONFIG: MarkStrategies = {
|
export const MARK_TYPE_CONFIG: MarkStrategies = {
|
||||||
[MarkType.TEXT]: TextStrategy,
|
[MarkType.TEXT]: TextStrategy,
|
||||||
[MarkType.SIGNATURE]: SignatureStrategy
|
[MarkType.SIGNATURE]: SignatureStrategy,
|
||||||
|
[MarkType.FULLNAME]: FullNameStrategy
|
||||||
}
|
}
|
||||||
|
64
src/hooks/useLocalStorage.ts
Normal file
64
src/hooks/useLocalStorage.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import {
|
||||||
|
getLocalStorageItem,
|
||||||
|
mergeWithInitialValue,
|
||||||
|
removeLocalStorageItem,
|
||||||
|
setLocalStorageItem
|
||||||
|
} from '../utils'
|
||||||
|
|
||||||
|
const useLocalStorageSubscribe = (callback: () => void) => {
|
||||||
|
window.addEventListener('storage', callback)
|
||||||
|
return () => window.removeEventListener('storage', callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLocalStorage<T>(
|
||||||
|
key: string,
|
||||||
|
initialValue: T
|
||||||
|
): [T, React.Dispatch<React.SetStateAction<T>>] {
|
||||||
|
const getSnapshot = () => {
|
||||||
|
// Get the stored value
|
||||||
|
const storedValue = getLocalStorageItem(key, initialValue)
|
||||||
|
|
||||||
|
// Parse the value
|
||||||
|
const parsedStoredValue = JSON.parse(storedValue)
|
||||||
|
|
||||||
|
// Merge the default and the stored in case some of the required fields are missing
|
||||||
|
return JSON.stringify(
|
||||||
|
mergeWithInitialValue(parsedStoredValue, initialValue)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = React.useSyncExternalStore(useLocalStorageSubscribe, getSnapshot)
|
||||||
|
|||||||
|
|
||||||
|
const setState: React.Dispatch<React.SetStateAction<T>> = React.useCallback(
|
||||||
s
commented
I'll be good to add some explanatory comments I'll be good to add some explanatory comments
|
|||||||
|
(v: React.SetStateAction<T>) => {
|
||||||
|
try {
|
||||||
|
const nextState =
|
||||||
|
typeof v === 'function'
|
||||||
|
? (v as (prevState: T) => T)(JSON.parse(data))
|
||||||
|
: v
|
||||||
|
|
||||||
|
if (nextState === undefined || nextState === null) {
|
||||||
|
removeLocalStorageItem(key)
|
||||||
|
} else {
|
||||||
|
setLocalStorageItem(key, JSON.stringify(nextState))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[data, key]
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
// Set local storage only when it's empty
|
||||||
|
const data = window.localStorage.getItem(key)
|
||||||
|
if (data === null) {
|
||||||
|
setLocalStorageItem(key, JSON.stringify(initialValue))
|
||||||
|
}
|
||||||
|
}, [key, initialValue])
|
||||||
|
|
||||||
|
const memoized = useMemo(() => JSON.parse(data) as T, [data])
|
||||||
|
|
||||||
|
return [memoized, setState]
|
||||||
|
}
|
@ -42,3 +42,47 @@ export const clear = () => {
|
|||||||
clearAuthToken()
|
clearAuthToken()
|
||||||
clearState()
|
clearState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mergeWithInitialValue<T>(storedValue: T, initialValue: T): T {
|
||||||
|
if (
|
||||||
|
!Array.isArray(storedValue) &&
|
||||||
|
typeof storedValue === 'object' &&
|
||||||
|
storedValue !== null
|
||||||
|
) {
|
||||||
|
return { ...initialValue, ...storedValue }
|
||||||
|
}
|
||||||
|
return storedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocalStorageItem<T>(key: string, defaultValue: T): string {
|
||||||
|
try {
|
||||||
|
const data = window.localStorage.getItem(key)
|
||||||
|
if (data === null) return JSON.stringify(defaultValue)
|
||||||
|
return data
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error while fetching local storage value: `, err)
|
||||||
|
return JSON.stringify(defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLocalStorageItem(key: string, value: string) {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(key, value)
|
||||||
|
dispatchLocalStorageEvent(key, value)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error while saving local storage value: `, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeLocalStorageItem(key: string) {
|
||||||
|
try {
|
||||||
|
window.localStorage.removeItem(key)
|
||||||
|
dispatchLocalStorageEvent(key, null)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error while deleting local storage value: `, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatchLocalStorageEvent(key: string, newValue: string | null) {
|
||||||
|
window.dispatchEvent(new StorageEvent('storage', { key, newValue }))
|
||||||
|
}
|
||||||
|
@ -171,8 +171,7 @@ export const DEFAULT_TOOLBOX: DrawTool[] = [
|
|||||||
{
|
{
|
||||||
identifier: MarkType.FULLNAME,
|
identifier: MarkType.FULLNAME,
|
||||||
icon: faIdCard,
|
icon: faIdCard,
|
||||||
label: 'Full Name',
|
label: 'Full Name'
|
||||||
isComingSoon: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.JOBTITLE,
|
identifier: MarkType.JOBTITLE,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user
I'll be good to add some explanatory comments