Просмотр исходного кода

migrate rest context swr hooks

Yuki Takei 6 месяцев назад
Родитель
Сommit
8c1f89254e

+ 4 - 5
apps/app/src/client/components/Admin/AuditLog/AuditLogSettings.tsx

@@ -1,22 +1,21 @@
 import type { FC } from 'react';
 import React, { useState } from 'react';
 
+import { useAtomValue } from 'jotai';
 import { useTranslation } from 'react-i18next';
 import { Collapse } from 'reactstrap';
 
 import { AllSupportedActions } from '~/interfaces/activity';
-import { useActivityExpirationSeconds, useAuditLogAvailableActions } from '~/stores-universal/context';
+import { activityExpirationSecondsAtom, auditLogAvailableActionsAtom } from '~/states/server-configurations';
 
 export const AuditLogSettings: FC = () => {
   const { t } = useTranslation();
 
   const [isExpandActionList, setIsExpandActionList] = useState(false);
 
-  const { data: activityExpirationSecondsData } = useActivityExpirationSeconds();
-  const activityExpirationSeconds = activityExpirationSecondsData != null ? activityExpirationSecondsData : 2592000;
+  const activityExpirationSeconds = useAtomValue(activityExpirationSecondsAtom) || 2592000;
 
-  const { data: availableActionsData } = useAuditLogAvailableActions();
-  const availableActions = availableActionsData != null ? availableActionsData : [];
+  const availableActions = useAtomValue(auditLogAvailableActionsAtom);
 
   return (
     <>

+ 4 - 3
apps/app/src/client/components/Admin/AuditLogManagement.tsx

@@ -3,12 +3,13 @@ import React, { useState, useCallback, useRef } from 'react';
 
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { format } from 'date-fns/format';
+import { useAtomValue } from 'jotai';
 import { useTranslation } from 'react-i18next';
 
 import type { IClearable } from '~/client/interfaces/clearable';
 import { toastError } from '~/client/util/toastr';
 import type { SupportedActionType } from '~/interfaces/activity';
-import { useAuditLogEnabled, useAuditLogAvailableActions } from '~/stores-universal/context';
+import { auditLogEnabledAtom, auditLogAvailableActionsAtom } from '~/states/server-configurations';
 import { useSWRxActivity } from '~/stores/activity';
 
 import PaginationWrapper from '../PaginationWrapper';
@@ -34,7 +35,7 @@ export const AuditLogManagement: FC = () => {
 
   const typeaheadRef = useRef<IClearable>(null);
 
-  const { data: auditLogAvailableActionsData } = useAuditLogAvailableActions();
+  const auditLogAvailableActionsData = useAtomValue(auditLogAvailableActionsAtom);
 
   /*
    * State
@@ -67,7 +68,7 @@ export const AuditLogManagement: FC = () => {
     toastError('Failed to get Audit Log');
   }
 
-  const { data: auditLogEnabled } = useAuditLogEnabled();
+  const auditLogEnabled = useAtomValue(auditLogEnabledAtom);
 
   /*
    * Functions

+ 15 - 13
apps/app/src/client/components/Admin/Customize/CustomizeLogoSetting.tsx

@@ -1,5 +1,6 @@
 import React, { useCallback, useState, type JSX } from 'react';
 
+import { useAtomValue, useSetAtom } from 'jotai';
 import { useTranslation } from 'react-i18next';
 
 import ImageCropModal from '~/client/components/Common/ImageCropModal';
@@ -8,7 +9,7 @@ import {
 } from '~/client/util/apiv3-client';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { useIsDefaultLogo } from '~/states/global';
-import { useIsCustomizedLogoUploaded } from '~/stores-universal/context';
+import { isCustomizedLogoUploadedAtom } from '~/states/server-configurations';
 
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
@@ -20,7 +21,8 @@ const CustomizeLogoSetting = (): JSX.Element => {
 
   const { t } = useTranslation();
   const isDefaultLogo = useIsDefaultLogo();
-  const { data: isCustomizedLogoUploaded, mutate: mutateIsCustomizedLogoUploaded } = useIsCustomizedLogoUploaded();
+  const isCustomizedLogoUploaded = useAtomValue(isCustomizedLogoUploadedAtom);
+  const setIsCustomizedLogoUploaded = useSetAtom(isCustomizedLogoUploadedAtom);
 
   const [uploadLogoSrc, setUploadLogoSrc] = useState<ArrayBuffer | string | null>(null);
   const [isImageCropModalShow, setIsImageCropModalShow] = useState<boolean>(false);
@@ -36,7 +38,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
     }
   }, []);
 
-  const onClickSubmit = useCallback(async() => {
+  const onClickSubmit = useCallback(async () => {
     try {
       await apiv3Put('/customize-setting/customize-logo', { isDefaultLogo: isDefaultLogoSelected });
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_logo'), ns: 'commons' }));
@@ -46,10 +48,10 @@ const CustomizeLogoSetting = (): JSX.Element => {
     }
   }, [t, isDefaultLogoSelected]);
 
-  const onClickDeleteBtn = useCallback(async() => {
+  const onClickDeleteBtn = useCallback(async () => {
     try {
       await apiv3Delete('/customize-setting/delete-brand-logo');
-      mutateIsCustomizedLogoUploaded(false);
+      setIsCustomizedLogoUploaded(false);
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
     }
     catch (err) {
@@ -57,15 +59,15 @@ const CustomizeLogoSetting = (): JSX.Element => {
       setRetrieveError(err);
       throw new Error('Failed to delete logo');
     }
-  }, [mutateIsCustomizedLogoUploaded, t]);
+  }, [setIsCustomizedLogoUploaded, t]);
 
 
-  const processImageCompletedHandler = useCallback(async(croppedImage) => {
+  const processImageCompletedHandler = useCallback(async (croppedImage) => {
     try {
       const formData = new FormData();
       formData.append('file', croppedImage);
       await apiv3PostForm('/customize-setting/upload-brand-logo', formData);
-      mutateIsCustomizedLogoUploaded(true);
+      setIsCustomizedLogoUploaded(true);
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
     }
     catch (err) {
@@ -73,7 +75,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
       setRetrieveError(err);
       throw new Error('Failed to upload brand logo');
     }
-  }, [mutateIsCustomizedLogoUploaded, t]);
+  }, [setIsCustomizedLogoUploaded, t]);
 
   return (
     <React.Fragment>
@@ -114,13 +116,13 @@ const CustomizeLogoSetting = (): JSX.Element => {
                       onChange={() => { setIsDefaultLogoSelected(false) }}
                     />
                     <label className="form-check-label" htmlFor="radioUploadLogo">
-                      { t('admin:customize_settings.upload_logo') }
+                      {t('admin:customize_settings.upload_logo')}
                     </label>
                   </div>
                 </h4>
                 <div className="row mb-3">
                   <label className="col-sm-4 col-12 col-form-label text-start">
-                    { t('admin:customize_settings.current_logo') }
+                    {t('admin:customize_settings.current_logo')}
                   </label>
                   <div className="col-sm-8 col-12">
                     {isCustomizedLogoUploaded && (
@@ -129,7 +131,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
                           <img src={CUSTOMIZED_LOGO} id="settingBrandLogo" width="64" />
                         </p>
                         <button type="button" className="btn btn-danger" onClick={onClickDeleteBtn}>
-                          { t('admin:customize_settings.delete_logo') }
+                          {t('admin:customize_settings.delete_logo')}
                         </button>
                       </>
                     )}
@@ -137,7 +139,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
                 </div>
                 <div className="row">
                   <label className="col-sm-4 col-12 col-form-label text-start">
-                    { t('admin:customize_settings.upload_new_logo') }
+                    {t('admin:customize_settings.upload_new_logo')}
                   </label>
                   <div className="col-sm-8 col-12">
                     <input type="file" onChange={onSelectFile} name="brandLogo" accept="image/*" />

+ 3 - 4
apps/app/src/client/components/PageEditor/PageEditor.tsx

@@ -23,6 +23,7 @@ import { useUpdateStateAfterSave } from '~/client/services/page-operation';
 import { useUpdatePage, extractRemoteRevisionDataFromErrorObj } from '~/client/services/update-page';
 import { uploadAttachments } from '~/client/services/upload-attachments';
 import { toastError, toastSuccess, toastWarning } from '~/client/util/toastr';
+import { useIsEnableUnifiedMergeView } from '~/features/openai/client/states';
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
 import { useCurrentPathname, useCurrentUser } from '~/states/global';
 import {
@@ -42,9 +43,7 @@ import {
 import {
   useEditorMode, EditorMode, useEditingMarkdown, useSelectedGrant,
 } from '~/states/ui/editor';
-import {
-  useAcceptedUploadFileType, useIsEnableUnifiedMergeView,
-} from '~/stores-universal/context';
+import { useAcceptedUploadFileType } from '~/stores-universal/context';
 import { useNextThemes } from '~/stores-universal/use-next-themes';
 import {
   useReservedNextCaretLine,
@@ -123,7 +122,7 @@ export const PageEditorSubstance = (props: Props): JSX.Element => {
   const { mutate: mutateEditingUsers } = useEditingClients();
   const onConflict = useConflictResolver();
   const { data: reservedNextCaretLine, mutate: mutateReservedNextCaretLine } = useReservedNextCaretLine();
-  const { data: isEnableUnifiedMergeView } = useIsEnableUnifiedMergeView();
+  const isEnableUnifiedMergeView = useIsEnableUnifiedMergeView();
 
   const { data: rendererOptions } = usePreviewOptions();
 

+ 19 - 19
apps/app/src/features/openai/client/components/AiAssistant/AiAssistantSidebar/AiAssistantSidebar.tsx

@@ -10,7 +10,6 @@ import SimpleBar from 'simplebar-react';
 
 import { toastError } from '~/client/util/toastr';
 import { useGrowiCloudUri } from '~/states/global';
-import { useIsEnableUnifiedMergeView } from '~/stores-universal/context';
 import loggerFactory from '~/utils/logger';
 
 import type { AiAssistantHasId } from '../../../../interfaces/ai-assistant';
@@ -27,6 +26,7 @@ import {
   useFetchAndSetMessageDataEffect,
   type FormData as FormDataForKnowledgeAssistant,
 } from '../../../services/knowledge-assistant';
+import { useUnifiedMergeViewActions } from '../../../states';
 import { useAiAssistantSidebar } from '../../../stores/ai-assistant';
 import { useSWRxThreads } from '../../../stores/thread';
 
@@ -117,7 +117,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
     resetFormForKnowledgeAssistant();
   }, [isEditorAssistant, resetFormEditorAssistant, resetFormForKnowledgeAssistant]);
 
-  const createThread = useCallback(async(initialUserMessage: string) => {
+  const createThread = useCallback(async (initialUserMessage: string) => {
     if (isEditorAssistant) {
       const thread = await createThreadForEditorAssistant();
       return thread;
@@ -130,7 +130,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
     return thread;
   }, [aiAssistantData, createThreadForEditorAssistant, createThreadForKnowledgeAssistant, isEditorAssistant]);
 
-  const postMessage = useCallback(async(threadId: string, formData: FormData) => {
+  const postMessage = useCallback(async (threadId: string, formData: FormData) => {
     if (threadId == null) {
       throw new Error('threadId is not set');
     }
@@ -156,7 +156,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
   }, [aiAssistantData?._id, isEditorAssistant, postMessageForEditorAssistant, postMessageForKnowledgeAssistant]);
 
   const isGenerating = generatingAnswerMessage != null;
-  const submitSubstance = useCallback(async(data: FormData) => {
+  const submitSubstance = useCallback(async (data: FormData) => {
     // do nothing when the assistant is generating an answer
     if (isGenerating) {
       return;
@@ -230,7 +230,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
       const reader = response.body?.getReader();
       const decoder = new TextDecoder('utf-8');
 
-      const read = async() => {
+      const read = async () => {
         if (reader == null) return;
 
         const { done, value } = await reader.read();
@@ -330,7 +330,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
       form.setError('input', { type: 'manual', message: err.toString() });
     }
 
-  // eslint-disable-next-line max-len
+    // eslint-disable-next-line max-len
   }, [isGenerating, messageLogs, resetForm, threadData?.threadId, createThread, onNewThreadCreated, t, postMessage, form, onMessageReceived, processMessageForKnowledgeAssistant, processMessageForEditorAssistant, growiCloudUri]);
 
   const submit = useCallback((data: FormData) => {
@@ -434,10 +434,10 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
             autoHide
           >
             <div className="p-4 d-flex flex-column gap-4 flex-grow-1">
-              { threadData != null
+              {threadData != null
                 ? (
                   <div className="vstack gap-4 pb-2">
-                    { messageLogs.map(message => (
+                    {messageLogs.map(message => (
                       <>
                         <MessageCard
                           role={message.isUserMessage ? 'user' : 'assistant'}
@@ -446,8 +446,8 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
                           {message.content}
                         </MessageCard>
                       </>
-                    )) }
-                    { generatingAnswerMessage != null && (
+                    ))}
+                    {generatingAnswerMessage != null && (
                       <MessageCard
                         role="assistant"
                         additionalItem={messageCardAdditionalItemForGeneratingMessage}
@@ -455,8 +455,8 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
                         {generatingAnswerMessage.content}
                       </MessageCard>
                     )}
-                    { isEditorAssistant && partialContentWarnLabel }
-                    { messageLogs.length > 0 && (
+                    {isEditorAssistant && partialContentWarnLabel}
+                    {messageLogs.length > 0 && (
                       <div className="d-flex justify-content-center">
                         <span className="bg-body-tertiary text-body-secondary rounded-pill px-3 py-1" style={{ fontSize: 'smaller' }}>
                           {t('sidebar_ai_assistant.caution_against_hallucination')}
@@ -466,7 +466,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
                   </div>
                 )
                 : (
-                  <>{ initialView }</>
+                  <>{initialView}</>
                 )
               }
             </div>
@@ -492,8 +492,8 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
               )}
             />
             <div className="flex-fill hstack gap-2 justify-content-between m-0">
-              { !isEditorAssistant && generateModeSwitchesDropdownForKnowledgeAssistant(isGenerating) }
-              { isEditorAssistant && <div /> }
+              {!isEditorAssistant && generateModeSwitchesDropdownForKnowledgeAssistant(isGenerating)}
+              {isEditorAssistant && <div />}
               <button
                 type="submit"
                 className="btn btn-submit no-border"
@@ -508,7 +508,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
             <div className="mt-4 bg-danger bg-opacity-10 rounded-3 p-2 w-100">
               <div>
                 <span className="material-symbols-outlined text-danger me-2">error</span>
-                <span className="text-danger">{ errorMessage != null ? t(errorMessage) : t('sidebar_ai_assistant.error_message') }</span>
+                <span className="text-danger">{errorMessage != null ? t(errorMessage) : t('sidebar_ai_assistant.error_message')}</span>
               </div>
 
               <button
@@ -543,7 +543,7 @@ const AiAssistantSidebarSubstance: React.FC<AiAssistantSidebarSubstanceProps> =
 
 export const AiAssistantSidebar: FC = memo((): JSX.Element => {
   const { data: aiAssistantSidebarData, close: closeAiAssistantSidebar, refreshThreadData } = useAiAssistantSidebar();
-  const { mutate: mutateIsEnableUnifiedMergeView } = useIsEnableUnifiedMergeView();
+  const { disable: disableUnifiedMergeView } = useUnifiedMergeViewActions();
 
   const aiAssistantData = aiAssistantSidebarData?.aiAssistantData;
   const threadData = aiAssistantSidebarData?.threadData;
@@ -558,9 +558,9 @@ export const AiAssistantSidebar: FC = memo((): JSX.Element => {
 
   useEffect(() => {
     if (!aiAssistantSidebarData?.isOpened) {
-      mutateIsEnableUnifiedMergeView(false);
+      disableUnifiedMergeView();
     }
-  }, [aiAssistantSidebarData?.isOpened, mutateIsEnableUnifiedMergeView]);
+  }, [aiAssistantSidebarData?.isOpened, disableUnifiedMergeView]);
 
   // refresh thread data when the data is changed
   useEffect(() => {

+ 18 - 16
apps/app/src/features/openai/client/services/editor-assistant/use-editor-assistant.tsx

@@ -14,7 +14,7 @@ import { type Text as YText } from 'yjs';
 
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { useCurrentPageId } from '~/states/page';
-import { useIsEnableUnifiedMergeView } from '~/stores-universal/context';
+
 
 import type { AiAssistantHasId } from '../../../interfaces/ai-assistant';
 import {
@@ -32,6 +32,7 @@ import { ThreadType } from '../../../interfaces/thread-relation';
 import { handleIfSuccessfullyParsed } from '../../../utils/handle-if-successfully-parsed';
 import { AiAssistantDropdown } from '../../components/AiAssistant/AiAssistantSidebar/AiAssistantDropdown';
 import { QuickMenuList } from '../../components/AiAssistant/AiAssistantSidebar/QuickMenuList';
+import { useIsEnableUnifiedMergeView, useUnifiedMergeViewActions } from '../../states';
 import { useAiAssistantSidebar } from '../../stores/ai-assistant';
 import { useClientEngineIntegration, shouldUseClientProcessing } from '../client-engine-integration';
 
@@ -140,7 +141,8 @@ export const useEditorAssistant: UseEditorAssistant = () => {
   // Hooks
   const { t } = useTranslation();
   const currentPageId = useCurrentPageId();
-  const { data: isEnableUnifiedMergeView, mutate: mutateIsEnableUnifiedMergeView } = useIsEnableUnifiedMergeView();
+  const isEnableUnifiedMergeView = useIsEnableUnifiedMergeView();
+  const { disable: disableUnifiedMergeView, enable: enableUnifiedMergeView } = useUnifiedMergeViewActions();
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
   const yDocs = useSecondaryYdocs(isEnableUnifiedMergeView ?? false, { pageId: currentPageId ?? undefined, useSecondary: isEnableUnifiedMergeView ?? false });
   const { data: aiAssistantSidebarData } = useAiAssistantSidebar();
@@ -161,7 +163,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
     form.reset({ input: '' });
   }, [form]);
 
-  const createThread: CreateThread = useCallback(async() => {
+  const createThread: CreateThread = useCallback(async () => {
     const response = await apiv3Post<IThreadRelationHasId>('/openai/thread', {
       type: ThreadType.EDITOR,
       aiAssistantId: selectedAiAssistant?._id,
@@ -169,12 +171,12 @@ export const useEditorAssistant: UseEditorAssistant = () => {
     return response.data;
   }, [selectedAiAssistant?._id]);
 
-  const postMessage: PostMessage = useCallback(async({ threadId, formData }) => {
+  const postMessage: PostMessage = useCallback(async ({ threadId, formData }) => {
     // Clear partial content info on new request
     setPartialContentInfo(null);
 
     // Disable UnifiedMergeView when a Form is submitted with UnifiedMergeView enabled
-    mutateIsEnableUnifiedMergeView(false);
+    disableUnifiedMergeView();
 
     const pageBodyContext = getPageBodyForContext(codeMirrorEditor, 2000, 8000);
 
@@ -212,18 +214,18 @@ export const useEditorAssistant: UseEditorAssistant = () => {
     });
 
     return response;
-  }, [codeMirrorEditor, mutateIsEnableUnifiedMergeView, selectedAiAssistant?._id, selectedText, selectedTextIndex]);
+  }, [codeMirrorEditor, disableUnifiedMergeView, selectedAiAssistant?._id, selectedText, selectedTextIndex]);
 
 
   // Enhanced processMessage with client engine support (保持)
-  const processMessage = useCallback(async(data: unknown, handler: {
+  const processMessage = useCallback(async (data: unknown, handler: {
     onMessage: (data: SseMessage) => void;
     onDetectedDiff: (data: SseDetectedDiff) => void;
     onFinalized: (data: SseFinalized) => void;
   }) => {
     // Reset timer whenever data is received
     const handleDataReceived = () => {
-    // Clear existing timer
+      // Clear existing timer
       if (timerRef.current != null) {
         clearTimeout(timerRef.current);
       }
@@ -244,9 +246,9 @@ export const useEditorAssistant: UseEditorAssistant = () => {
       handler.onMessage(data);
     });
 
-    handleIfSuccessfullyParsed(data, SseDetectedDiffSchema, async(diffData: SseDetectedDiff) => {
+    handleIfSuccessfullyParsed(data, SseDetectedDiffSchema, async (diffData: SseDetectedDiff) => {
       handleDataReceived();
-      mutateIsEnableUnifiedMergeView(true);
+      enableUnifiedMergeView();
 
       // Check if client engine processing is enabled
       if (clientEngine.isClientProcessingEnabled && yDocs?.secondaryDoc != null) {
@@ -259,7 +261,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
           const result = await clientEngine.processHybrid(
             currentContent,
             [diffData],
-            async() => {
+            async () => {
               // Fallback to original server-side processing
               setDetectedDiff((prev) => {
                 const newData = { data: diffData, applied: false, id: crypto.randomUUID() };
@@ -299,7 +301,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
     handleIfSuccessfullyParsed(data, SseFinalizedSchema, (data: SseFinalized) => {
       handler.onFinalized(data);
     });
-  }, [isGeneratingEditorText, mutateIsEnableUnifiedMergeView, clientEngine, yDocs]);
+  }, [isGeneratingEditorText, enableUnifiedMergeView, clientEngine, yDocs]);
 
   const selectTextHandler = useCallback(({ selectedText, selectedTextIndex, selectedTextFirstLineNumber }) => {
     setSelectedText(selectedText);
@@ -384,7 +386,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
       setSelectedAiAssistant(aiAssistant);
     };
 
-    const clickQuickMenuHandler = async(quickMenu: string) => {
+    const clickQuickMenuHandler = async (quickMenu: string) => {
       await onSubmit({ input: quickMenu, markdownType: 'full' });
     };
 
@@ -434,11 +436,11 @@ export const useEditorAssistant: UseEditorAssistant = () => {
       }
 
       acceptAllChunks(codeMirrorEditor.view);
-      mutateIsEnableUnifiedMergeView(false);
+      disableUnifiedMergeView();
     };
 
     const reject = () => {
-      mutateIsEnableUnifiedMergeView(false);
+      disableUnifiedMergeView();
     };
 
     if (!isActionButtonShown) {
@@ -463,7 +465,7 @@ export const useEditorAssistant: UseEditorAssistant = () => {
         </button>
       </div>
     );
-  }, [aiAssistantSidebarData?.isEditorAssistant, codeMirrorEditor?.view, isEnableUnifiedMergeView, mutateIsEnableUnifiedMergeView, t]);
+  }, [aiAssistantSidebarData?.isEditorAssistant, codeMirrorEditor?.view, isEnableUnifiedMergeView, disableUnifiedMergeView, t]);
 
   const generatingEditorTextLabel = useMemo(() => {
     return (

+ 1 - 0
apps/app/src/features/openai/client/states/index.ts

@@ -0,0 +1 @@
+export { useIsEnableUnifiedMergeView, useUnifiedMergeViewActions } from './unified-merge-view';

+ 36 - 0
apps/app/src/features/openai/client/states/unified-merge-view.ts

@@ -0,0 +1,36 @@
+import { useCallback } from 'react';
+
+import { atom, useAtomValue, useSetAtom } from 'jotai';
+
+// Type definitions
+export type UnifiedMergeViewActions = {
+  enable: () => void;
+  disable: () => void;
+};
+
+// Atom definition
+const isEnableUnifiedMergeViewAtom = atom<boolean>(false);
+
+/**
+ * Hook to get the current unified merge view state
+ */
+export const useIsEnableUnifiedMergeView = (): boolean => {
+  return useAtomValue(isEnableUnifiedMergeViewAtom);
+};
+
+/**
+ * Hook to get actions for unified merge view state
+ */
+export const useUnifiedMergeViewActions = (): UnifiedMergeViewActions => {
+  const setIsEnabled = useSetAtom(isEnableUnifiedMergeViewAtom);
+
+  const enable = useCallback(() => {
+    setIsEnabled(true);
+  }, [setIsEnabled]);
+
+  const disable = useCallback(() => {
+    setIsEnabled(false);
+  }, [setIsEnabled]);
+
+  return { enable, disable };
+};

+ 0 - 53
apps/app/src/stores-universal/context.tsx

@@ -1,72 +1,19 @@
 import type EventEmitter from 'node:events';
 import { AcceptedUploadFileType } from '@growi/core';
-import { useSWRStatic } from '@growi/core/dist/swr';
 import { useAtomValue } from 'jotai';
 import type { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
-import type { SupportedActionType } from '~/interfaces/activity';
 import {
   isUploadAllFileAllowedAtom,
   isUploadEnabledAtom,
 } from '~/states/server-configurations';
 
-import { useContextSWR } from './use-context-swr';
-
 declare global {
   // eslint-disable-next-line vars-on-top, no-var
   var globalEmitter: EventEmitter;
 }
 
-export const useAuditLogEnabled = (
-  initialData?: boolean,
-): SWRResponse<boolean, Error> => {
-  return useContextSWR<boolean, Error>('auditLogEnabled', initialData, {
-    fallbackData: false,
-  });
-};
-
-export const useActivityExpirationSeconds = (
-  initialData?: number,
-): SWRResponse<number, Error> => {
-  return useContextSWR<number, Error>('activityExpirationSeconds', initialData);
-};
-
-export const useAuditLogAvailableActions = (
-  initialData?: Array<SupportedActionType>,
-): SWRResponse<Array<SupportedActionType>, Error> => {
-  return useContextSWR<Array<SupportedActionType>, Error>(
-    'auditLogAvailableActions',
-    initialData,
-  );
-};
-
-export const useIsBlinkedHeaderAtBoot = (
-  initialData?: boolean,
-): SWRResponse<boolean, Error> => {
-  return useContextSWR('isBlinkedAtBoot', initialData, { fallbackData: false });
-};
-
-export const useCustomizeTitle = (
-  initialData?: string,
-): SWRResponse<string, Error> => {
-  return useContextSWR('CustomizeTitle', initialData);
-};
-
-export const useIsCustomizedLogoUploaded = (
-  initialData?: boolean,
-): SWRResponse<boolean, Error> => {
-  return useSWRStatic('isCustomizedLogoUploaded', initialData);
-};
-
-export const useIsEnableUnifiedMergeView = (
-  initialData?: boolean,
-): SWRResponse<boolean, Error> => {
-  return useSWRStatic<boolean, Error>('isEnableUnifiedMergeView', initialData, {
-    fallbackData: false,
-  });
-};
-
 /** **********************************************************
  *                     Computed contexts
  *********************************************************** */

+ 3 - 2
apps/app/src/stores/activity.ts

@@ -1,13 +1,14 @@
+import { useAtomValue } from 'jotai';
 import type { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 import type { IActivityHasId, ISearchFilter } from '~/interfaces/activity';
 import type { PaginateResult } from '~/interfaces/mongoose-utils';
-import { useAuditLogEnabled } from '~/stores-universal/context';
+import { auditLogEnabledAtom } from '~/states/server-configurations';
 
 export const useSWRxActivity = (limit?: number, offset?: number, searchFilter?: ISearchFilter): SWRResponse<PaginateResult<IActivityHasId>, Error> => {
-  const { data: auditLogEnabled } = useAuditLogEnabled();
+  const auditLogEnabled = useAtomValue(auditLogEnabledAtom);
 
   const stringifiedSearchFilter = JSON.stringify(searchFilter);
   return useSWRImmutable(