184 lines
4.6 KiB
TypeScript
184 lines
4.6 KiB
TypeScript
|
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||
|
|
import { useLocation, useSearchParams } from 'react-router-dom'
|
||
|
|
|
||
|
|
const DESIGNER_QUERY_KEY = 'design'
|
||
|
|
|
||
|
|
interface UseDesignerStateOptions {
|
||
|
|
currentLanguage?: string
|
||
|
|
supportedLanguages?: string[]
|
||
|
|
}
|
||
|
|
|
||
|
|
function serializeValue<T>(value: T | null | undefined) {
|
||
|
|
if (value === undefined) {
|
||
|
|
return '__undefined__'
|
||
|
|
}
|
||
|
|
|
||
|
|
if (value === null) {
|
||
|
|
return '__null__'
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
return JSON.stringify(value)
|
||
|
|
} catch {
|
||
|
|
return '__unserializable__'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function normalizeLanguageKey(language?: string) {
|
||
|
|
if (!language) {
|
||
|
|
return 'tr'
|
||
|
|
}
|
||
|
|
|
||
|
|
return language.toLowerCase().split('-')[0]
|
||
|
|
}
|
||
|
|
|
||
|
|
function cloneValue<T>(value: T): T {
|
||
|
|
try {
|
||
|
|
return JSON.parse(JSON.stringify(value)) as T
|
||
|
|
} catch {
|
||
|
|
return value
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useDesignerState<T>(
|
||
|
|
pageKey: string,
|
||
|
|
initialContent: T | null,
|
||
|
|
options?: UseDesignerStateOptions,
|
||
|
|
) {
|
||
|
|
const location = useLocation()
|
||
|
|
const [searchParams] = useSearchParams()
|
||
|
|
const isDesignMode =
|
||
|
|
searchParams.get(DESIGNER_QUERY_KEY) === '1' || location.pathname.endsWith('/designer')
|
||
|
|
const currentLanguage = normalizeLanguageKey(options?.currentLanguage)
|
||
|
|
const supportedLanguages = useMemo(() => {
|
||
|
|
const base = (options?.supportedLanguages ?? [currentLanguage]).map((language) =>
|
||
|
|
normalizeLanguageKey(language),
|
||
|
|
)
|
||
|
|
if (!base.includes(currentLanguage)) {
|
||
|
|
return [currentLanguage, ...base]
|
||
|
|
}
|
||
|
|
|
||
|
|
return Array.from(new Set(base))
|
||
|
|
}, [currentLanguage, options?.supportedLanguages])
|
||
|
|
|
||
|
|
const [selectedLanguage, setSelectedLanguage] = useState(currentLanguage)
|
||
|
|
const [byLanguage, setByLanguage] = useState<Record<string, T>>({})
|
||
|
|
const [selectedBlockId, setSelectedBlockId] = useState<string | null>(null)
|
||
|
|
const seededDefaultsRef = useRef<Record<string, string>>({})
|
||
|
|
|
||
|
|
const selectedLanguagePrefix = normalizeLanguageKey(selectedLanguage)
|
||
|
|
const content =
|
||
|
|
byLanguage[selectedLanguage] ??
|
||
|
|
byLanguage[selectedLanguagePrefix] ??
|
||
|
|
byLanguage[currentLanguage] ??
|
||
|
|
initialContent ??
|
||
|
|
null
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
setSelectedLanguage(normalizeLanguageKey(currentLanguage))
|
||
|
|
}, [currentLanguage])
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!initialContent) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
const nextSerializedInitial = serializeValue(initialContent)
|
||
|
|
|
||
|
|
setByLanguage((prev) => {
|
||
|
|
const currentValue = prev[currentLanguage]
|
||
|
|
const currentSerialized = serializeValue(currentValue)
|
||
|
|
const previousSeed = seededDefaultsRef.current[currentLanguage]
|
||
|
|
|
||
|
|
if (currentValue !== undefined && previousSeed !== undefined && currentSerialized !== previousSeed) {
|
||
|
|
return prev
|
||
|
|
}
|
||
|
|
|
||
|
|
if (currentSerialized === nextSerializedInitial) {
|
||
|
|
seededDefaultsRef.current[currentLanguage] = nextSerializedInitial
|
||
|
|
return prev
|
||
|
|
}
|
||
|
|
|
||
|
|
seededDefaultsRef.current[currentLanguage] = nextSerializedInitial
|
||
|
|
|
||
|
|
return {
|
||
|
|
...prev,
|
||
|
|
[currentLanguage]: cloneValue(initialContent),
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
if (!isDesignMode && selectedBlockId !== null) {
|
||
|
|
setSelectedBlockId(null)
|
||
|
|
}
|
||
|
|
}, [currentLanguage, initialContent, isDesignMode, selectedBlockId])
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!isDesignMode || !initialContent) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
const normalizedSelectedLanguage = normalizeLanguageKey(selectedLanguage)
|
||
|
|
const hasSelectedLanguageContent =
|
||
|
|
byLanguage[normalizedSelectedLanguage] !== undefined ||
|
||
|
|
byLanguage[selectedLanguage] !== undefined
|
||
|
|
|
||
|
|
if (hasSelectedLanguageContent) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// If selected language has no saved value, initialize it from the current translated defaults.
|
||
|
|
seededDefaultsRef.current[normalizedSelectedLanguage] = serializeValue(initialContent)
|
||
|
|
setByLanguage((prev) => ({
|
||
|
|
...prev,
|
||
|
|
[normalizedSelectedLanguage]: cloneValue(initialContent),
|
||
|
|
}))
|
||
|
|
}, [byLanguage, initialContent, isDesignMode, selectedLanguage])
|
||
|
|
|
||
|
|
const setContent = (updater: (current: T | null) => T | null) => {
|
||
|
|
setByLanguage((prev) => {
|
||
|
|
const current =
|
||
|
|
prev[selectedLanguage] ??
|
||
|
|
prev[normalizeLanguageKey(selectedLanguage)] ??
|
||
|
|
prev[currentLanguage] ??
|
||
|
|
initialContent ??
|
||
|
|
null
|
||
|
|
const next = updater(current)
|
||
|
|
|
||
|
|
if (!next) {
|
||
|
|
return prev
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
...prev,
|
||
|
|
[normalizeLanguageKey(selectedLanguage)]: cloneValue(next),
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const resetContent = () => {
|
||
|
|
if (!initialContent) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
const normalizedSelectedLanguage = normalizeLanguageKey(selectedLanguage)
|
||
|
|
seededDefaultsRef.current[normalizedSelectedLanguage] = serializeValue(initialContent)
|
||
|
|
|
||
|
|
setByLanguage((prev) => ({
|
||
|
|
...prev,
|
||
|
|
[normalizedSelectedLanguage]: cloneValue(initialContent),
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
content,
|
||
|
|
isDesignMode,
|
||
|
|
selectedBlockId,
|
||
|
|
selectedLanguage,
|
||
|
|
supportedLanguages,
|
||
|
|
setContent,
|
||
|
|
setSelectedBlockId,
|
||
|
|
setSelectedLanguage,
|
||
|
|
resetContent,
|
||
|
|
}
|
||
|
|
}
|