diff --git a/src/App.tsx b/src/App.tsx index 291095ae..60449991 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ import { Routes, Route, useSearchParams, useNavigate } from "react-router-dom"; import Navbar from "./components/Navbar"; import tour from "./components/Tour"; import useAppStore from "./store/store"; +import { shallow } from "zustand/shallow"; import LearnContent from "./components/Content"; import PlaygroundSidebar from "./components/PlaygroundSidebar"; import "./styles/App.css"; @@ -23,7 +24,7 @@ const App = () => { useAppStore((state) => ({ isAIConfigOpen: state.isAIConfigOpen, setAIConfigOpen: state.setAIConfigOpen, - })); + }), shallow); const backgroundColor = useAppStore((state) => state.backgroundColor); const textColor = useAppStore((state) => state.textColor); const [loading, setLoading] = useState(true); diff --git a/src/components/AIChatPanel.tsx b/src/components/AIChatPanel.tsx index 10778e9c..127834a8 100644 --- a/src/components/AIChatPanel.tsx +++ b/src/components/AIChatPanel.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState, useMemo } from "react"; import ReactMarkdown from "react-markdown"; import useAppStore from "../store/store"; +import { shallow } from "zustand/shallow"; import { sendMessage, stopMessage } from "../ai-assistant/chatRelay"; export const AIChatPanel = () => { @@ -13,7 +14,7 @@ export const AIChatPanel = () => { editorTemplateMark: state.editorValue, editorModelCto: state.editorModelCto, editorAgreementData: state.editorAgreementData, - })); + }), shallow); const { chatState, resetChat, aiConfig, setAIConfig, setAIConfigOpen, setAIChatOpen, textColor, backgroundColor } = useAppStore((state) => ({ chatState: state.chatState, @@ -24,7 +25,7 @@ export const AIChatPanel = () => { setAIChatOpen: state.setAIChatOpen, textColor: state.textColor, backgroundColor: state.backgroundColor - })); + }), shallow); const latestMessageRef = useRef(null); diff --git a/src/components/AIConfigPopup.tsx b/src/components/AIConfigPopup.tsx index 6a4eae61..01c310a1 100644 --- a/src/components/AIConfigPopup.tsx +++ b/src/components/AIConfigPopup.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useMemo, useCallback } from 'react'; import { AIConfig, AIConfigPopupProps, KeyProtectionLevel } from '../types/components/AIAssistant.types'; import { useDebounce } from 'use-debounce'; import useAppStore from '../store/store'; +import { shallow } from 'zustand/shallow'; import { isWebAuthnPRFSupported, encryptAndStoreApiKey, @@ -15,7 +16,7 @@ const AIConfigPopup = ({ isOpen, onClose }: AIConfigPopupProps) => { backgroundColor: state.backgroundColor, keyProtectionLevel: state.keyProtectionLevel, setKeyProtectionLevel: state.setKeyProtectionLevel, - })); + }), shallow); const theme = useMemo(() => { const isDarkMode = backgroundColor !== '#ffffff'; diff --git a/src/components/CodeSelectionMenu.tsx b/src/components/CodeSelectionMenu.tsx index d2df0c92..1f59a64b 100644 --- a/src/components/CodeSelectionMenu.tsx +++ b/src/components/CodeSelectionMenu.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { sendMessage } from '../ai-assistant/chatRelay'; import { CodeSelectionMenuProps } from '../types/components/AIAssistant.types'; import useAppStore from '../store/store'; +import { shallow } from 'zustand/shallow'; import ReactMarkdown from "react-markdown"; import * as monaco from 'monaco-editor'; @@ -48,7 +49,7 @@ const CodeSelectionMenu: React.FC = ({ editorTemplateMark: state.editorValue, editorModelCto: state.editorModelCto, editorAgreementData: state.editorAgreementData, - })); + }), shallow); const handleExplain = async () => { if (!aiConfig) { diff --git a/src/components/PlaygroundSidebar.tsx b/src/components/PlaygroundSidebar.tsx index 9731b266..82514896 100644 --- a/src/components/PlaygroundSidebar.tsx +++ b/src/components/PlaygroundSidebar.tsx @@ -5,6 +5,7 @@ import { FiTerminal, FiShare2, FiSettings } from "react-icons/fi"; import { FaCirclePlay } from "react-icons/fa6"; import { IoChatbubbleEllipsesOutline } from "react-icons/io5"; import useAppStore from "../store/store"; +import { shallow } from "zustand/shallow"; import { message, Tooltip } from "antd"; import FullScreenModal from "./FullScreenModal"; import SettingsModal from "./SettingsModal"; @@ -34,7 +35,7 @@ const PlaygroundSidebar = () => { setAIChatOpen: state.setAIChatOpen, generateShareableLink: state.generateShareableLink, setSettingsOpen: state.setSettingsOpen, - })); + }), shallow); const handleShare = async () => { try { @@ -163,6 +164,12 @@ const PlaygroundSidebar = () => { aria-label={title} tabIndex={0} onClick={onClick} + onKeyDown={(e) => { + if (onClick && (e.key === 'Enter' || e.key === ' ')) { + e.preventDefault(); + onClick(); + } + }} className={`group playground-sidebar-nav-item ${ active ? 'playground-sidebar-nav-item-active' : 'playground-sidebar-nav-item-inactive' } tour-${title.toLowerCase().replace(' ', '-')}`} @@ -188,6 +195,12 @@ const PlaygroundSidebar = () => { aria-label={title} tabIndex={0} onClick={onClick} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick(); + } + }} className={`group playground-sidebar-nav-bottom-item tour-${title.toLowerCase().replace(' ', '-')}`} > diff --git a/src/components/ProblemPanel.tsx b/src/components/ProblemPanel.tsx index cb23d382..8c33dd5a 100644 --- a/src/components/ProblemPanel.tsx +++ b/src/components/ProblemPanel.tsx @@ -1,5 +1,6 @@ import React, { useMemo, useCallback } from 'react'; import useAppStore from '../store/store'; +import { shallow } from 'zustand/shallow'; import { navigateToLine, EditorSource } from '../utils/editorNavigation'; import '../styles/components/ProblemPanel.css'; @@ -18,7 +19,7 @@ const ProblemPanel: React.FC = () => { error: state.error, backgroundColor: state.backgroundColor, textColor: state.textColor - })); + }), shallow); const parseError = (errorMessage: string) => { const errors: Omit[] = []; diff --git a/src/editors/ConcertoEditor.tsx b/src/editors/ConcertoEditor.tsx index c6d60204..6a596ab2 100644 --- a/src/editors/ConcertoEditor.tsx +++ b/src/editors/ConcertoEditor.tsx @@ -1,7 +1,8 @@ import { useMonaco } from "@monaco-editor/react"; -import { lazy, Suspense, useCallback, useEffect, useMemo, MutableRefObject } from "react"; +import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, MutableRefObject } from "react"; import * as monaco from "monaco-editor"; import useAppStore from "../store/store"; +import { shallow } from "zustand/shallow"; import { useCodeSelection } from "../components/CodeSelectionMenu"; import { registerAutocompletion } from "../ai-assistant/autocompletion"; import { registerEditor, unregisterEditor } from "../utils/editorNavigation"; @@ -124,12 +125,13 @@ export default function ConcertoEditor({ }: ConcertoEditorProps) { const { handleSelection, MenuComponent } = useCodeSelection("concerto"); const monacoInstance = useMonaco(); + const localEditorRef = useRef(null); const { error, backgroundColor, aiConfig, showLineNumbers } = useAppStore((state) => ({ error: state.error, backgroundColor: state.backgroundColor, aiConfig: state.aiConfig, showLineNumbers: state.showLineNumbers, - })); + }), shallow); const ctoErr = error?.startsWith("c:") ? error : undefined; const themeName = useMemo( @@ -165,6 +167,7 @@ export default function ConcertoEditor({ }), [aiConfig?.enableInlineSuggestions, showLineNumbers]); const handleEditorDidMount = (editor: monaco.editor.IStandaloneCodeEditor) => { + localEditorRef.current = editor; if (editorRef) { editorRef.current = editor; } @@ -190,7 +193,9 @@ export default function ConcertoEditor({ useEffect(() => { if (!monacoInstance) return; - const model = monacoInstance.editor.getModels()?.[0]; + const editor = localEditorRef.current; + if (!editor) return; + const model = editor.getModel(); if (!model) return; if (ctoErr) { diff --git a/src/editors/JSONEditor.tsx b/src/editors/JSONEditor.tsx index f208a45c..05455345 100644 --- a/src/editors/JSONEditor.tsx +++ b/src/editors/JSONEditor.tsx @@ -1,6 +1,7 @@ import { lazy, Suspense, useMemo, useCallback, useEffect } from "react"; import * as monaco from "monaco-editor"; import useAppStore from "../store/store"; +import { shallow } from "zustand/shallow"; import { useCodeSelection } from "../components/CodeSelectionMenu"; import { registerAutocompletion } from "../ai-assistant/autocompletion"; import { registerEditor, unregisterEditor } from "../utils/editorNavigation"; @@ -24,7 +25,7 @@ export default function JSONEditor({ backgroundColor: state.backgroundColor, aiConfig: state.aiConfig, showLineNumbers: state.showLineNumbers, - })); + }), shallow); const themeName = useMemo( () => (backgroundColor ? "darkTheme" : "lightTheme"), diff --git a/src/editors/MarkdownEditor.tsx b/src/editors/MarkdownEditor.tsx index ac46a008..8fb73dc2 100644 --- a/src/editors/MarkdownEditor.tsx +++ b/src/editors/MarkdownEditor.tsx @@ -1,5 +1,6 @@ import { lazy, Suspense, useMemo, useCallback, useEffect, MutableRefObject } from "react"; import useAppStore from "../store/store"; +import { shallow } from "zustand/shallow"; import { useMonaco } from "@monaco-editor/react"; import { useCodeSelection } from "../components/CodeSelectionMenu"; import type { editor } from "monaco-editor"; @@ -28,7 +29,7 @@ export default function MarkdownEditor({ textColor: state.textColor, aiConfig: state.aiConfig, showLineNumbers: state.showLineNumbers, - })); + }), shallow); const monaco = useMonaco(); const themeName = useMemo(