Skip to main content
Glama
EditedContentContext.tsx10.1 kB
'use client'; import { editDictionaryByKeyPath, getContentNodeByKeyPath, renameContentNodeByKeyPath, } from '@intlayer/core'; import { MessageKey } from '@intlayer/editor'; import { type ContentNode, type Dictionary, type KeyPath, type LocalDictionaryId, NodeType, } from '@intlayer/types'; import { createContext, type Dispatch, type FC, type PropsWithChildren, type SetStateAction, useContext, } from 'react'; import { type DictionaryContent, useDictionariesRecord, } from './DictionariesRecordContext'; import { useCrossFrameMessageListener } from './useCrossFrameMessageListener'; import { useCrossFrameState } from './useCrossFrameState'; import { useEditorLocale } from './useEditorLocale'; type EditedContentStateContextType = { editedContent: Record<LocalDictionaryId, Dictionary> | undefined; }; const EditedContentStateContext = createContext< EditedContentStateContextType | undefined >(undefined); export const usePostEditedContentState = <S,>( onEventTriggered?: (data: S) => void ) => useCrossFrameMessageListener( `${MessageKey.INTLAYER_EDITED_CONTENT_CHANGED}/post`, onEventTriggered ); export const useGetEditedContentState = <S,>( onEventTriggered?: (data: S) => void ) => useCrossFrameMessageListener( `${MessageKey.INTLAYER_EDITED_CONTENT_CHANGED}/get`, onEventTriggered ); type EditedContentActionsContextType = { setEditedContentState: (editedContent: DictionaryContent) => void; setEditedDictionary: Dispatch<SetStateAction<Dictionary>>; setEditedContent: ( localDictionaryId: LocalDictionaryId, newValue: Dictionary['content'] ) => void; addEditedContent: ( localDictionaryId: LocalDictionaryId, newValue: ContentNode<any>, keyPath?: KeyPath[], overwrite?: boolean ) => void; renameEditedContent: ( localDictionaryId: LocalDictionaryId, newKey: KeyPath['key'], keyPath?: KeyPath[] ) => void; removeEditedContent: ( localDictionaryId: LocalDictionaryId, keyPath: KeyPath[] ) => void; restoreEditedContent: (localDictionaryId: LocalDictionaryId) => void; clearEditedDictionaryContent: (localDictionaryId: LocalDictionaryId) => void; clearEditedContent: () => void; getEditedContentValue: ( localDictionaryIdOrKey: LocalDictionaryId | Dictionary['key'] | string, keyPath: KeyPath[] ) => ContentNode | undefined; }; const EditedContentActionsContext = createContext< EditedContentActionsContextType | undefined >(undefined); const resolveState = <S,>(state?: SetStateAction<S>, prevState?: S): S => typeof state === 'function' ? (state as (prevState?: S) => S)(prevState) : (state as S); export const EditedContentProvider: FC<PropsWithChildren> = ({ children }) => { const { localeDictionaries } = useDictionariesRecord(); const currentLocale = useEditorLocale(); const [editedContent, setEditedContentState] = useCrossFrameState<DictionaryContent>( MessageKey.INTLAYER_EDITED_CONTENT_CHANGED ); const setEditedDictionary: Dispatch<SetStateAction<Dictionary>> = ( newValue ) => { let updatedDictionaries: Dictionary = resolveState(newValue); setEditedContentState((prev) => { if (!updatedDictionaries.localId) { console.error('no localId', updatedDictionaries); return prev; } updatedDictionaries = resolveState( newValue, prev?.[updatedDictionaries.localId] ); return { ...prev, [updatedDictionaries.localId as LocalDictionaryId]: updatedDictionaries, }; }); return updatedDictionaries; }; const setEditedContent = ( localDictionaryId: LocalDictionaryId, newValue: Dictionary['content'] ) => { setEditedContentState((prev) => ({ ...prev, [localDictionaryId]: { ...prev?.[localDictionaryId], content: newValue, }, })); }; const addEditedContent = ( localDictionaryId: LocalDictionaryId, newValue: ContentNode, keyPath: KeyPath[] = [], overwrite: boolean = true ) => { setEditedContentState((prev) => { // Get the starting content: edited version if available, otherwise a deep copy of the original const originalContent = localeDictionaries[localDictionaryId]?.content; const currentContent = structuredClone( prev?.[localDictionaryId]?.content ?? originalContent ); let newKeyPath = keyPath; if (!overwrite) { // Find a unique key based on the keyPath provided let index = 0; const otherKeyPath = keyPath.slice(0, -1); const lastKeyPath: KeyPath = keyPath[keyPath.length - 1]; let finalKey = lastKeyPath.key; // Loop until we find a key that does not exist while ( typeof getContentNodeByKeyPath(currentContent, newKeyPath) !== 'undefined' ) { index++; finalKey = index === 0 ? lastKeyPath.key : `${lastKeyPath.key} (${index})`; newKeyPath = [ ...otherKeyPath, { ...lastKeyPath, key: finalKey } as KeyPath, ]; } } const updatedContent = editDictionaryByKeyPath( currentContent, newKeyPath, newValue ); return { ...prev, [localDictionaryId]: { ...prev?.[localDictionaryId], content: updatedContent as Dictionary['content'], }, }; }); }; const renameEditedContent = ( localDictionaryId: LocalDictionaryId, newKey: KeyPath['key'], keyPath: KeyPath[] = [] ) => { setEditedContentState((prev) => { // Retrieve the base content: use edited version if available, otherwise deep copy of original const originalContent = localeDictionaries[localDictionaryId]?.content; const currentContent = structuredClone( prev?.[localDictionaryId]?.content ?? originalContent ); const contentWithNewField = renameContentNodeByKeyPath( currentContent, newKey, keyPath ); return { ...prev, [localDictionaryId]: { ...prev?.[localDictionaryId], content: contentWithNewField as Dictionary['content'], }, }; }); }; const removeEditedContent = ( localDictionaryId: LocalDictionaryId, keyPath: KeyPath[] ) => { setEditedContentState((prev) => { // Retrieve the original content as reference const originalContent = localeDictionaries[localDictionaryId]?.content; const currentContent = structuredClone( prev?.[localDictionaryId]?.content ?? originalContent ); // Get the initial value from the original dictionary content const initialContent = getContentNodeByKeyPath(originalContent, keyPath); // Restore the value at the given keyPath const restoredContent = editDictionaryByKeyPath( currentContent, keyPath, initialContent ); return { ...prev, [localDictionaryId]: { ...prev?.[localDictionaryId], content: restoredContent as Dictionary['content'], }, }; }); }; const restoreEditedContent = (localDictionaryId: LocalDictionaryId) => { setEditedContentState((prev) => { const updated = { ...prev }; delete updated[localDictionaryId]; return updated; }); }; const clearEditedDictionaryContent = ( localDictionaryId: LocalDictionaryId ) => { setEditedContentState((prev) => { const filtered = Object.entries(prev).reduce((acc, [key, value]) => { if (key === localDictionaryId) { return acc; } return { ...acc, [key]: value }; }, {} as DictionaryContent); return filtered; }); }; const clearEditedContent = () => { setEditedContentState({}); }; const getEditedContentValue = ( localDictionaryIdOrKey: LocalDictionaryId | Dictionary['key'] | string, keyPath: KeyPath[] ): ContentNode | undefined => { if (!editedContent) return undefined; const filteredKeyPath = keyPath.filter( (key) => key.type !== NodeType.Translation ); const isDictionaryId = localDictionaryIdOrKey.includes(':local:') || localDictionaryIdOrKey.includes(':remote:'); if (isDictionaryId) { const currentContent = editedContent?.[localDictionaryIdOrKey as LocalDictionaryId]?.content ?? {}; const contentNode = getContentNodeByKeyPath( currentContent, filteredKeyPath, currentLocale ); return contentNode; } const filteredDictionariesLocalId = Object.keys(editedContent).filter( (key) => key.startsWith(`${localDictionaryIdOrKey}:`) ); for (const localDictionaryId of filteredDictionariesLocalId) { const currentContent = editedContent?.[localDictionaryId as LocalDictionaryId]?.content ?? {}; const contentNode = getContentNodeByKeyPath( currentContent, filteredKeyPath, currentLocale ); if (contentNode) return contentNode; } return undefined; }; return ( <EditedContentStateContext.Provider value={{ editedContent, }} > <EditedContentActionsContext.Provider value={{ setEditedContentState, setEditedDictionary, setEditedContent, addEditedContent, renameEditedContent, removeEditedContent, restoreEditedContent, clearEditedDictionaryContent, clearEditedContent, getEditedContentValue, }} > {children} </EditedContentActionsContext.Provider> </EditedContentStateContext.Provider> ); }; export const useEditedContentActions = () => useContext(EditedContentActionsContext); export const useEditedContent = () => { const stateContext = useContext(EditedContentStateContext); const actionContext = useEditedContentActions(); return { ...stateContext, ...actionContext }; };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server