sozsoft-platform/ui/src/views/public/designer/useDesignerState.ts

184 lines
4.6 KiB
TypeScript
Raw Normal View History

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,
}
}