diff --git a/.pnp.cjs b/.pnp.cjs index 89de913be..d46614471 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6634,6 +6634,7 @@ const RAW_RUNTIME_STATE = ["fuse.js", "npm:6.6.2"],\ ["immer", "npm:10.0.3"],\ ["jest-diff", "npm:29.7.0"],\ + ["jotai", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:2.11.0"],\ ["jsonpath-plus", "npm:7.2.0"],\ ["lodash-es", "npm:4.17.21"],\ ["lru-cache", "npm:11.0.0"],\ @@ -6664,8 +6665,6 @@ const RAW_RUNTIME_STATE = ["react-toastify", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:9.1.3"],\ ["react-transition-group", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:4.4.5"],\ ["react-window", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:1.8.9"],\ - ["recoil", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:0.7.7"],\ - ["recoil-persist", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:5.1.0"],\ ["retry", "npm:0.13.1"],\ ["rollup", "npm:4.17.2"],\ ["rollup-plugin-visualizer", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:5.9.2"],\ @@ -17027,15 +17026,6 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ - ["hamt_plus", [\ - ["npm:1.0.2", {\ - "packageLocation": "./.yarn/cache/hamt_plus-npm-1.0.2-67a52ee1df-3680a1820b.zip/node_modules/hamt_plus/",\ - "packageDependencies": [\ - ["hamt_plus", "npm:1.0.2"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["handle-thing", [\ ["npm:2.0.1", {\ "packageLocation": "./.yarn/cache/handle-thing-npm-2.0.1-084baca59e-441ec98b07.zip/node_modules/handle-thing/",\ @@ -18577,6 +18567,28 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["jotai", [\ + ["npm:2.11.0", {\ + "packageLocation": "./.yarn/cache/jotai-npm-2.11.0-f1bd5ea76c-607c2ab783.zip/node_modules/jotai/",\ + "packageDependencies": [\ + ["jotai", "npm:2.11.0"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:2.11.0", {\ + "packageLocation": "./.yarn/__virtual__/jotai-virtual-44bbcaebaa/0/cache/jotai-npm-2.11.0-f1bd5ea76c-607c2ab783.zip/node_modules/jotai/",\ + "packageDependencies": [\ + ["jotai", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:2.11.0"],\ + ["@types/react", "npm:18.2.31"],\ + ["react", "npm:18.2.0"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["js-cookie", [\ ["npm:2.2.1", {\ "packageLocation": "./.yarn/cache/js-cookie-npm-2.2.1-e879cd2148-4387f5f569.zip/node_modules/js-cookie/",\ @@ -23298,59 +23310,6 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ - ["recoil", [\ - ["npm:0.7.7", {\ - "packageLocation": "./.yarn/cache/recoil-npm-0.7.7-4452f58b67-4ac9dfeddd.zip/node_modules/recoil/",\ - "packageDependencies": [\ - ["recoil", "npm:0.7.7"]\ - ],\ - "linkType": "SOFT"\ - }],\ - ["virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:0.7.7", {\ - "packageLocation": "./.yarn/__virtual__/recoil-virtual-48d8d5fa57/0/cache/recoil-npm-0.7.7-4452f58b67-4ac9dfeddd.zip/node_modules/recoil/",\ - "packageDependencies": [\ - ["recoil", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:0.7.7"],\ - ["@types/react", "npm:18.2.31"],\ - ["@types/react-dom", "npm:18.2.14"],\ - ["@types/react-native", null],\ - ["hamt_plus", "npm:1.0.2"],\ - ["react", "npm:18.2.0"],\ - ["react-dom", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:18.2.0"],\ - ["react-native", null]\ - ],\ - "packagePeers": [\ - "@types/react-dom",\ - "@types/react-native",\ - "@types/react",\ - "react-dom",\ - "react-native",\ - "react"\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ - ["recoil-persist", [\ - ["npm:5.1.0", {\ - "packageLocation": "./.yarn/cache/recoil-persist-npm-5.1.0-6c9c3d1452-979a199862.zip/node_modules/recoil-persist/",\ - "packageDependencies": [\ - ["recoil-persist", "npm:5.1.0"]\ - ],\ - "linkType": "SOFT"\ - }],\ - ["virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:5.1.0", {\ - "packageLocation": "./.yarn/__virtual__/recoil-persist-virtual-eceea532ff/0/cache/recoil-persist-npm-5.1.0-6c9c3d1452-979a199862.zip/node_modules/recoil-persist/",\ - "packageDependencies": [\ - ["recoil-persist", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:5.1.0"],\ - ["@types/recoil", null],\ - ["recoil", "virtual:388c29633752d7c364e0487c276ae72861ce5d69c069bff16a49b35801303d87d39cb24723bbac1721c48df59f346575324fe3c6de8ead4fb7d83d6ae4a0e521#npm:0.7.7"]\ - ],\ - "packagePeers": [\ - "@types/recoil",\ - "recoil"\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["recursive-readdir", [\ ["npm:2.2.3", {\ "packageLocation": "./.yarn/cache/recursive-readdir-npm-2.2.3-3f177ebd90-19298852b0.zip/node_modules/recursive-readdir/",\ diff --git a/packages/app/package.json b/packages/app/package.json index 3dcc1e5e8..30877a159 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -88,6 +88,7 @@ "fuse.js": "^6.6.2", "immer": "^10.0.3", "jest-diff": "^29.7.0", + "jotai": "^2.11.0", "jsonpath-plus": "^7.2.0", "lodash-es": "^4.17.21", "lru-cache": "^11.0.0", @@ -118,8 +119,6 @@ "react-toastify": "^9.1.3", "react-transition-group": "^4.4.5", "react-window": "^1.8.9", - "recoil": "^0.7.7", - "recoil-persist": "^5.1.0", "retry": "^0.13.1", "rollup": "^4.1.4", "safe-stable-stringify": "^2.4.3", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index dd038bf6f..fa1b43fee 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -1,5 +1,4 @@ import 'core-js/actual'; -import { RecoilRoot } from 'recoil'; import '@atlaskit/css-reset'; import { RivetApp } from './components/RivetApp.js'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -8,11 +7,9 @@ const queryClient = new QueryClient(); function App() { return ( - - - - - + + + ); } diff --git a/packages/app/src/components/ActionBar.tsx b/packages/app/src/components/ActionBar.tsx index 9a15e805a..e40763395 100644 --- a/packages/app/src/components/ActionBar.tsx +++ b/packages/app/src/components/ActionBar.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/react'; import clsx from 'clsx'; import { type FC } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { useLoadRecording } from '../hooks/useLoadRecording'; import { useSaveRecording } from '../hooks/useSaveRecording'; import { graphRunningState, graphPausedState } from '../state/dataFlow'; @@ -134,18 +134,18 @@ export type ActionBarProps = { }; export const ActionBar: FC = ({ onRunGraph, onAbortGraph, onPauseGraph, onResumeGraph }) => { - const graphMetadata = useRecoilValue(graphMetadataState); - const projectMetadata = useRecoilValue(projectMetadataState); - const lastRecording = useRecoilValue(lastRecordingState); + const graphMetadata = useAtomValue(graphMetadataState); + const projectMetadata = useAtomValue(projectMetadataState); + const lastRecording = useAtomValue(lastRecordingState); const saveRecording = useSaveRecording(); - const graphRunning = useRecoilValue(graphRunningState); - const graphPaused = useRecoilValue(graphPausedState); + const graphRunning = useAtomValue(graphRunningState); + const graphPaused = useAtomValue(graphPausedState); - const loadedRecording = useRecoilValue(loadedRecordingState); + const loadedRecording = useAtomValue(loadedRecordingState); const { unloadRecording } = useLoadRecording(); const [menuIsOpen, toggleMenuIsOpen] = useToggle(); - const selectedExecutor = useRecoilValue(selectedExecutorState); + const selectedExecutor = useAtomValue(selectedExecutorState); const { remoteDebuggerState: remoteDebugger, disconnect } = useRemoteDebugger(); const isActuallyRemoteDebugging = remoteDebugger.started && !remoteDebugger.isInternalExecutor; diff --git a/packages/app/src/components/ActionBarMoreMenu.tsx b/packages/app/src/components/ActionBarMoreMenu.tsx index bd7724c29..5656d794c 100644 --- a/packages/app/src/components/ActionBarMoreMenu.tsx +++ b/packages/app/src/components/ActionBarMoreMenu.tsx @@ -2,7 +2,6 @@ import Portal from '@atlaskit/portal'; import Select from '@atlaskit/select'; import { css } from '@emotion/react'; import { type FC, useRef } from 'react'; -import { useSetRecoilState, useRecoilState } from 'recoil'; import { useLoadRecording } from '../hooks/useLoadRecording'; import { useRemoteDebugger } from '../hooks/useRemoteDebugger'; import { selectedExecutorState } from '../state/execution'; @@ -18,6 +17,7 @@ import { CopyAsTestCaseModal } from './CopyAsTestCaseModal'; import { useToggle } from 'ahooks'; import { executorOptions } from '../state/settings'; import QuestionIcon from 'majesticons/line/question-circle-line.svg?react'; +import { useSetAtom, useAtom } from 'jotai'; const moreMenuStyles = css` background-color: var(--grey-darkish); @@ -77,12 +77,12 @@ export const ActionBarMoreMenu: FC<{ onCopyAsTestCase: () => void; }> = ({ onClose, onCopyAsTestCase }) => { const dropdownTarget = useRef(null); - const setSettingsOpen = useSetRecoilState(settingsModalOpenState); - const setDebuggerPanelOpen = useSetRecoilState(debuggerPanelOpenState); - const [selectedExecutor, setSelectedExecutor] = useRecoilState(selectedExecutorState); + const setSettingsOpen = useSetAtom(settingsModalOpenState); + const setDebuggerPanelOpen = useSetAtom(debuggerPanelOpenState); + const [selectedExecutor, setSelectedExecutor] = useAtom(selectedExecutorState); const selectedExecutorOption = executorOptions.find((option) => option.value === selectedExecutor); const { loadRecording } = useLoadRecording(); - const setHelpModalOpen = useSetRecoilState(helpModalOpenState); + const setHelpModalOpen = useSetAtom(helpModalOpenState); const openDebuggerPanel = () => { setDebuggerPanelOpen(true); diff --git a/packages/app/src/components/ChatViewer.tsx b/packages/app/src/components/ChatViewer.tsx index f7fa0819a..1b978916a 100644 --- a/packages/app/src/components/ChatViewer.tsx +++ b/packages/app/src/components/ChatViewer.tsx @@ -1,5 +1,5 @@ import { type FC, useLayoutEffect, useMemo, useRef, useState, type CSSProperties } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtomValue, useAtom } from 'jotai'; import { orderBy } from 'lodash-es'; import { overlayOpenState } from '../state/ui'; import { css } from '@emotion/react'; @@ -33,7 +33,7 @@ import { FixedSizeList } from 'react-window'; import { useCurrentExecution } from '../hooks/useCurrentExecution'; export const ChatViewerRenderer: FC = () => { - const [openOverlay, setOpenOverlay] = useRecoilState(overlayOpenState); + const [openOverlay, setOpenOverlay] = useAtom(overlayOpenState); if (openOverlay !== 'chatViewer') return null; @@ -200,11 +200,11 @@ const styles = css` export const ChatViewer: FC<{ onClose: () => void; }> = ({ onClose }) => { - const project = useRecoilValue(projectState); - const allLastRunData = useRecoilValue(lastRunDataByNodeState); + const project = useAtomValue(projectState); + const allLastRunData = useAtomValue(lastRunDataByNodeState); const [graphFilter, setGraphFilter] = useState(''); const goToNode = useGoToNode(); - const graphRunning = useRecoilValue(graphRunningState); + const graphRunning = useAtomValue(graphRunningState); const nodesToGraphNameMap = useMemo(() => { const map: Record = {}; diff --git a/packages/app/src/components/CodeEditor.tsx b/packages/app/src/components/CodeEditor.tsx index 9ecf0b2d9..dbe83b746 100644 --- a/packages/app/src/components/CodeEditor.tsx +++ b/packages/app/src/components/CodeEditor.tsx @@ -1,7 +1,7 @@ import { useLatest } from 'ahooks'; import { type FC, type MutableRefObject, useEffect, useRef } from 'react'; import { monaco } from '../utils/monaco.js'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { themeState } from '../state/settings'; export const CodeEditor: FC<{ @@ -32,7 +32,7 @@ export const CodeEditor: FC<{ const onChangeLatest = useLatest(onChange); - const appTheme = useRecoilValue(themeState); + const appTheme = useAtomValue(themeState); const actualTheme = theme === 'prompt-interpolation' ? `prompt-interpolation-${appTheme}` : theme; useEffect(() => { diff --git a/packages/app/src/components/ColorizedPreformattedText.tsx b/packages/app/src/components/ColorizedPreformattedText.tsx index 9b1b2e3d2..3f1aba1be 100644 --- a/packages/app/src/components/ColorizedPreformattedText.tsx +++ b/packages/app/src/components/ColorizedPreformattedText.tsx @@ -1,6 +1,6 @@ import { type FC, useLayoutEffect, useRef } from 'react'; import { monaco } from '../utils/monaco'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { themeState } from '../state/settings'; export const ColorizedPreformattedText: FC<{ text: string; language: string; theme?: string }> = ({ @@ -9,7 +9,7 @@ export const ColorizedPreformattedText: FC<{ text: string; language: string; the theme, }) => { const bodyRef = useRef(null); - const appTheme = useRecoilValue(themeState); + const appTheme = useAtomValue(themeState); const actualTheme = theme === 'prompt-interpolation' ? `prompt-interpolation-${appTheme}` : theme; useLayoutEffect(() => { diff --git a/packages/app/src/components/CopyAsTestCaseModal.tsx b/packages/app/src/components/CopyAsTestCaseModal.tsx index 66d4de7b0..02e613f60 100644 --- a/packages/app/src/components/CopyAsTestCaseModal.tsx +++ b/packages/app/src/components/CopyAsTestCaseModal.tsx @@ -3,8 +3,8 @@ import { type FC, useState } from 'react'; import Select from '@atlaskit/select'; import TextField from '@atlaskit/textfield'; import { LazyCodeEditor } from './LazyComponents'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; -import { lastRunData, lastRunDataByNodeState } from '../state/dataFlow'; +import { useAtomValue, useAtom, useSetAtom } from 'jotai'; +import { lastRunDataByNodeState } from '../state/dataFlow'; import { graphState } from '../state/graph'; import { BuiltInNodeType, type BuiltInNodes, type GraphInputNode, type PortId } from '@ironclad/rivet-core'; import { max, maxBy, range } from 'lodash-es'; @@ -42,13 +42,13 @@ export const CopyAsTestCaseModal: FC<{ open: boolean; onClose: () => void; }> = ({ open, onClose }) => { - const lastRunData = useRecoilValue(lastRunDataByNodeState); - const graph = useRecoilValue(graphState); + const lastRunData = useAtomValue(lastRunDataByNodeState); + const graph = useAtomValue(graphState); const [selectedExecutionNum, setSelectedExecutionNum] = useState(1); const [selectedTestSuiteId, setSelectedTestSuiteId] = useState(undefined); - const [{ testSuites }, setTrivetState] = useRecoilState(trivetState); + const [{ testSuites }, setTrivetState] = useAtom(trivetState); const { addTestCase } = useTestSuite(selectedTestSuiteId); - const setOverlay = useSetRecoilState(overlayOpenState); + const setOverlay = useSetAtom(overlayOpenState); const inputNodes = graph.nodes.filter((n) => (n as BuiltInNodes).type === 'graphInput') as GraphInputNode[]; const lastRunDataForInputNodes = inputNodes.map((n) => lastRunData[n.id]); diff --git a/packages/app/src/components/DebuggerConnectPanel.tsx b/packages/app/src/components/DebuggerConnectPanel.tsx index e089a77b2..0d916cf8b 100644 --- a/packages/app/src/components/DebuggerConnectPanel.tsx +++ b/packages/app/src/components/DebuggerConnectPanel.tsx @@ -4,12 +4,12 @@ import { css } from '@emotion/react'; import { type ChangeEvent, type FC, useEffect, useRef, useState } from 'react'; import { Field } from '@atlaskit/form'; import { useRemoteDebugger } from '../hooks/useRemoteDebugger'; -import { useRecoilState, useSetRecoilState } from 'recoil'; import { debuggerPanelOpenState } from '../state/ui'; import { debuggerDefaultUrlState } from '../state/settings'; +import { useSetAtom, useAtom } from 'jotai'; export function useToggleRemoteDebugger() { - const setDebuggerPanelOpen = useSetRecoilState(debuggerPanelOpenState); + const setDebuggerPanelOpen = useSetAtom(debuggerPanelOpenState); const { remoteDebuggerState: remoteDebugger, connect, disconnect } = useRemoteDebugger(); const isActuallyRemoteDebugging = remoteDebugger.started && !remoteDebugger.isInternalExecutor; @@ -23,7 +23,7 @@ export function useToggleRemoteDebugger() { } export const DebuggerPanelRenderer: FC = () => { - const [debuggerPanelOpen, setDebuggerPanelOpen] = useRecoilState(debuggerPanelOpenState); + const [debuggerPanelOpen, setDebuggerPanelOpen] = useAtom(debuggerPanelOpenState); const { connect } = useRemoteDebugger(); @@ -74,7 +74,7 @@ export type DebuggerConnectPanelProps = { }; export const DebuggerConnectPanel: FC = ({ onConnect, onCancel }) => { - const [defaultConnectUrl, setDefaultConnectUrl] = useRecoilState(debuggerDefaultUrlState); + const [defaultConnectUrl, setDefaultConnectUrl] = useAtom(debuggerDefaultUrlState); const [connectUrl, setConnectUrl] = useState(defaultConnectUrl); const textField = useRef(null); diff --git a/packages/app/src/components/GraphBuilder.tsx b/packages/app/src/components/GraphBuilder.tsx index ff083b04b..dbcdb29dd 100644 --- a/packages/app/src/components/GraphBuilder.tsx +++ b/packages/app/src/components/GraphBuilder.tsx @@ -1,6 +1,6 @@ import { type FC, useEffect, useMemo, useState, type MouseEvent } from 'react'; import { NodeCanvas } from './NodeCanvas.js'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useAtom, useSetAtom } from 'jotai'; import { connectionsState, isReadOnlyGraphState, nodesByIdState, nodesState } from '../state/graph.js'; import { editingNodeState, selectedNodesState } from '../state/graphBuilder.js'; import { NodeEditorRenderer } from './NodeEditor.js'; @@ -59,12 +59,12 @@ const Container = styled.div` `; export const GraphBuilder: FC = () => { - const [nodes, setNodes] = useRecoilState(nodesState); - const [connections, setConnections] = useRecoilState(connectionsState); - const [selectedNodeIds, setSelectedNodeIds] = useRecoilState(selectedNodesState); - const setEditingNodeId = useSetRecoilState(editingNodeState); - const loadedRecording = useRecoilValue(loadedRecordingState); - const project = useRecoilValue(projectState); + const [nodes, setNodes] = useAtom(nodesState); + const [connections, setConnections] = useAtom(connectionsState); + const [selectedNodeIds, setSelectedNodeIds] = useAtom(selectedNodesState); + const setEditingNodeId = useSetAtom(editingNodeState); + const loadedRecording = useAtomValue(loadedRecordingState); + const project = useAtomValue(projectState); useDatasets(project.metadata.id); @@ -75,7 +75,7 @@ export const GraphBuilder: FC = () => { setNodes?.(newNodes); }); - const nodesById = useRecoilValue(nodesByIdState); + const nodesById = useAtomValue(nodesByIdState); const contextMenuHandler = useGraphBuilderContextMenuHandler(); const nodeSelected = useStableCallback((node: ChartNode, multi: boolean) => { @@ -89,8 +89,8 @@ export const GraphBuilder: FC = () => { setEditingNodeId(node.id); }); - const allCurrentQuestions = useRecoilValue(userInputModalQuestionsState); - const userInputModalSubmit = useRecoilValue(userInputModalSubmitState); + const allCurrentQuestions = useAtomValue(userInputModalQuestionsState); + const userInputModalSubmit = useAtomValue(userInputModalSubmitState); const firstNodeQuestions = useMemo(() => entries(allCurrentQuestions)[0], [allCurrentQuestions]); const [isUserInputModalOpen, setUserInputModalOpen] = useState(false); @@ -135,8 +135,8 @@ export const GraphBuilder: FC = () => { [selectedNodeIds, nodesById], ); - const overlay = useRecoilValue(overlayOpenState); - const isReadOnly = useRecoilValue(isReadOnlyGraphState); + const overlay = useAtomValue(overlayOpenState); + const isReadOnly = useAtomValue(isReadOnlyGraphState); return ( diff --git a/packages/app/src/components/GraphExecutionSelectorBar.tsx b/packages/app/src/components/GraphExecutionSelectorBar.tsx index 532c09492..481a9029e 100644 --- a/packages/app/src/components/GraphExecutionSelectorBar.tsx +++ b/packages/app/src/components/GraphExecutionSelectorBar.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/react'; import { useMemo, type FC } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { lastRunDataByNodeState, selectedProcessPageNodesState } from '../state/dataFlow'; import { nodesState } from '../state/graph'; import LeftIcon from 'majesticons/line/chevron-left-line.svg?react'; @@ -69,9 +69,9 @@ const styles = css` `; export const GraphExecutionSelectorBar: FC = () => { - const nodes = useRecoilValue(nodesState); - const [selectedExecutionByNode, setSelectedExecutionByNode] = useRecoilState(selectedProcessPageNodesState); - const lastRunDataByNode = useRecoilValue(lastRunDataByNodeState); + const nodes = useAtomValue(nodesState); + const [selectedExecutionByNode, setSelectedExecutionByNode] = useAtom(selectedProcessPageNodesState); + const lastRunDataByNode = useAtomValue(lastRunDataByNodeState); const nodeIds = useMemo(() => nodes.map((n) => n.id), [nodes]); diff --git a/packages/app/src/components/GraphInfoSidebarTab.tsx b/packages/app/src/components/GraphInfoSidebarTab.tsx index b73062786..a18fd1bd6 100644 --- a/packages/app/src/components/GraphInfoSidebarTab.tsx +++ b/packages/app/src/components/GraphInfoSidebarTab.tsx @@ -1,5 +1,5 @@ import { type FC } from 'react'; -import { useRecoilState } from 'recoil'; +import { useAtom, useSetAtom } from 'jotai'; import { graphState } from '../state/graph.js'; import { savedGraphsState } from '../state/savedGraphs.js'; import { InlineEditableTextfield } from '@atlaskit/inline-edit'; @@ -8,12 +8,12 @@ import { Label } from '@atlaskit/form'; import { GraphRevisions } from './GraphRevisionList'; export const GraphInfoSidebarTab: FC = () => { - const [graph, setGraph] = useRecoilState(graphState); - const [savedGraphs, setSavedGraphs] = useRecoilState(savedGraphsState); + const [graph, setGraph] = useAtom(graphState); + const setSavedGraphs = useSetAtom(savedGraphsState); function setGraphAndSavedGraph(graph: NodeGraph) { setGraph(graph); - setSavedGraphs(savedGraphs.map((g) => (g.metadata!.id === graph.metadata!.id ? graph : g))); + setSavedGraphs((prev) => prev.map((g) => (g.metadata!.id === graph.metadata!.id ? graph : g))); } return ( diff --git a/packages/app/src/components/GraphList.tsx b/packages/app/src/components/GraphList.tsx index 8e9331aae..9cc3cbc86 100644 --- a/packages/app/src/components/GraphList.tsx +++ b/packages/app/src/components/GraphList.tsx @@ -17,7 +17,8 @@ import { type KeyboardEvent, memo, } from 'react'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { produce } from 'immer'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { graphState } from '../state/graph.js'; import { projectMetadataState, savedGraphsState } from '../state/savedGraphs.js'; import { orderBy, range } from 'lodash-es'; @@ -312,9 +313,9 @@ function getFolderNames(folderedGraphs: NodeGraphFolderItem[]): string[] { } export const GraphList: FC<{ onRunGraph?: (graphId: GraphId) => void }> = memo(({ onRunGraph }) => { - const projectMetadata = useRecoilValue(projectMetadataState); - const [graph, setGraph] = useRecoilState(graphState); - const [savedGraphs, setSavedGraphs] = useRecoilState(savedGraphsState); + const projectMetadata = useAtomValue(projectMetadataState); + const [savedGraphs, setSavedGraphs] = useAtom(savedGraphsState); + const [graph, setGraph] = useAtom(graphState); const [searchText, setSearchText] = useState(''); const searchedGraphs = useFuseSearch( @@ -339,7 +340,7 @@ export const GraphList: FC<{ onRunGraph?: (graphId: GraphId) => void }> = memo(( [filteredGraphs, folderNames], ); - const runningGraphs = useRecoilValue(runningGraphsState); + const runningGraphs = useAtomValue(runningGraphsState); const deleteGraph = useDeleteGraph(); const loadGraph = useLoadGraph(); @@ -374,11 +375,11 @@ export const GraphList: FC<{ onRunGraph?: (graphId: GraphId) => void }> = memo(( graph.metadata!.name = i === 1 ? `Untitled Graph` : `Untitled Graph ${i}`; } loadGraph(graph); - setSavedGraphs((savedGraphs) => [...savedGraphs, graph]); + setSavedGraphs((prev) => [...prev, graph]); startRename(graph.metadata!.name!); }); - const setExpandedFolders = useSetRecoilState(expandedFoldersState); + const setExpandedFolders = useSetAtom(expandedFoldersState); const handleNewFolder = useStableCallback((parentPath?: string) => { const newFolderPath = parentPath ? `${parentPath}/New Folder` : 'New Folder'; @@ -400,7 +401,8 @@ export const GraphList: FC<{ onRunGraph?: (graphId: GraphId) => void }> = memo(( (graph) => graph.metadata?.name && isInFolder(folderName, graph.metadata?.name), ); graphsToDelete.forEach((graph) => deleteGraph(graph)); - setFolderNames((prev) => prev.filter((name) => folderName !== name && !isInFolder(folderName, name))); + const newFolderNames = folderNames.filter((name) => folderName !== name && !isInFolder(folderName, name)); + setFolderNames(newFolderNames); }); const runGraph = useStableCallback((folderName: string) => { @@ -429,8 +431,9 @@ export const GraphList: FC<{ onRunGraph?: (graphId: GraphId) => void }> = memo(( toast.error('A graph or folder with that name already exists.'); return; } - setSavedGraphs( - savedGraphs.map((g) => { + + setSavedGraphs((prev) => { + return prev.map((g) => { if (g.metadata?.name && (fullPath === g.metadata.name || isInFolder(fullPath, g.metadata.name))) { return { ...g, @@ -439,23 +442,24 @@ export const GraphList: FC<{ onRunGraph?: (graphId: GraphId) => void }> = memo(( name: g.metadata.name.replace(fullPath, newFullPath), }, }; - } else { - return g; } + return g; + }); + }); + + setGraph((prev) => + produce(prev, (draft) => { + const metadata = draft.metadata ?? { name: '' }; + metadata.name = metadata.name!.replace(fullPath, newFullPath); + draft.metadata = metadata; }), ); - setGraph({ - ...graph, - metadata: { - ...graph.metadata, - name: graph.metadata?.name?.replace(fullPath, newFullPath), - }, - }); - setFolderNames((prev) => - prev.map((name) => - name === fullPath || isInFolder(fullPath, name) ? name.replace(fullPath, newFullPath) : name, - ), + + const newFolderNames = folderNames.map((name) => + name === fullPath || isInFolder(fullPath, name) ? name.replace(fullPath, newFullPath) : name, ); + setFolderNames(newFolderNames); + setRenamingItemFullPath(undefined); setExpandedFolders((prev) => ({ ...prev, @@ -749,8 +753,8 @@ export const FolderItem: FC<{ depth, dragOverFolderName, }) => { - const projectMetadata = useRecoilValue(projectMetadataState); - const [expandedFolders, setExpandedFolders] = useRecoilState(expandedFoldersState); + const projectMetadata = useAtomValue(projectMetadataState); + const [expandedFolders, setExpandedFolders] = useAtom(expandedFoldersState); const savedGraph = item.type === 'graph' ? item.graph : undefined; const graphIsRunning = savedGraph && runningGraphs.includes(savedGraph.metadata?.id ?? ('' as GraphId)); diff --git a/packages/app/src/components/GraphRevisionList.tsx b/packages/app/src/components/GraphRevisionList.tsx index 769c35f2d..06e7c7825 100644 --- a/packages/app/src/components/GraphRevisionList.tsx +++ b/packages/app/src/components/GraphRevisionList.tsx @@ -1,5 +1,5 @@ import { useState, type FC } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue } from 'jotai'; import { loadedProjectState } from '../state/savedGraphs'; import { useGraphRevisions } from '../hooks/useGraphRevisions'; import { css } from '@emotion/react'; @@ -59,7 +59,7 @@ export const revisionStyles = css` `; export const GraphRevisions: FC = () => { - const projectState = useRecoilValue(loadedProjectState); + const projectState = useAtomValue(loadedProjectState); const [enabled, setEnabled] = useState(false); if (!projectState.loaded || !projectState.path) { @@ -113,7 +113,7 @@ export const GraphRevisionList: FC = () => { export const GraphRevisionListEntry: FC<{ revision: CalculatedRevision; }> = ({ revision }) => { - const currentGraphId = useRecoilValue(graphState).metadata!.id!; + const currentGraphId = useAtomValue(graphState).metadata!.id!; const chooseGraph = useChooseHistoricalGraph(revision); return ( diff --git a/packages/app/src/components/HelpModal.tsx b/packages/app/src/components/HelpModal.tsx index 81a69b33f..8c6a99fc7 100644 --- a/packages/app/src/components/HelpModal.tsx +++ b/packages/app/src/components/HelpModal.tsx @@ -1,5 +1,4 @@ import { type FC } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; import { helpModalOpenState } from '../state/ui'; import Modal, { ModalTransition, ModalHeader, ModalTitle, ModalBody, ModalFooter } from '@atlaskit/modal-dialog'; import Button from '@atlaskit/button'; @@ -9,6 +8,7 @@ import TwitterIcon from '../assets/vendor_logos/twitter-logo.svg?react'; import YoutubeIcon from '../assets/vendor_logos/youtube-icon.png'; import QuestionIcon from 'majesticons/line/question-circle-line.svg?react'; import { css } from '@emotion/react'; +import { useAtom } from 'jotai'; const styles = css` ul li a, @@ -30,7 +30,7 @@ const styles = css` `; export const HelpModal: FC = () => { - const [helpModalOpen, setHelpModalOpen] = useRecoilState(helpModalOpenState); + const [helpModalOpen, setHelpModalOpen] = useAtom(helpModalOpenState); return ( diff --git a/packages/app/src/components/HistoricalGraphNotice.tsx b/packages/app/src/components/HistoricalGraphNotice.tsx index b8cef9bba..e7c9beb59 100644 --- a/packages/app/src/components/HistoricalGraphNotice.tsx +++ b/packages/app/src/components/HistoricalGraphNotice.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/react'; import { type FC } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { graphState, historicalGraphState, isReadOnlyGraphState } from '../state/graph'; import Button from '@atlaskit/button'; import { useLoadGraph } from '../hooks/useLoadGraph'; @@ -33,10 +33,10 @@ const styles = css` export const HistoricalGraphNotice: FC = () => { const loadGraph = useLoadGraph(); - const project = useRecoilValue(projectState); - const graph = useRecoilValue(graphState); - const [historicalGraph, setHistoricalGraph] = useRecoilState(historicalGraphState); - const [isReadOnlyGraph, setIsReadOnlyGraph] = useRecoilState(isReadOnlyGraphState); + const project = useAtomValue(projectState); + const graph = useAtomValue(graphState); + const [historicalGraph, setHistoricalGraph] = useAtom(historicalGraphState); + const [isReadOnlyGraph, setIsReadOnlyGraph] = useAtom(isReadOnlyGraphState); const currentGraphExists = graph.metadata!.id! in project.graphs; diff --git a/packages/app/src/components/LeftSidebar.tsx b/packages/app/src/components/LeftSidebar.tsx index cff6cb331..a1d41d875 100644 --- a/packages/app/src/components/LeftSidebar.tsx +++ b/packages/app/src/components/LeftSidebar.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/react'; import { type FC } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { projectState } from '../state/savedGraphs.js'; import ExpandLeftIcon from 'majesticons/line/menu-expand-left-line.svg?react'; import ExpandRightIcon from 'majesticons/line/menu-expand-right-line.svg?react'; @@ -73,8 +73,8 @@ const styles = css` export const LeftSidebar: FC<{ onRunGraph?: (graphId: GraphId) => void; }> = ({ onRunGraph }) => { - const project = useRecoilValue(projectState); - const [sidebarOpen, setSidebarOpen] = useRecoilState(sidebarOpenState); + const project = useAtomValue(projectState); + const [sidebarOpen, setSidebarOpen] = useAtom(sidebarOpenState); return (
= ({ }) => { const { inputDefinitions, outputDefinitions } = useNodeIO(node.id)!; - const preservePortTextCase = useRecoilValue(preservePortTextCaseState); + const preservePortTextCase = useAtomValue(preservePortTextCaseState); const handlePortMouseDown = useStableCallback((event: MouseEvent, port: PortId, isInput: boolean) => { event.stopPropagation(); diff --git a/packages/app/src/components/MouseIcon.tsx b/packages/app/src/components/MouseIcon.tsx index 4924c6fa1..0baa44924 100644 --- a/packages/app/src/components/MouseIcon.tsx +++ b/packages/app/src/components/MouseIcon.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/react'; import { useEffect, type FC, useState } from 'react'; import { lastMousePositionState } from '../state/graphBuilder'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; const styles = css` position: fixed; @@ -16,7 +16,7 @@ const styles = css` `; export const MouseIcon: FC = () => { - const lastMousePosition = useRecoilValue(lastMousePositionState); + const lastMousePosition = useAtomValue(lastMousePositionState); const [shiftPressed, setShiftPressed] = useState(false); const offset = { diff --git a/packages/app/src/components/NavigationBar.tsx b/packages/app/src/components/NavigationBar.tsx index 012059a29..ff6a69fc2 100644 --- a/packages/app/src/components/NavigationBar.tsx +++ b/packages/app/src/components/NavigationBar.tsx @@ -4,7 +4,7 @@ import { useGraphHistoryNavigation } from '../hooks/useGraphHistoryNavigation'; import LeftIcon from 'majesticons/line/chevron-left-line.svg?react'; import RightIcon from 'majesticons/line/chevron-right-line.svg?react'; import CrossIcon from 'majesticons/line/multiply-line.svg?react'; -import { useRecoilState } from 'recoil'; +import { useAtom } from 'jotai'; import { searchingGraphState } from '../state/graphBuilder'; import { Tooltip } from './Tooltip'; @@ -90,7 +90,7 @@ const styles = css` export const NavigationBar: FC = () => { const navigationStack = useGraphHistoryNavigation(); - const [searching, setSearching] = useRecoilState(searchingGraphState); + const [searching, setSearching] = useAtom(searchingGraphState); return (
diff --git a/packages/app/src/components/NewProjectModal.tsx b/packages/app/src/components/NewProjectModal.tsx index 78c8048a3..18edf5f99 100644 --- a/packages/app/src/components/NewProjectModal.tsx +++ b/packages/app/src/components/NewProjectModal.tsx @@ -1,5 +1,5 @@ import { useState, type FC, type FormEvent } from 'react'; -import { useRecoilState, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; import { newProjectModalOpenState } from '../state/ui'; import Modal, { ModalTransition, ModalBody, ModalHeader, ModalTitle, ModalFooter } from '@atlaskit/modal-dialog'; import { ButtonItem, SideNavigation, Section } from '@atlaskit/side-navigation'; @@ -15,7 +15,7 @@ import documentationTutorialProject from '../assets/tutorials/documentation-tuto import { useNewProjectFromTemplate } from '../hooks/useNewProjectFromTemplate'; export const NewProjectModalRenderer: FC = () => { - const [newProjectModalOpen] = useRecoilState(newProjectModalOpenState); + const newProjectModalOpen = useAtomValue(newProjectModalOpenState); return {newProjectModalOpen && }; }; @@ -48,7 +48,7 @@ const buttonsContainer = css` `; export const NewProjectModal: FC = () => { - const setNewProjectModalOpen = useSetRecoilState(newProjectModalOpenState); + const setNewProjectModalOpen = useSetAtom(newProjectModalOpenState); const [selectedTemplate, setSelectedTemplate] = useState('blank_project'); diff --git a/packages/app/src/components/NoProject.tsx b/packages/app/src/components/NoProject.tsx index e1c355e1f..a0ee0c365 100644 --- a/packages/app/src/components/NoProject.tsx +++ b/packages/app/src/components/NoProject.tsx @@ -5,7 +5,7 @@ import { useOpenUrl } from '../hooks/useOpenUrl'; import DiscordIcon from '../assets/vendor_logos/discord-mark-white.svg?react'; import GearIcon from 'majesticons/line/settings-cog-line.svg?react'; import RivetIcon from '../rivet-logo-1024-full.png'; -import { useSetRecoilState } from 'recoil'; +import { useSetAtom } from 'jotai'; import { newProjectModalOpenState } from '../state/ui'; import { settingsModalOpenState } from './SettingsModal'; import { useLoadProjectWithFileBrowser } from '../hooks/useLoadProjectWithFileBrowser'; @@ -82,8 +82,8 @@ const styles = css` export const NoProject: FC = () => { const openDocumentation = useOpenUrl('https://rivet.ironcladapp.com/docs'); const joinDiscord = useOpenUrl('https://discord.gg/qT8B2gv9Mg'); - const setNewProjectModalOpen = useSetRecoilState(newProjectModalOpenState); - const setSettingsModalOpen = useSetRecoilState(settingsModalOpenState); + const setNewProjectModalOpen = useSetAtom(newProjectModalOpenState); + const setSettingsModalOpen = useSetAtom(settingsModalOpenState); const openProject = useLoadProjectWithFileBrowser(); return ( diff --git a/packages/app/src/components/NodeCanvas.tsx b/packages/app/src/components/NodeCanvas.tsx index 0adbf7163..8bef6ebc8 100644 --- a/packages/app/src/components/NodeCanvas.tsx +++ b/packages/app/src/components/NodeCanvas.tsx @@ -19,7 +19,7 @@ import { type NodeInputDefinition, type NodeOutputDefinition, } from '@ironclad/rivet-core'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { type CanvasPosition, canvasPositionState, @@ -169,15 +169,15 @@ export const NodeCanvas: FC = ({ onNodeStartEditing, onContextMenuItemSelected, }) => { - const [canvasPosition, setCanvasPosition] = useRecoilState(canvasPositionState); - const selectedGraphMetadata = useRecoilValue(graphMetadataState); + const [canvasPosition, setCanvasPosition] = useAtom(canvasPositionState); + const selectedGraphMetadata = useAtomValue(graphMetadataState); - const setLastSavedCanvasPosition = useSetRecoilState(lastCanvasPositionByGraphState); + const setLastSavedCanvasPosition = useSetAtom(lastCanvasPositionByGraphState); const [isDraggingCanvas, setIsDraggingCanvas] = useState(false); const [dragStart, setDragStart] = useState({ x: 0, y: 0, canvasStartX: 0, canvasStartY: 0 }); const { clientToCanvasPosition } = useCanvasPositioning(); - const setLastMousePosition = useSetRecoilState(lastMousePositionState); + const setLastMousePosition = useSetAtom(lastMousePositionState); const removeNodes = useRemoveNodes(); const { refs, floatingStyles } = useFloating({ @@ -192,8 +192,8 @@ export const NodeCanvas: FC = ({ target: undefined, }); - const [editingNodeId, setEditingNodeId] = useRecoilState(editingNodeState); - const [selectedNodeIds, setSelectedNodeIds] = useRecoilState(selectedNodesState); + const [editingNodeId, setEditingNodeId] = useAtom(editingNodeState); + const [selectedNodeIds, setSelectedNodeIds] = useAtom(selectedNodesState); const [selectionBox, setSelectionBox] = useState<{ x: number; y: number; width: number; height: number } | null>( null, ); @@ -360,7 +360,7 @@ export const NodeCanvas: FC = ({ return false; }; - const zoomSensitivity = useRecoilValue(zoomSensitivityState); + const zoomSensitivity = useAtomValue(zoomSensitivityState); // I think safari deals with wheel events differently, so we need to throttle the zooming // because otherwise it lags like CRAZY @@ -440,7 +440,7 @@ export const NodeCanvas: FC = ({ ); }); - const [hoveringNode, setHoveringNode] = useRecoilState(hoveringNodeState); + const [hoveringNode, setHoveringNode] = useAtom(hoveringNodeState); const [hoveringPort, setHoveringPort] = useState< | { nodeId: NodeId; @@ -453,7 +453,7 @@ export const NodeCanvas: FC = ({ const hoveringPortTimeout = useRef(); const [hoveringShowPortInfo, setHoveringPortShowInfo] = useState(false); - const closestPort = useRecoilValue(draggingWireClosestPortState); + const closestPort = useAtomValue(draggingWireClosestPortState); const { setReference } = refs; @@ -565,7 +565,7 @@ export const NodeCanvas: FC = ({ handleContextMenu(e); }); - const lastRunPerNode = useRecoilValue(lastRunDataByNodeState); + const lastRunPerNode = useAtomValue(lastRunDataByNodeState); const hydratedContextMenuData = useMemo((): ContextMenuContext | null => { if (contextMenuData.data?.type.startsWith('node-')) { @@ -594,12 +594,12 @@ export const NodeCanvas: FC = ({ useCanvasHotkeys(); useSearchGraph(); - const searchMatchingNodes = useRecoilValue(searchMatchingNodeIdsState); + const searchMatchingNodes = useAtomValue(searchMatchingNodeIdsState); - const pinnedNodes = useRecoilValue(pinnedNodesState); + const pinnedNodes = useAtomValue(pinnedNodesState); const nodeTypes = useNodeTypes(); - const selectedProcessPagePerNode = useRecoilValue(selectedProcessPageNodesState); + const selectedProcessPagePerNode = useAtomValue(selectedProcessPageNodesState); const isZoomedOut = canvasPosition.zoom < 0.4; const isReallyZoomedOut = canvasPosition.zoom < 0.2; @@ -813,9 +813,9 @@ export const NodeCanvas: FC = ({ }; const DebugOverlay: FC<{ enabled: boolean }> = ({ enabled }) => { - const canvasPosition = useRecoilValue(canvasPositionState); + const canvasPosition = useAtomValue(canvasPositionState); - const lastMousePosition = useRecoilValue(lastMousePositionState); + const lastMousePosition = useAtomValue(lastMousePositionState); const { clientToCanvasPosition } = useCanvasPositioning(); diff --git a/packages/app/src/components/NodeChangesModal.tsx b/packages/app/src/components/NodeChangesModal.tsx index 592deaccf..ed24fc607 100644 --- a/packages/app/src/components/NodeChangesModal.tsx +++ b/packages/app/src/components/NodeChangesModal.tsx @@ -1,21 +1,21 @@ import { type FC } from 'react'; import Modal, { ModalBody, ModalTransition, ModalTitle, ModalFooter, ModalHeader } from '@atlaskit/modal-dialog'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; import { viewingNodeChangesState } from '../state/graphBuilder'; import { useHistoricalNodeChangeInfo } from '../hooks/useHistoricalNodeChangeInfo'; import * as yaml from 'yaml'; import { diffStringsUnified } from 'jest-diff'; export const NodeChangesModalRenderer: FC = () => { - const changes = useRecoilValue(viewingNodeChangesState); + const changes = useAtomValue(viewingNodeChangesState); return {changes == null ? null : }; }; export const NodeChangesModal: FC = () => { - const nodeId = useRecoilValue(viewingNodeChangesState); + const nodeId = useAtomValue(viewingNodeChangesState); const changes = useHistoricalNodeChangeInfo(nodeId!); - const setViewingNodeChanges = useSetRecoilState(viewingNodeChangesState); + const setViewingNodeChanges = useSetAtom(viewingNodeChangesState); if (changes == null || changes.changed === false) { return null; diff --git a/packages/app/src/components/NodeEditor.tsx b/packages/app/src/components/NodeEditor.tsx index b5c99fd8b..f2e3ed2da 100644 --- a/packages/app/src/components/NodeEditor.tsx +++ b/packages/app/src/components/NodeEditor.tsx @@ -1,6 +1,5 @@ import { type FC, useMemo, useState, type MouseEvent } from 'react'; import { editingNodeState } from '../state/graphBuilder.js'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { connectionsForSingleNodeState, connectionsState, nodesByIdState, nodesState } from '../state/graph.js'; import styled from '@emotion/styled'; import MultiplyIcon from 'majesticons/line/multiply-line.svg?react'; @@ -30,10 +29,11 @@ import { projectDataState, projectState } from '../state/savedGraphs'; import { useSetStaticData } from '../hooks/useSetStaticData'; import { DefaultNodeEditor } from './editors/DefaultNodeEditor'; import { NodeColorPicker } from './NodeColorPicker'; +import { useAtom, useSetAtom, useAtomValue } from 'jotai'; export const NodeEditorRenderer: FC = () => { - const nodesById = useRecoilValue(nodesByIdState); - const [editingNodeId, setEditingNodeId] = useRecoilState(editingNodeState); + const nodesById = useAtomValue(nodesByIdState); + const [editingNodeId, setEditingNodeId] = useAtom(editingNodeState); const deselect = useStableCallback(() => { setEditingNodeId(null); @@ -264,20 +264,19 @@ type NodeEditorProps = { selectedNode: ChartNode; onDeselect: () => void }; export type NodeChanged = (changed: ChartNode, newData?: Record) => void; export const NodeEditor: FC = ({ selectedNode, onDeselect }) => { - const setNodes = useSetRecoilState(nodesState); + const setNodes = useSetAtom(nodesState); const [selectedVariant, setSelectedVariant] = useState(); const [addVariantPopupOpen, setAddVariantPopupOpen] = useState(false); - const nodesById = useRecoilValue(nodesByIdState); - const project = useRecoilValue(projectState); - const connectionsForNode = useRecoilValue(connectionsForSingleNodeState(selectedNode.id)); - const setConnections = useSetRecoilState(connectionsState); + const nodesById = useAtomValue(nodesByIdState); + const project = useAtomValue(projectState); + const connectionsForNode = useAtomValue(connectionsForSingleNodeState(selectedNode.id)); + const setConnections = useSetAtom(connectionsState); const setStaticData = useSetStaticData(); const updateNode = useStableCallback((node: ChartNode, newData?: Record) => { - // Update the node - setNodes((nodes) => - produce(nodes, (draft) => { + setNodes((prev) => + produce(prev, (draft) => { const index = draft.findIndex((n) => n.id === node.id); draft[index] = node; }), diff --git a/packages/app/src/components/NodeOutput.tsx b/packages/app/src/components/NodeOutput.tsx index 80f5d0af3..948816432 100644 --- a/packages/app/src/components/NodeOutput.tsx +++ b/packages/app/src/components/NodeOutput.tsx @@ -1,9 +1,9 @@ -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { type NodeRunData, type ProcessDataForNode, - lastRunData, - selectedProcessPage, + lastRunDataState, + selectedProcessPageState, type NodeRunDataWithRefs, } from '../state/dataFlow.js'; import { type FC, type ReactNode, memo, useMemo, useState, type MouseEvent } from 'react'; @@ -32,7 +32,7 @@ export const NodeOutput: FC<{ node: ChartNode }> = memo(({ node }) => { const [isModalOpen, setIsModalOpen] = useState(false); useDependsOnPlugins(); - const isPinned = useRecoilValue(pinnedNodesState).includes(node.id); + const isPinned = useAtomValue(pinnedNodesState).includes(node.id); const handleWheel = useStableCallback((e: MouseEvent) => { if (isPinned) { @@ -169,16 +169,16 @@ const fullscreenOutputButtonsCss = css` `; const NodeFullscreenOutput: FC<{ node: ChartNode }> = ({ node }) => { - const output = useRecoilValue(lastRunData(node.id)); - const [selectedPage, setSelectedPage] = useRecoilState(selectedProcessPage(node.id)); + const output = useAtomValue(lastRunDataState(node.id)); + const [selectedPage, setSelectedPage] = useAtom(selectedProcessPageState(node.id)); const { FullscreenOutput, Output, OutputSimple, FullscreenOutputSimple, defaultRenderMarkdown } = useUnknownNodeComponentDescriptorFor(node); const [renderMarkdown, toggleRenderMarkdown] = useToggle(defaultRenderMarkdown ?? false); - const setOverlayOpen = useSetRecoilState(overlayOpenState); - const setPromptDesignerAttachedNode = useSetRecoilState(promptDesignerAttachedChatNodeState); + const setOverlayOpen = useSetAtom(overlayOpenState); + const setPromptDesignerAttachedNode = useSetAtom(promptDesignerAttachedChatNodeState); const io = useNodeIO(node.id); @@ -355,7 +355,7 @@ const NodeOutputBase: FC<{ node: ChartNode; children?: ReactNode; onOpenFullscre children, onOpenFullscreenModal, }) => { - const output = useRecoilValue(lastRunData(node.id)); + const output = useAtomValue(lastRunDataState(node.id)); if (!output?.length) { return null; } @@ -388,8 +388,8 @@ const NodeOutputSingleProcess: FC<{ }> = ({ node, data, processId, onOpenFullscreenModal }) => { const { Output, OutputSimple } = useUnknownNodeComponentDescriptorFor(node); - const setOverlayOpen = useSetRecoilState(overlayOpenState); - const setPromptDesignerAttachedNode = useSetRecoilState(promptDesignerAttachedChatNodeState); + const setOverlayOpen = useSetAtom(overlayOpenState); + const setPromptDesignerAttachedNode = useSetAtom(promptDesignerAttachedChatNodeState); const io = useNodeIO(node.id); const handleOpenPromptDesigner = () => { @@ -505,7 +505,7 @@ const NodeOutputMultiProcess: FC<{ data: ProcessDataForNode[]; onOpenFullscreenModal?: () => void; }> = ({ node, data, onOpenFullscreenModal }) => { - const [selectedPage, setSelectedPage] = useRecoilState(selectedProcessPage(node.id)); + const [selectedPage, setSelectedPage] = useAtom(selectedProcessPageState(node.id)); const prevPage = useStableCallback(() => { setSelectedPage((page) => { diff --git a/packages/app/src/components/NodePorts.tsx b/packages/app/src/components/NodePorts.tsx index 2592be80e..cfe590c3e 100644 --- a/packages/app/src/components/NodePorts.tsx +++ b/packages/app/src/components/NodePorts.tsx @@ -15,7 +15,7 @@ import { ErrorBoundary } from 'react-error-boundary'; import { useDependsOnPlugins } from '../hooks/useDependsOnPlugins'; import { LoopControllerNodePorts } from './LoopControllerNodePorts'; import { type DraggingWireDef } from '../state/graphBuilder'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { preservePortTextCaseState } from '../state/settings.js'; export type NodePortsProps = { @@ -66,7 +66,7 @@ export const NodePorts: FC = ({ onPortMouseOut, }) => { const { inputDefinitions, outputDefinitions } = useNodeIO(node.id)!; - const preservePortTextCase = useRecoilValue(preservePortTextCaseState); + const preservePortTextCase = useAtomValue(preservePortTextCaseState); const handlePortMouseDown = useStableCallback((event: MouseEvent, port: PortId, isInput: boolean) => { event.stopPropagation(); diff --git a/packages/app/src/components/OverlayTabs.tsx b/packages/app/src/components/OverlayTabs.tsx index 3c77858ec..bce5732ce 100644 --- a/packages/app/src/components/OverlayTabs.tsx +++ b/packages/app/src/components/OverlayTabs.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/react'; import { type FC, useState } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import clsx from 'clsx'; import { trivetState } from '../state/trivet.js'; import { useRunMenuCommand } from '../hooks/useMenuCommands.js'; @@ -177,12 +177,12 @@ const styles = css` `; export const OverlayTabs: FC = () => { - const [openOverlay, setOpenOverlay] = useRecoilState(overlayOpenState); + const [openOverlay, setOpenOverlay] = useAtom(overlayOpenState); const runMenuCommandImpl = useRunMenuCommand(); const [fileMenuOpen, setFileMenuOpen] = useState(false); - const sidebarOpen = useRecoilValue(sidebarOpenState); + const sidebarOpen = useAtomValue(sidebarOpenState); - const trivet = useRecoilValue(trivetState); + const trivet = useAtomValue(trivetState); const runMenuCommand: typeof runMenuCommandImpl = (command) => { setFileMenuOpen(false); diff --git a/packages/app/src/components/PluginsOverlay.tsx b/packages/app/src/components/PluginsOverlay.tsx index 985ddd843..8b8326315 100644 --- a/packages/app/src/components/PluginsOverlay.tsx +++ b/packages/app/src/components/PluginsOverlay.tsx @@ -13,7 +13,6 @@ import CopyIcon from 'majesticons/line/clipboard-line.svg?react'; import GithubMark from '../assets/vendor_logos/github-mark-white.svg?react'; import { copyToClipboard } from '../utils/copyToClipboard'; import { useLoadPackagePlugin } from '../hooks/useLoadPackagePlugin'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { pluginsState } from '../state/plugins'; import useAsyncEffect from 'use-async-effect'; import { type BuiltInPluginInfo, type PackagePluginInfo, pluginInfos, type PluginInfo } from '../plugins.js'; @@ -25,6 +24,7 @@ import clsx from 'clsx'; import { useMarkdown } from '../hooks/useMarkdown'; import { projectPluginsState } from '../state/savedGraphs'; import { produce } from 'immer'; +import { useAtom, useAtomValue } from 'jotai'; const styles = css` position: fixed; @@ -206,7 +206,7 @@ const addPluginBody = css` `; export const PluginsOverlayRenderer: FC = () => { - const [openOverlay] = useRecoilState(overlayOpenState); + const openOverlay = useAtomValue(overlayOpenState); if (openOverlay !== 'plugins') return null; @@ -221,7 +221,7 @@ export const PluginsOverlay: FC = () => { const { loadPackagePlugin, packageInstallLog, setPackageInstallLog } = useLoadPackagePlugin({ onLog: (msg) => console.log(msg), }); - const plugins = useRecoilValue(pluginsState); + const plugins = useAtomValue(pluginsState); const [searchText, setSearchText] = useState(''); const isPluginInstalledInProject = (plugin: PluginInfo): boolean => { @@ -230,11 +230,11 @@ export const PluginsOverlay: FC = () => { const [pluginLogModalOpen, togglePluginLogModal] = useToggle(); const [addNPMPluginModalOpen, toggleAddNPMPluginModal] = useToggle(); - const setPluginSpecs = useSetRecoilState(projectPluginsState); + const [pluginSpecs, setPluginSpecs] = useAtom(projectPluginsState); const addPluginSpec = (spec: PluginLoadSpec) => { - setPluginSpecs((specs) => - produce(specs, (draft) => { + setPluginSpecs( + produce(pluginSpecs, (draft) => { if (draft.find((s) => s.id === spec.id)) { return; } diff --git a/packages/app/src/components/PortInfo.tsx b/packages/app/src/components/PortInfo.tsx index e6d0a8739..210803444 100644 --- a/packages/app/src/components/PortInfo.tsx +++ b/packages/app/src/components/PortInfo.tsx @@ -9,9 +9,9 @@ import { type PortId, } from '@ironclad/rivet-core'; import { type CSSProperties, forwardRef, useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { draggingWireState } from '../state/graphBuilder'; -import { lastRunData, selectedProcessPage } from '../state/dataFlow'; +import { lastRunDataState, selectedProcessPageState } from '../state/dataFlow'; import clsx from 'clsx'; import { RenderDataValue } from './RenderDataValue'; @@ -109,8 +109,8 @@ export const PortInfo = forwardRef< const { definition } = port; const { dataType, title, description, id } = definition; - const lastRun = useRecoilValue(lastRunData(port.nodeId)); - const selectedPage = useRecoilValue(selectedProcessPage(port.nodeId)); + const lastRun = useAtomValue(lastRunDataState(port.nodeId)); + const selectedPage = useAtomValue(selectedProcessPageState(port.nodeId)); const portData = useMemo(() => { if (!lastRun || selectedPage == null) { @@ -132,7 +132,7 @@ export const PortInfo = forwardRef< const didNotRun = portData?.[port.portId]?.type === 'control-flow-excluded'; - const draggingWire = useRecoilValue(draggingWireState); + const draggingWire = useAtomValue(draggingWireState); const dataTypeDisplay: string = Array.isArray(dataType) ? dataType.join(' or ') : (dataType as string); let dataTypeDisplayWithCoerced = dataTypeDisplay; diff --git a/packages/app/src/components/ProjectInfoSidebarTab.tsx b/packages/app/src/components/ProjectInfoSidebarTab.tsx index aa46dee54..c773d23e6 100644 --- a/packages/app/src/components/ProjectInfoSidebarTab.tsx +++ b/packages/app/src/components/ProjectInfoSidebarTab.tsx @@ -3,7 +3,6 @@ import { InlineEditableTextfield } from '@atlaskit/inline-edit'; import { ProjectPluginsConfiguration } from './ProjectPluginConfiguration'; import { Field, Label } from '@atlaskit/form'; import Select from '@atlaskit/select'; -import { useRecoilState } from 'recoil'; import { projectContextState, projectState, savedGraphsState } from '../state/savedGraphs'; import Button from '@atlaskit/button'; import Modal, { ModalTransition, ModalHeader, ModalTitle, ModalBody, ModalFooter } from '@atlaskit/modal-dialog'; @@ -15,6 +14,7 @@ import Toggle from '@atlaskit/toggle'; import { entries } from '../../../core/src/utils/typeSafety'; import { css } from '@emotion/react'; import { ProjectRevisions } from './ProjectRevisionList'; +import { useAtom, useAtomValue } from 'jotai'; const styles = css` .context-list { @@ -69,9 +69,9 @@ type ContextValue = { }; export const ProjectInfoSidebarTab: FC = () => { - const [project, setProject] = useRecoilState(projectState); - const [savedGraphs, setSavedGraphs] = useRecoilState(savedGraphsState); - const [projectContext, setProjectContext] = useRecoilState(projectContextState(project.metadata.id)); + const [project, setProject] = useAtom(projectState); + const savedGraphs = useAtomValue(savedGraphsState); + const [projectContext, setProjectContext] = useAtom(projectContextState(project.metadata.id)); const [projectEditContextModalOpen, toggleProjectEditContextModalOpen] = useToggle(false); const [editContextData, setEditContextData] = useState(); diff --git a/packages/app/src/components/ProjectPluginConfiguration.tsx b/packages/app/src/components/ProjectPluginConfiguration.tsx index ad43e421c..2c9dd2d3e 100644 --- a/packages/app/src/components/ProjectPluginConfiguration.tsx +++ b/packages/app/src/components/ProjectPluginConfiguration.tsx @@ -1,6 +1,6 @@ import { Label } from '@atlaskit/form'; import { type FC } from 'react'; -import { useRecoilState } from 'recoil'; +import { useAtom } from 'jotai'; import { projectPluginsState } from '../state/savedGraphs'; import MoreMenuVerticalIcon from 'majesticons/line/more-menu-vertical-line.svg?react'; import DeleteBinIcon from 'majesticons/line/delete-bin-line.svg?react'; @@ -85,10 +85,10 @@ const styles = css` `; export const ProjectPluginsConfiguration: FC = () => { - const [pluginSpecs, setPluginSpecs] = useRecoilState(projectPluginsState); + const [pluginSpecs, setPluginSpecs] = useAtom(projectPluginsState); const deletePlugin = (spec: PluginLoadSpec) => { - setPluginSpecs((specs) => specs.filter((s) => s.id !== spec.id)); + setPluginSpecs((prev) => (prev || []).filter((s) => s.id !== spec.id)); }; return ( diff --git a/packages/app/src/components/ProjectRevisionList.tsx b/packages/app/src/components/ProjectRevisionList.tsx index 989852689..ea376be76 100644 --- a/packages/app/src/components/ProjectRevisionList.tsx +++ b/packages/app/src/components/ProjectRevisionList.tsx @@ -1,6 +1,6 @@ import { useState, type FC } from 'react'; import { loadedProjectState, projectState } from '../state/savedGraphs'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { revisionStyles } from './GraphRevisionList'; import Button from '@atlaskit/button'; import { useHasGitHistory, useProjectRevisions } from '../hooks/useGraphRevisions'; @@ -11,7 +11,7 @@ import { type GraphId } from '@ironclad/rivet-core'; import { useChooseHistoricalGraph } from '../hooks/useChooseHistoricalGraph'; export const ProjectRevisions: FC = () => { - const projectState = useRecoilValue(loadedProjectState); + const projectState = useAtomValue(loadedProjectState); const [enabled, setEnabled] = useState(false); diff --git a/packages/app/src/components/ProjectSelector.tsx b/packages/app/src/components/ProjectSelector.tsx index 71fe8f0ae..f488a0399 100644 --- a/packages/app/src/components/ProjectSelector.tsx +++ b/packages/app/src/components/ProjectSelector.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/react'; import { useMemo, type FC } from 'react'; import { DndContext, type DragEndEvent } from '@dnd-kit/core'; import { type ProjectId } from '@ironclad/rivet-core'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import CloseIcon from 'majesticons/line/multiply-line.svg?react'; import BlankFileIcon from 'majesticons/line/file-line.svg?react'; import FileIcon from 'majesticons/line/file-plus-line.svg?react'; @@ -210,9 +210,9 @@ export const styles = css` `; export const ProjectSelector: FC = () => { - const setProjects = useSetRecoilState(projectsState); - const [openedProjects, setOpenedProjects] = useRecoilState(openedProjectsState); - const [openedProjectsSortedIds, setOpenedProjectsSortedIds] = useRecoilState(openedProjectsSortedIdsState); + const setProjects = useSetAtom(projectsState); + const openedProjects = useAtomValue(openedProjectsState); + const [openedProjectsSortedIds, setOpenedProjectsSortedIds] = useAtom(openedProjectsSortedIdsState); const sortedOpenedProjects = useMemo(() => { return openedProjectsSortedIds @@ -224,17 +224,17 @@ export const ProjectSelector: FC = () => { }, [openedProjectsSortedIds, openedProjects]); const loadProject = useLoadProject(); - const setNewProjectModalOpen = useSetRecoilState(newProjectModalOpenState); + const setNewProjectModalOpen = useSetAtom(newProjectModalOpenState); const loadProjectWithFileBrowser = useLoadProjectWithFileBrowser(); useSyncCurrentStateIntoOpenedProjects(); const handleDragEnd = ({ active, over }: DragEndEvent) => { if (over && active.id !== over.id) { - setOpenedProjectsSortedIds((openedProjectsSortedIds) => { - const oldIndex = openedProjectsSortedIds.indexOf(active?.id as ProjectId); - const newIndex = openedProjectsSortedIds.indexOf(over?.id as ProjectId); - return arrayMove(openedProjectsSortedIds, oldIndex, newIndex); + setOpenedProjectsSortedIds((prev) => { + const oldIndex = prev.indexOf(active?.id as ProjectId); + const newIndex = prev.indexOf(over?.id as ProjectId); + return arrayMove(prev, oldIndex, newIndex); }); } }; @@ -342,8 +342,8 @@ export const ProjectTab: FC<{ onCloseProject?: () => void; onSelectProject?: () => void; }> = ({ projectId, dragListeners, onCloseProject, onSelectProject }) => { - const openedProjects = useRecoilValue(openedProjectsState); - const currentProject = useRecoilValue(projectState); + const openedProjects = useAtomValue(openedProjectsState); + const currentProject = useAtomValue(projectState); const project = openedProjects[projectId]; diff --git a/packages/app/src/components/PromptDesigner.tsx b/packages/app/src/components/PromptDesigner.tsx index abcd15b40..f24ad53f4 100644 --- a/packages/app/src/components/PromptDesigner.tsx +++ b/packages/app/src/components/PromptDesigner.tsx @@ -1,7 +1,6 @@ -import Button from '@atlaskit/button'; import { css } from '@emotion/react'; import { type ChangeEvent, type FC, useEffect, useState, useRef, useLayoutEffect } from 'react'; -import { atom, useRecoilState, useRecoilValue } from 'recoil'; +import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; import { type PromptDesignerTestGroupResults, promptDesignerAttachedChatNodeState, @@ -38,6 +37,7 @@ import { import TextField from '@atlaskit/textfield'; import { Field } from '@atlaskit/form'; import Tabs, { Tab, TabList, TabPanel } from '@atlaskit/tabs'; +import Button from '@atlaskit/button'; import Select from '@atlaskit/select'; import Toggle from '@atlaskit/toggle'; import { nanoid } from 'nanoid/non-secure'; @@ -290,7 +290,7 @@ const styles = css` `; export const PromptDesignerRenderer: FC = () => { - const [openOverlay, setOpenOverlay] = useRecoilState(overlayOpenState); + const [openOverlay, setOpenOverlay] = useAtom(overlayOpenState); if (openOverlay !== 'promptDesigner') { return null; @@ -303,26 +303,23 @@ export type PromptDesignerProps = { onClose: () => void; }; -const lastPromptDesignerAttachedNodeState = atom({ - key: 'lastPromptDesignerAttachedNodeState', - default: undefined, -}); +const lastPromptDesignerAttachedNodeState = atom(undefined); export const PromptDesigner: FC = ({ onClose }) => { - const [{ messages }, setMessages] = useRecoilState(promptDesignerMessagesState); - const attachedNodeId = useRecoilValue(promptDesignerAttachedChatNodeState); - const [, setNodes] = useRecoilState(nodesState); - const nodeOutput = useRecoilValue(lastRunDataByNodeState); - const [config, setConfig] = useRecoilState(promptDesignerConfigurationState); - const [response, setResponse] = useRecoilState(promptDesignerResponseState); - const [promptDesigner, setPromptDesigner] = useRecoilState(promptDesignerState); - const nodesById = useRecoilValue(nodesByIdState); + const [{ messages }, setMessages] = useAtom(promptDesignerMessagesState); + const attachedNodeId = useAtomValue(promptDesignerAttachedChatNodeState); + const [nodes, setNodes] = useAtom(nodesState); + const nodeOutput = useAtomValue(lastRunDataByNodeState); + const [config, setConfig] = useAtom(promptDesignerConfigurationState); + const [response, setResponse] = useAtom(promptDesignerResponseState); + const [promptDesigner, setPromptDesigner] = useAtom(promptDesignerState); + const nodesById = useAtomValue(nodesByIdState); const attachedNode = attachedNodeId?.nodeId ? (nodesById[attachedNodeId.nodeId] as ChatNode) : undefined; const testGroups = attachedNode?.tests ?? []; - const [lastPromptDesignerAttachedNode, setLastPromptDesignerAttachedNode] = useRecoilState( + const [lastPromptDesignerAttachedNode, setLastPromptDesignerAttachedNode] = useAtom( lastPromptDesignerAttachedNodeState, ); @@ -379,7 +376,7 @@ export const PromptDesigner: FC = ({ onClose }) => { ]); const attachedNodeChanged = (newNode: ChatNode) => { - setNodes((s) => s.map((n) => (n.id === newNode.id ? newNode : n))); + setNodes((prev) => prev.map((n) => (n.id === newNode.id ? newNode : n))); }; const messageChanged = (newMessage: ChatMessage, index: number) => { @@ -446,9 +443,7 @@ export const PromptDesigner: FC = ({ onClose }) => { const runTestGroup = useRunTestGroupSampleCount(); - const [testGroupResultsByNodeId, setTestGroupResultsByNodeId] = useRecoilState( - promptDesignerTestGroupResultsByNodeIdState, - ); + const [testGroupResultsByNodeId, setTestGroupResultsByNodeId] = useAtom(promptDesignerTestGroupResultsByNodeIdState); const resultsForAttachedNode = testGroupResultsByNodeId[attachedNodeId?.nodeId ?? '']; @@ -986,8 +981,8 @@ async function runAdHocChat(messages: ChatMessage[], data: ChatNodeConfigData, c } function useRunTestGroup() { - const project = useRecoilValue(projectState); - const settings = useRecoilValue(settingsState); + const project = useAtomValue(projectState); + const settings = useAtomValue(settingsState); return async ( testGroup: NodeTestGroup, diff --git a/packages/app/src/components/RivetApp.tsx b/packages/app/src/components/RivetApp.tsx index a2abc556e..5cb357ff4 100644 --- a/packages/app/src/components/RivetApp.tsx +++ b/packages/app/src/components/RivetApp.tsx @@ -15,7 +15,7 @@ import { TrivetRenderer } from './trivet/Trivet.js'; import { ActionBar } from './ActionBar'; import { DebuggerPanelRenderer } from './DebuggerConnectPanel'; import { ChatViewerRenderer } from './ChatViewer'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { themeState } from '../state/settings'; import clsx from 'clsx'; import { useLoadStaticData } from '../hooks/useLoadStaticData'; @@ -31,7 +31,7 @@ import { NewProjectModalRenderer } from './NewProjectModal'; import { useWindowTitle } from '../hooks/useWindowTitle'; import { CommunityOverlayRenderer } from './community/CommunityOverlay'; import { HelpModal } from './HelpModal'; -import { openedProjectsSortedIdsState, projectsState } from '../state/savedGraphs'; +import { openedProjectsSortedIdsState } from '../state/savedGraphs'; import { NoProject } from './NoProject'; const styles = css` @@ -44,8 +44,8 @@ setGlobalTheme({ export const RivetApp: FC = () => { const { tryRunGraph, tryRunTests, tryAbortGraph, tryPauseGraph, tryResumeGraph } = useGraphExecutor(); - const theme = useRecoilValue(themeState); - const openedProjectIds = useRecoilValue(openedProjectsSortedIdsState); + const theme = useAtomValue(themeState); + const openedProjectIds = useAtomValue(openedProjectsSortedIdsState); const noProjectOpen = openedProjectIds.length === 0; diff --git a/packages/app/src/components/SettingsModal.tsx b/packages/app/src/components/SettingsModal.tsx index b8d61f4c7..6aa200605 100644 --- a/packages/app/src/components/SettingsModal.tsx +++ b/packages/app/src/components/SettingsModal.tsx @@ -1,5 +1,5 @@ import { type FC, useState } from 'react'; -import { atom, useRecoilState, useRecoilValue } from 'recoil'; +import { atom, useAtom, useAtomValue } from 'jotai'; import { checkForUpdatesState, defaultExecutorState, @@ -35,10 +35,7 @@ import { getVersion } from '@tauri-apps/api/app'; interface SettingsModalProps {} -export const settingsModalOpenState = atom({ - key: 'settingsModalOpen', - default: false, -}); +export const settingsModalOpenState = atom(false); const modalBody = css` min-height: 300px; @@ -64,7 +61,7 @@ const buttonsContainer = css` `; export const SettingsModal: FC = () => { - const [isOpen, setIsOpen] = useRecoilState(settingsModalOpenState); + const [isOpen, setIsOpen] = useAtom(settingsModalOpenState); const [page, setPage] = useState('general'); const closeModal = () => setIsOpen(false); @@ -118,13 +115,13 @@ export const SettingsModal: FC = () => { }; export const GeneralSettingsPage: FC = () => { - const [settings, setSettings] = useRecoilState(settingsState); - const [theme, setTheme] = useRecoilState(themeState); - const [recordExecutions, setRecordExecutions] = useRecoilState(recordExecutionsState); - const [defaultExecutor, setDefaultExecutor] = useRecoilState(defaultExecutorState); - const [previousDataPerNodeToKeep, setPreviousDataPerNodeToKeep] = useRecoilState(previousDataPerNodeToKeepState); - const [zoomSensitivity, setZoomSensitivity] = useRecoilState(zoomSensitivityState); - const [preservePortTextCase, setPreservePortTextCase] = useRecoilState(preservePortTextCaseState); + const [settings, setSettings] = useAtom(settingsState); + const [theme, setTheme] = useAtom(themeState); + const [recordExecutions, setRecordExecutions] = useAtom(recordExecutionsState); + const [defaultExecutor, setDefaultExecutor] = useAtom(defaultExecutorState); + const [previousDataPerNodeToKeep, setPreviousDataPerNodeToKeep] = useAtom(previousDataPerNodeToKeepState); + const [zoomSensitivity, setZoomSensitivity] = useAtom(zoomSensitivityState); + const [preservePortTextCase, setPreservePortTextCase] = useAtom(preservePortTextCaseState); return (
@@ -280,7 +277,7 @@ const fields = css` `; export const OpenAiSettingsPage: FC = () => { - const [settings, setSettings] = useRecoilState(settingsState); + const [settings, setSettings] = useAtom(settingsState); const chatNodeHeadersPairs = entries(settings.chatNodeHeaders ?? {}).map(([key, value]) => ({ key, @@ -433,7 +430,7 @@ export const OpenAiSettingsPage: FC = () => { export const PluginsSettingsPage: FC = () => { const plugins = useDependsOnPlugins(); - const [settings, setSettings] = useRecoilState(settingsState); + const [settings, setSettings] = useAtom(settingsState); if (plugins.length === 0) { return ( @@ -494,7 +491,7 @@ export const PluginsSettingsPage: FC = () => { export const UpdatesSettingsPage: FC = () => { const checkForUpdatesNow = useCheckForUpdate({ notifyNoUpdates: true, force: true }); - const [checkForUpdates, setCheckForUpdates] = useRecoilState(checkForUpdatesState); + const [checkForUpdates, setCheckForUpdates] = useAtom(checkForUpdatesState); const [currentVersion, setCurrentVersion] = useState(''); @@ -502,7 +499,7 @@ export const UpdatesSettingsPage: FC = () => { setCurrentVersion(await getVersion()); }, []); - const skippedMaxVersion = useRecoilValue(skippedMaxVersionState); + const skippedMaxVersion = useAtomValue(skippedMaxVersionState); return (
diff --git a/packages/app/src/components/StatusBar.tsx b/packages/app/src/components/StatusBar.tsx index 7cd40ef62..8a137bd49 100644 --- a/packages/app/src/components/StatusBar.tsx +++ b/packages/app/src/components/StatusBar.tsx @@ -1,9 +1,9 @@ import { useState, type FC, useRef, useEffect } from 'react'; import { css } from '@emotion/react'; import { useTotalRunCost } from '../hooks/useTotalRunCost'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { graphRunningState, graphStartTimeState } from '../state/dataFlow'; -import { useInterval, useLatest } from 'ahooks'; +import { useLatest } from 'ahooks'; import prettyMs from 'pretty-ms'; const styles = css` @@ -28,8 +28,8 @@ const styles = css` export const StatusBar: FC<{}> = () => { const { cost, tokens } = useTotalRunCost(); - const graphRunning = useRecoilValue(graphRunningState); - const graphStartTime = useRecoilValue(graphStartTimeState); + const graphRunning = useAtomValue(graphRunningState); + const graphStartTime = useAtomValue(graphStartTimeState); const runtimeRef = useRef(null); const latestGraphRunning = useLatest(graphRunning); diff --git a/packages/app/src/components/UpdateModal.tsx b/packages/app/src/components/UpdateModal.tsx index e6244af0e..5a1514e64 100644 --- a/packages/app/src/components/UpdateModal.tsx +++ b/packages/app/src/components/UpdateModal.tsx @@ -1,7 +1,7 @@ import { useState, type FC } from 'react'; import Modal, { ModalTransition, ModalBody, ModalFooter, ModalHeader, ModalTitle } from '@atlaskit/modal-dialog'; -import { useRecoilState, useSetRecoilState } from 'recoil'; +import { useAtom, useSetAtom } from 'jotai'; import { skippedMaxVersionState, updateModalOpenState, updateStatusState } from '../state/settings'; import Button from '@atlaskit/button'; import useAsyncEffect from 'use-async-effect'; @@ -18,16 +18,16 @@ const bodyStyle = css` `; export const UpdateModalRenderer: FC = () => { - const [modalOpen] = useRecoilState(updateModalOpenState); + const [modalOpen] = useAtom(updateModalOpenState); return {modalOpen && }; }; export const UpdateModal: FC = () => { - const setModalOpen = useSetRecoilState(updateModalOpenState); + const setModalOpen = useSetAtom(updateModalOpenState); const [isUpdating, setIsUpdating] = useState(false); - const [updateStatus, setUpdateStatus] = useRecoilState(updateStatusState); - const setSkippedMaxVersion = useSetRecoilState(skippedMaxVersionState); + const [updateStatus, setUpdateStatus] = useAtom(updateStatusState); + const setSkippedMaxVersion = useSetAtom(skippedMaxVersionState); const [currentVersion, setCurrentVersion] = useState(''); const [latestVersion, setLatestVersion] = useState(''); diff --git a/packages/app/src/components/UserInputModal.tsx b/packages/app/src/components/UserInputModal.tsx index 317c0082d..ec8e1eef5 100644 --- a/packages/app/src/components/UserInputModal.tsx +++ b/packages/app/src/components/UserInputModal.tsx @@ -1,5 +1,5 @@ import { type FC, Suspense, useEffect, useRef, useState, useMemo } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { type UserInputNode, type ArrayDataValue, type StringDataValue, type NodeId } from '@ironclad/rivet-core'; import { lastAnswersState } from '../state/userInput.js'; import Modal, { ModalBody, ModalFooter, ModalHeader, ModalTitle, ModalTransition } from '@atlaskit/modal-dialog'; @@ -45,10 +45,10 @@ type UserInputModalProps = { export const UserInputModal: FC = ({ open, questions, questionsNodeId, onSubmit, onClose }) => { const [answers, setAnswers] = useState([]); - const [lastAnswers, setLastAnswers] = useRecoilState(lastAnswersState); + const [lastAnswers, setLastAnswers] = useAtom(lastAnswersState); - const project = useRecoilValue(projectState); - const currentGraphNodes = useRecoilValue(nodesState); + const project = useAtomValue(projectState); + const currentGraphNodes = useAtomValue(nodesState); const questionsNode = useMemo(() => { if (!questionsNodeId) { @@ -72,17 +72,21 @@ export const UserInputModal: FC = ({ open, questions, quest }, [open, lastAnswers, questions]); const handleChange = (index: number, value: string) => { - const newAnswers = [...answers]; - newAnswers[index] = value; - setAnswers(newAnswers); + setAnswers((prev) => { + const newAnswers = [...prev]; + newAnswers[index] = value; + return newAnswers; + }); }; const handleSubmit = () => { - const newLastAnswers = { ...lastAnswers }; - questions.forEach((question, index) => { - newLastAnswers[question] = answers[index]!; + setLastAnswers((prev) => { + const newLastAnswers = { ...prev }; + questions.forEach((question, index) => { + newLastAnswers[question] = answers[index]!; + }); + return newLastAnswers; }); - setLastAnswers(newLastAnswers); const results: ArrayDataValue = { type: 'string[]', value: answers }; onSubmit(results); diff --git a/packages/app/src/components/VisualNode.tsx b/packages/app/src/components/VisualNode.tsx index 4dfc1bd42..35ae0fb33 100644 --- a/packages/app/src/components/VisualNode.tsx +++ b/packages/app/src/components/VisualNode.tsx @@ -10,7 +10,7 @@ import { useState, useMemo, } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; import { match } from 'ts-pattern'; import { type NodeInputDefinition, @@ -22,7 +22,7 @@ import { type NodeOutputDefinition, } from '@ironclad/rivet-core'; import type { HeightCache } from '../hooks/useNodeBodyHeight'; -import { type ProcessDataForNode, lastRunData, selectedProcessPage } from '../state/dataFlow.js'; +import { type ProcessDataForNode } from '../state/dataFlow.js'; import { NodeBody } from './NodeBody.js'; import { NodeOutput } from './NodeOutput.js'; import SettingsCogIcon from 'majesticons/line/settings-cog-line.svg?react'; @@ -336,8 +336,8 @@ const ZoomedOutVisualNodeContent: FC<{ onSelectNode?.(event.shiftKey); }); - const draggingWire = useRecoilValue(draggingWireState); - const closestPortToDraggingWire = useRecoilValue(draggingWireClosestPortState); + const draggingWire = useAtomValue(draggingWireState); + const closestPortToDraggingWire = useAtomValue(draggingWireClosestPortState); return ( <> @@ -550,10 +550,10 @@ const NormalVisualNodeContent: FC<{ onSelectNode?.(event.shiftKey); }); - const draggingWire = useRecoilValue(draggingWireState); - const closestPortToDraggingWire = useRecoilValue(draggingWireClosestPortState); + const draggingWire = useAtomValue(draggingWireState); + const closestPortToDraggingWire = useAtomValue(draggingWireClosestPortState); - const setPinnedNodes = useSetRecoilState(pinnedNodesState); + const setPinnedNodes = useSetAtom(pinnedNodesState); const togglePinned = useStableCallback(() => { setPinnedNodes((prev) => { @@ -565,7 +565,7 @@ const NormalVisualNodeContent: FC<{ }); }); - const setViewingNodeChanges = useSetRecoilState(viewingNodeChangesState); + const setViewingNodeChanges = useSetAtom(viewingNodeChangesState); const viewChanges = () => { if (!isHistoricalChanged) { diff --git a/packages/app/src/components/Wire.tsx b/packages/app/src/components/Wire.tsx index e3c63e05b..31ef63568 100644 --- a/packages/app/src/components/Wire.tsx +++ b/packages/app/src/components/Wire.tsx @@ -1,6 +1,6 @@ import { type FC, memo } from 'react'; import { type ChartNode, type NodeConnection, type NodeId, type PortId } from '@ironclad/rivet-core'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import clsx from 'clsx'; import { ErrorBoundary } from 'react-error-boundary'; import { nodeByIdState } from '../state/graph'; @@ -62,7 +62,7 @@ export const PartialWire: FC<{ connection: PartialConnection; portPositions: Por connection, portPositions, }) => { - const node = useRecoilValue(nodeByIdState(connection.nodeId)); + const node = useAtomValue(nodeByIdState(connection.nodeId)); if (!node) { return null; diff --git a/packages/app/src/components/WireLayer.tsx b/packages/app/src/components/WireLayer.tsx index 9150fc377..f58bb8085 100644 --- a/packages/app/src/components/WireLayer.tsx +++ b/packages/app/src/components/WireLayer.tsx @@ -4,7 +4,6 @@ import { css } from '@emotion/react'; import { ConditionallyRenderWire, PartialWire, getConnectionCacheKeys, getNodePortPosition } from './Wire.js'; import { canvasToClientPosition, useCanvasPositioning } from '../hooks/useCanvasPositioning.js'; import { ErrorBoundary } from 'react-error-boundary'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { draggingWireClosestPortState } from '../state/graphBuilder.js'; import { orderBy } from 'lodash-es'; import { ioDefinitionsState, nodesByIdState } from '../state/graph'; @@ -13,6 +12,7 @@ import { type RunDataByNodeId, lastRunDataByNodeState, selectedProcessPageNodesS import select from '@atlaskit/select/dist/types/entry-points/select'; import { useStableCallback } from '../hooks/useStableCallback'; import { lineCrossesViewport } from '../utils/lineClipping'; +import { useAtom, useAtomValue } from 'jotai'; const wiresStyles = css` width: 100%; @@ -66,12 +66,11 @@ export const WireLayer: FC = ({ highlightedPort, }) => { const [mousePosition, setMousePosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); - const [closestPort, setClosestPort] = useRecoilState(draggingWireClosestPortState); - const ioByNode = useRecoilValue(ioDefinitionsState); + const [closestPort, setClosestPort] = useAtom(draggingWireClosestPortState); + const ioByNode = useAtomValue(ioDefinitionsState); - // Is this too inefficient? - const lastRunDataByNode = useRecoilValue(lastRunDataByNodeState); - const selectedProcessPageNodes = useRecoilValue(selectedProcessPageNodesState); + const lastRunDataByNode = useAtomValue(lastRunDataByNodeState); + const selectedProcessPageNodes = useAtomValue(selectedProcessPageNodesState); const handleMouseDown = useStableCallback((event: MouseEvent) => { const { clientX, clientY } = event; @@ -138,7 +137,7 @@ export const WireLayer: FC = ({ const { canvasPosition, clientToCanvasPosition, canvasToClientPosition } = useCanvasPositioning(); const mousePositionCanvas = clientToCanvasPosition(mousePosition.x, mousePosition.y); - const nodesById = useRecoilValue(nodesByIdState); + const nodesById = useAtomValue(nodesByIdState); // Despite having to run getNodePortPositions in ConditionallyRenderWire, it's still faster to filter here // using lineCrossesViewport, especially for gigantic graphs when zoomed in. Avoiding rendering thousands of diff --git a/packages/app/src/components/community/CommunityOverlay.tsx b/packages/app/src/components/community/CommunityOverlay.tsx index c5c3bd7b8..8a3729ef4 100644 --- a/packages/app/src/components/community/CommunityOverlay.tsx +++ b/packages/app/src/components/community/CommunityOverlay.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/react'; import { useState, type FC } from 'react'; -import { useRecoilState } from 'recoil'; +import { useAtomValue } from 'jotai'; import { overlayOpenState } from '../../state/ui'; import { ErrorBoundary } from 'react-error-boundary'; import { SideNavigation, ButtonItem, Section } from '@atlaskit/side-navigation'; @@ -44,7 +44,7 @@ const styles = css` `; export const CommunityOverlayRenderer: FC = () => { - const [openOverlay] = useRecoilState(overlayOpenState); + const openOverlay = useAtomValue(overlayOpenState); if (openOverlay !== 'community') return null; diff --git a/packages/app/src/components/community/CreateTemplateForm.tsx b/packages/app/src/components/community/CreateTemplateForm.tsx index c06b6f8e7..d42f3b2cc 100644 --- a/packages/app/src/components/community/CreateTemplateForm.tsx +++ b/packages/app/src/components/community/CreateTemplateForm.tsx @@ -5,7 +5,7 @@ import { type GraphId } from '@ironclad/rivet-core'; import clsx from 'clsx'; import { orderBy } from 'lodash-es'; import { type FormEvent, Suspense, useMemo, useState, type FC } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectState } from '../../state/savedGraphs'; import { LazyCodeEditor } from '../LazyComponents'; import { css } from '@emotion/react'; @@ -81,7 +81,7 @@ export const CreateTemplateForm: FC<{ versionDescription: string; }) => void; }> = ({ existingTemplate, working, onCreate }) => { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const [templateName, setTemplateName] = useState(existingTemplate?.name ?? project.metadata.title); diff --git a/packages/app/src/components/community/NeedsLoginPage.tsx b/packages/app/src/components/community/NeedsLoginPage.tsx index 6c708a1a4..3de445cbe 100644 --- a/packages/app/src/components/community/NeedsLoginPage.tsx +++ b/packages/app/src/components/community/NeedsLoginPage.tsx @@ -1,13 +1,13 @@ import Button from '@atlaskit/button'; import { WebviewWindow } from '@tauri-apps/api/window'; import { type FC } from 'react'; -import { useSetRecoilState } from 'recoil'; +import { useSetAtom } from 'jotai'; import { getCommunityLoginUrl } from '../../utils/getCommunityApi'; import { isLoggedInToCommunityState } from '../../state/community'; export const NeedsLoginPage: FC = () => { const loginUrl = getCommunityLoginUrl(); - const setIsLoggedInToCommunity = useSetRecoilState(isLoggedInToCommunityState); + const setIsLoggedInToCommunity = useSetAtom(isLoggedInToCommunityState); const handleLogInClick = () => { const window = new WebviewWindow('login', { alwaysOnTop: true, center: true, url: loginUrl }); diff --git a/packages/app/src/components/dataStudio/DataStudio.tsx b/packages/app/src/components/dataStudio/DataStudio.tsx index 398ccbc7f..2f0d79add 100644 --- a/packages/app/src/components/dataStudio/DataStudio.tsx +++ b/packages/app/src/components/dataStudio/DataStudio.tsx @@ -1,5 +1,4 @@ import { type FC } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; import { css } from '@emotion/react'; import { ErrorBoundary } from 'react-error-boundary'; import { useDatasets } from '../../hooks/useDatasets'; @@ -8,9 +7,10 @@ import { projectState } from '../../state/savedGraphs'; import { overlayOpenState } from '../../state/ui'; import { DatasetList } from './DatasetList'; import { DatasetDisplay } from './DatasetDisplay'; +import { useAtom, useAtomValue } from 'jotai'; export const DataStudioRenderer: FC = () => { - const [openOverlay, setOpenOverlay] = useRecoilState(overlayOpenState); + const [openOverlay, setOpenOverlay] = useAtom(overlayOpenState); if (openOverlay !== 'dataStudio') return null; @@ -62,9 +62,9 @@ const styles = css` export const DataStudio: FC<{ onClose: () => void; }> = ({ onClose }) => { - const [selectedDataset, setSelectedDataset] = useRecoilState(selectedDatasetState); + const [selectedDataset, setSelectedDataset] = useAtom(selectedDatasetState); - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const { datasets } = useDatasets(project.metadata.id); const selectedDatasetMeta = datasets?.find((d) => d.id === selectedDataset); diff --git a/packages/app/src/components/dataStudio/DatasetDisplay.tsx b/packages/app/src/components/dataStudio/DatasetDisplay.tsx index c23b8e89b..d51eea23e 100644 --- a/packages/app/src/components/dataStudio/DatasetDisplay.tsx +++ b/packages/app/src/components/dataStudio/DatasetDisplay.tsx @@ -25,7 +25,7 @@ import useAsyncEffect from 'use-async-effect'; import { useGetAdHocInternalProcessContext } from '../../hooks/useGetAdHocInternalProcessContext'; import { InlineEditableTextfield } from '@atlaskit/inline-edit'; import { useDatasets } from '../../hooks/useDatasets'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectMetadataState } from '../../state/savedGraphs'; const datasetDisplayStyles = css` @@ -110,7 +110,7 @@ export const DatasetDisplay: FC<{ }> = ({ dataset, onChangedId }) => { const { dataset: datasetData, ...datasetMethods } = useDataset(dataset.id); - const projectMetadata = useRecoilValue(projectMetadataState); + const projectMetadata = useAtomValue(projectMetadataState); const datasetsMethods = useDatasets(projectMetadata.id); const { contextMenuData, contextMenuRef, handleContextMenu, showContextMenu } = useContextMenu(); diff --git a/packages/app/src/components/dataStudio/DatasetList.tsx b/packages/app/src/components/dataStudio/DatasetList.tsx index 3c38b4091..00de92d5f 100644 --- a/packages/app/src/components/dataStudio/DatasetList.tsx +++ b/packages/app/src/components/dataStudio/DatasetList.tsx @@ -4,7 +4,7 @@ import Portal from '@atlaskit/portal'; import { type DatasetId, type DatasetMetadata, newId, getError } from '@ironclad/rivet-core'; import { type FC, useState } from 'react'; import { toast } from 'react-toastify'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtomValue, useAtom } from 'jotai'; import { useContextMenu } from '../../hooks/useContextMenu'; import { useDatasets } from '../../hooks/useDatasets'; import { selectedDatasetState } from '../../state/dataStudio'; @@ -27,7 +27,7 @@ const contextMenuStyles = css` `; export const DatasetList: FC<{}> = () => { - const [selectedDataset, setSelectedDataset] = useRecoilState(selectedDatasetState); + const [selectedDataset, setSelectedDataset] = useAtom(selectedDatasetState); const { refs, floatingStyles, @@ -39,7 +39,7 @@ export const DatasetList: FC<{}> = () => { } = useContextMenu(); const [renamingDataset, setRenamingDataset] = useState(); - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const { datasets, ...datasetsMethods } = useDatasets(project.metadata.id); const newDataset = async () => { diff --git a/packages/app/src/components/editors/DatasetSelectorEditor.tsx b/packages/app/src/components/editors/DatasetSelectorEditor.tsx index 5413f8d93..103b59e1f 100644 --- a/packages/app/src/components/editors/DatasetSelectorEditor.tsx +++ b/packages/app/src/components/editors/DatasetSelectorEditor.tsx @@ -3,7 +3,7 @@ import Select from '@atlaskit/select'; import { type DatasetSelectorEditorDefinition, type ChartNode } from '@ironclad/rivet-core'; import { orderBy } from 'lodash-es'; import { type FC } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { useDatasets } from '../../hooks/useDatasets'; import { projectState } from '../../state/savedGraphs'; import { type SharedEditorProps } from './SharedEditorProps'; @@ -47,7 +47,7 @@ export const DatasetSelector: FC<{ helperMessage?: string; onChange?: (selected: string) => void; }> = ({ value, isReadonly, isDisabled = false, onChange, label, name, helperMessage }) => { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const { datasets } = useDatasets(project.metadata.id); const datasetOptions = orderBy( diff --git a/packages/app/src/components/editors/FileBrowserEditor.tsx b/packages/app/src/components/editors/FileBrowserEditor.tsx index 43903f20d..7db2e79dd 100644 --- a/packages/app/src/components/editors/FileBrowserEditor.tsx +++ b/packages/app/src/components/editors/FileBrowserEditor.tsx @@ -11,7 +11,7 @@ import { import { nanoid } from 'nanoid/non-secure'; import prettyBytes from 'pretty-bytes'; import { type FC } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectDataState } from '../../state/savedGraphs'; import { ioProvider } from '../../utils/globals'; import { type SharedEditorProps } from './SharedEditorProps'; @@ -24,7 +24,7 @@ export const DefaultFileBrowserEditor: FC< } > = ({ node, isReadonly, isDisabled, onChange, editor }) => { const data = node.data as Record; - const projectData = useRecoilValue(projectDataState); + const projectData = useAtomValue(projectDataState); const helperMessage = getHelperMessage(editor, node.data); const pickFile = async () => { diff --git a/packages/app/src/components/editors/GraphSelectorEditor.tsx b/packages/app/src/components/editors/GraphSelectorEditor.tsx index 1d5849a55..5e3501d83 100644 --- a/packages/app/src/components/editors/GraphSelectorEditor.tsx +++ b/packages/app/src/components/editors/GraphSelectorEditor.tsx @@ -5,7 +5,7 @@ import { Field, HelperMessage } from '@atlaskit/form'; import Select from '@atlaskit/select'; import { orderBy, values } from 'lodash-es'; import { nanoid } from 'nanoid/non-secure'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectState } from '../../state/savedGraphs'; import { getHelperMessage } from './editorUtils'; @@ -62,7 +62,7 @@ export const GraphSelectorSelect: FC<{ isReadonly?: boolean; onChange?: (selected: GraphId) => void; }> = ({ value, isReadonly, onChange }) => { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const graphOptions = orderBy( values(project.graphs).map((graph) => ({ diff --git a/packages/app/src/components/editors/ImageBrowserEditor.tsx b/packages/app/src/components/editors/ImageBrowserEditor.tsx index 18ae4acbf..d02fcfd9f 100644 --- a/packages/app/src/components/editors/ImageBrowserEditor.tsx +++ b/packages/app/src/components/editors/ImageBrowserEditor.tsx @@ -9,7 +9,7 @@ import { } from '@ironclad/rivet-core'; import { nanoid } from 'nanoid/non-secure'; import { type FC } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectDataState } from '../../state/savedGraphs'; import { ioProvider } from '../../utils/globals'; import { type SharedEditorProps } from './SharedEditorProps'; @@ -24,7 +24,7 @@ export const DefaultImageBrowserEditor: FC< const data = node.data as Record; const helperMessage = getHelperMessage(editor, node.data); - const dataState = useRecoilValue(projectDataState); + const dataState = useAtomValue(projectDataState); const pickFile = async () => { await ioProvider.readFileAsBinary(async (binaryData) => { diff --git a/packages/app/src/components/editors/custom/CodeNodeAIAssistEditor.tsx b/packages/app/src/components/editors/custom/CodeNodeAIAssistEditor.tsx index a140f6359..29ef9d6d1 100644 --- a/packages/app/src/components/editors/custom/CodeNodeAIAssistEditor.tsx +++ b/packages/app/src/components/editors/custom/CodeNodeAIAssistEditor.tsx @@ -17,7 +17,7 @@ import { css } from '@emotion/react'; import Select from '@atlaskit/select'; import { toast } from 'react-toastify'; import codeGeneratorProject from '../../../../graphs/code-node-generator.rivet-project?raw'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { settingsState } from '../../../state/settings'; import { fillMissingSettingsFromEnvironmentVariables } from '../../../utils/tauri'; import { useDependsOnPlugins } from '../../../hooks/useDependsOnPlugins'; @@ -47,7 +47,7 @@ export const CodeNodeAIAssistEditor: FC< const [working, setWorking] = useState(false); const [model, setModel] = useState('gpt-4o-mini'); - const settings = useRecoilValue(settingsState); + const settings = useAtomValue(settingsState); const plugins = useDependsOnPlugins(); const data = node.data as CodeNodeData; diff --git a/packages/app/src/components/editors/custom/ExtractRegexNodeAiAssistEditor.tsx b/packages/app/src/components/editors/custom/ExtractRegexNodeAiAssistEditor.tsx index 2845dd14d..34bdd33e4 100644 --- a/packages/app/src/components/editors/custom/ExtractRegexNodeAiAssistEditor.tsx +++ b/packages/app/src/components/editors/custom/ExtractRegexNodeAiAssistEditor.tsx @@ -17,7 +17,7 @@ import { css } from '@emotion/react'; import Select from '@atlaskit/select'; import { toast } from 'react-toastify'; import codeGeneratorProject from '../../../../graphs/code-node-generator.rivet-project?raw'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { settingsState } from '../../../state/settings'; import { fillMissingSettingsFromEnvironmentVariables } from '../../../utils/tauri'; import { useDependsOnPlugins } from '../../../hooks/useDependsOnPlugins'; @@ -47,7 +47,7 @@ export const ExtractRegexNodeAiAssistEditor: FC< const [working, setWorking] = useState(false); const [model, setModel] = useState('gpt-4o-mini'); - const settings = useRecoilValue(settingsState); + const settings = useAtomValue(settingsState); const plugins = useDependsOnPlugins(); const data = node.data as ExtractRegexNodeData; diff --git a/packages/app/src/components/editors/custom/GptFunctionJsonSchemaAiAssistEditor.tsx b/packages/app/src/components/editors/custom/GptFunctionJsonSchemaAiAssistEditor.tsx index cad0f7ee5..4188e6159 100644 --- a/packages/app/src/components/editors/custom/GptFunctionJsonSchemaAiAssistEditor.tsx +++ b/packages/app/src/components/editors/custom/GptFunctionJsonSchemaAiAssistEditor.tsx @@ -16,7 +16,7 @@ import { css } from '@emotion/react'; import Select from '@atlaskit/select'; import { toast } from 'react-toastify'; import codeGeneratorProject from '../../../../graphs/code-node-generator.rivet-project?raw'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { settingsState } from '../../../state/settings'; import { fillMissingSettingsFromEnvironmentVariables } from '../../../utils/tauri'; import { useDependsOnPlugins } from '../../../hooks/useDependsOnPlugins'; @@ -46,7 +46,7 @@ export const GptFunctionNodeJsonSchemaAiAssistEditor: FC< const [working, setWorking] = useState(false); const [model, setModel] = useState('gpt-4o-mini'); - const settings = useRecoilValue(settingsState); + const settings = useAtomValue(settingsState); const plugins = useDependsOnPlugins(); const data = node.data as GptFunctionNodeData; diff --git a/packages/app/src/components/editors/custom/ObjectNodeAiAssistEditor.tsx b/packages/app/src/components/editors/custom/ObjectNodeAiAssistEditor.tsx index f9ca61bd6..47f1da16a 100644 --- a/packages/app/src/components/editors/custom/ObjectNodeAiAssistEditor.tsx +++ b/packages/app/src/components/editors/custom/ObjectNodeAiAssistEditor.tsx @@ -17,7 +17,7 @@ import { css } from '@emotion/react'; import Select from '@atlaskit/select'; import { toast } from 'react-toastify'; import codeGeneratorProject from '../../../../graphs/code-node-generator.rivet-project?raw'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { settingsState } from '../../../state/settings'; import { fillMissingSettingsFromEnvironmentVariables } from '../../../utils/tauri'; import { useDependsOnPlugins } from '../../../hooks/useDependsOnPlugins'; @@ -47,7 +47,7 @@ export const ObjectNodeAiAssistEditor: FC< const [working, setWorking] = useState(false); const [model, setModel] = useState('gpt-4o-mini'); - const settings = useRecoilValue(settingsState); + const settings = useAtomValue(settingsState); const plugins = useDependsOnPlugins(); const data = node.data as ObjectNodeData; diff --git a/packages/app/src/components/gentrace/GentraceInteractors.tsx b/packages/app/src/components/gentrace/GentraceInteractors.tsx index bf1621da6..66448538d 100644 --- a/packages/app/src/components/gentrace/GentraceInteractors.tsx +++ b/packages/app/src/components/gentrace/GentraceInteractors.tsx @@ -7,7 +7,6 @@ import EditPen from 'majesticons/line/edit-pen-2-line.svg?react'; import TestTube from 'majesticons/line/test-tube-filled-line.svg?react'; import GentraceImage from '../../assets/vendor_logos/gentrace.svg?react'; import { toast } from 'react-toastify'; -import { useRecoilValue } from 'recoil'; import { runGentraceTests, runRemoteGentraceTests } from '../../../../core/src/plugins/gentrace/plugin'; import { useRemoteDebugger } from '../../hooks/useRemoteDebugger'; import { useRemoteExecutor } from '../../hooks/useRemoteExecutor'; @@ -18,13 +17,13 @@ import { settingsState } from '../../state/settings'; import { fillMissingSettingsFromEnvironmentVariables } from '../../utils/tauri'; import GentracePipelinePicker, { type GentracePipeline } from './GentracePipelinePicker'; import { entries } from '../../../../core/src/utils/typeSafety'; +import { useAtomValue } from 'jotai'; export const GentraceInteractors = () => { - const project = useRecoilValue(projectState); - const graph = useRecoilValue(graphState); - const savedSettings = useRecoilValue(settingsState); - const projectData = useRecoilValue(projectDataState); - const projectContext = useRecoilValue(projectContextState(project.metadata.id)); + const project = useAtomValue(projectState); + const graph = useAtomValue(graphState); + const savedSettings = useAtomValue(settingsState); + const projectContext = useAtomValue(projectContextState(project.metadata.id)); const remoteDebugger = useRemoteDebugger(); diff --git a/packages/app/src/components/gentrace/GentracePipelinePicker.tsx b/packages/app/src/components/gentrace/GentracePipelinePicker.tsx index 1be6ef1f2..612c1b323 100644 --- a/packages/app/src/components/gentrace/GentracePipelinePicker.tsx +++ b/packages/app/src/components/gentrace/GentracePipelinePicker.tsx @@ -3,7 +3,7 @@ import Select from '@atlaskit/select'; import { css } from '@emotion/react'; import { type FC, useEffect, useRef, useState } from 'react'; import { toast } from 'react-toastify'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { getGentracePipelines } from '../../../../core/src/plugins/gentrace/plugin'; import { graphState } from '../../state/graph'; import { settingsState } from '../../state/settings'; @@ -27,9 +27,9 @@ const pickerContainerStyles = css` `; const GentracePipelinePicker: FC = ({ onClose }) => { - const savedSettings = useRecoilValue(settingsState); + const savedSettings = useAtomValue(settingsState); - const [graph, setGraph] = useRecoilState(graphState); + const [graph, setGraph] = useAtom(graphState); const gentracePipelineSettings = graph?.metadata?.attachedData?.gentracePipeline as GentracePipeline | undefined; const currentGentracePipelineSlug = gentracePipelineSettings?.slug; @@ -80,16 +80,16 @@ const GentracePipelinePicker: FC = ({ onClose }) => const { cases, ...selectedPipelineNoCases } = selectedPipeline; - setGraph({ - ...graph, + setGraph((prev) => ({ + ...prev, metadata: { - ...graph.metadata, + ...prev.metadata, attachedData: { - ...(graph.metadata?.attachedData ?? {}), + ...(prev.metadata?.attachedData ?? {}), gentracePipeline: selectedPipelineNoCases, }, }, - }); + })); setSelectedPipeline(null); diff --git a/packages/app/src/components/nodes/AppendToDatasetNode.tsx b/packages/app/src/components/nodes/AppendToDatasetNode.tsx index a6a7fa8ca..368fac246 100644 --- a/packages/app/src/components/nodes/AppendToDatasetNode.tsx +++ b/packages/app/src/components/nodes/AppendToDatasetNode.tsx @@ -2,13 +2,13 @@ import { type AppendToDatasetNode } from '@ironclad/rivet-core'; import { type NodeComponentDescriptor } from '../../hooks/useNodeTypes'; import { type FC } from 'react'; import { useDatasets } from '../../hooks/useDatasets'; -import { useRecoilValue } from 'recoil'; import { projectState } from '../../state/savedGraphs'; +import { useAtomValue } from 'jotai'; export const AppendToDatasetNodeBody: FC<{ node: AppendToDatasetNode; }> = ({ node }) => { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const { datasets } = useDatasets(project.metadata.id); const dataset = datasets?.find((d) => d.id === node.data.datasetId); diff --git a/packages/app/src/components/nodes/AudioNode.tsx b/packages/app/src/components/nodes/AudioNode.tsx index ac4625ccf..0296ac29a 100644 --- a/packages/app/src/components/nodes/AudioNode.tsx +++ b/packages/app/src/components/nodes/AudioNode.tsx @@ -2,7 +2,7 @@ import { type FC, useLayoutEffect, useRef, useMemo } from 'react'; import { type NodeComponentDescriptor } from '../../hooks/useNodeTypes'; import { type AudioNode } from '@ironclad/rivet-core'; import { css } from '@emotion/react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectDataState } from '../../state/savedGraphs'; const styles = css` @@ -24,7 +24,7 @@ export const AudioNodeBody: FC = ({ node }) => { }; export const AudioNodeHasDataInput: FC = ({ node }) => { - const projectData = useRecoilValue(projectDataState); + const projectData = useAtomValue(projectDataState); const dataRef = node.data.data; diff --git a/packages/app/src/components/nodes/DatasetNearestNeighborsNode.tsx b/packages/app/src/components/nodes/DatasetNearestNeighborsNode.tsx index 4b2730327..bcb8438ba 100644 --- a/packages/app/src/components/nodes/DatasetNearestNeighborsNode.tsx +++ b/packages/app/src/components/nodes/DatasetNearestNeighborsNode.tsx @@ -2,13 +2,13 @@ import { type DatasetNearestNeighborsNode } from '@ironclad/rivet-core'; import { type NodeComponentDescriptor } from '../../hooks/useNodeTypes'; import { type FC } from 'react'; import { useDatasets } from '../../hooks/useDatasets'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectState } from '../../state/savedGraphs'; export const DatasetNearestNeightborsNode: FC<{ node: DatasetNearestNeighborsNode; }> = ({ node }) => { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const { datasets } = useDatasets(project.metadata.id); const dataset = datasets?.find((d) => d.id === node.data.datasetId); diff --git a/packages/app/src/components/nodes/GetDatasetRowNode.tsx b/packages/app/src/components/nodes/GetDatasetRowNode.tsx index 43c370b37..b28c73de3 100644 --- a/packages/app/src/components/nodes/GetDatasetRowNode.tsx +++ b/packages/app/src/components/nodes/GetDatasetRowNode.tsx @@ -2,13 +2,13 @@ import { type GetDatasetRowNode } from '@ironclad/rivet-core'; import { type NodeComponentDescriptor } from '../../hooks/useNodeTypes'; import { type FC } from 'react'; import { useDatasets } from '../../hooks/useDatasets'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectState } from '../../state/savedGraphs'; export const GetDatasetRowBody: FC<{ node: GetDatasetRowNode; }> = ({ node }) => { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const { datasets } = useDatasets(project.metadata.id); const dataset = datasets?.find((d) => d.id === node.data.datasetId); diff --git a/packages/app/src/components/nodes/ImageNode.tsx b/packages/app/src/components/nodes/ImageNode.tsx index 9f059ed36..438711ee0 100644 --- a/packages/app/src/components/nodes/ImageNode.tsx +++ b/packages/app/src/components/nodes/ImageNode.tsx @@ -2,7 +2,7 @@ import { type FC } from 'react'; import { type NodeComponentDescriptor } from '../../hooks/useNodeTypes'; import { type ImageNode } from '@ironclad/rivet-core'; import { css } from '@emotion/react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectDataState } from '../../state/savedGraphs'; const styles = css` @@ -16,7 +16,7 @@ type ImageNodeBodyProps = { }; export const ImageNodeBody: FC = ({ node }) => { - const projectData = useRecoilValue(projectDataState); + const projectData = useAtomValue(projectDataState); const dataRef = node.data.data; const b64Data = dataRef ? projectData?.[dataRef.refId] : undefined; diff --git a/packages/app/src/components/nodes/LoadDatasetNode.tsx b/packages/app/src/components/nodes/LoadDatasetNode.tsx index 2c99f5846..161b80496 100644 --- a/packages/app/src/components/nodes/LoadDatasetNode.tsx +++ b/packages/app/src/components/nodes/LoadDatasetNode.tsx @@ -2,13 +2,13 @@ import { type LoadDatasetNode } from '@ironclad/rivet-core'; import { type NodeComponentDescriptor } from '../../hooks/useNodeTypes'; import { type FC } from 'react'; import { useDatasets } from '../../hooks/useDatasets'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectState } from '../../state/savedGraphs'; export const LoadDatasetNodeBody: FC<{ node: LoadDatasetNode; }> = ({ node }) => { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const { datasets } = useDatasets(project.metadata.id); const dataset = datasets?.find((d) => d.id === node.data.datasetId); diff --git a/packages/app/src/components/nodes/ReplaceDatasetNode.tsx b/packages/app/src/components/nodes/ReplaceDatasetNode.tsx index 41b4cd5ee..92bf5c39c 100644 --- a/packages/app/src/components/nodes/ReplaceDatasetNode.tsx +++ b/packages/app/src/components/nodes/ReplaceDatasetNode.tsx @@ -2,13 +2,13 @@ import { type ReplaceDatasetNode } from '@ironclad/rivet-core'; import { type NodeComponentDescriptor } from '../../hooks/useNodeTypes'; import { type FC } from 'react'; import { useDatasets } from '../../hooks/useDatasets'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectState } from '../../state/savedGraphs'; export const ReplaceDatasetNodeBody: FC<{ node: ReplaceDatasetNode; }> = ({ node }) => { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const { datasets } = useDatasets(project.metadata.id); const dataset = datasets?.find((d) => d.id === node.data.datasetId); diff --git a/packages/app/src/components/nodes/SubGraphNode.tsx b/packages/app/src/components/nodes/SubGraphNode.tsx index 7ca47fb79..fbe41c4d0 100644 --- a/packages/app/src/components/nodes/SubGraphNode.tsx +++ b/packages/app/src/components/nodes/SubGraphNode.tsx @@ -1,5 +1,5 @@ import { type FC } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectState } from '../../state/savedGraphs.js'; import { type Outputs, type PortId, type SubGraphNode, coerceTypeOptional, type DataValue } from '@ironclad/rivet-core'; import { type NodeComponentDescriptor } from '../../hooks/useNodeTypes.js'; @@ -10,7 +10,7 @@ import { type InputsOrOutputsWithRefs } from '../../state/dataFlow'; export const SubGraphNodeBody: FC<{ node: SubGraphNode; }> = ({ node }) => { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const selectedGraph = project.graphs[node.data.graphId]; const selectedGraphName = selectedGraph?.metadata?.name ?? node.data.graphId; diff --git a/packages/app/src/components/trivet/TestCaseEditor.tsx b/packages/app/src/components/trivet/TestCaseEditor.tsx index 4160b3815..3a5f8b654 100644 --- a/packages/app/src/components/trivet/TestCaseEditor.tsx +++ b/packages/app/src/components/trivet/TestCaseEditor.tsx @@ -1,6 +1,6 @@ import { type FC, Suspense, useEffect, useMemo, useRef, useState } from 'react'; import { trivetState } from '../../state/trivet'; -import { useRecoilState } from 'recoil'; +import { useAtom } from 'jotai'; import Button from '@atlaskit/button'; import { css } from '@emotion/react'; import { isEqual, mean } from 'lodash-es'; @@ -56,8 +56,7 @@ const styles = css` `; export const TestCaseEditor: FC = () => { - const [{ testSuites, selectedTestSuiteId, editingTestCaseId, recentTestResults }, setState] = - useRecoilState(trivetState); + const [{ testSuites, selectedTestSuiteId, editingTestCaseId, recentTestResults }, setState] = useAtom(trivetState); const selectedTestSuite = useMemo( () => testSuites.find((ts) => ts.id === selectedTestSuiteId), [testSuites, selectedTestSuiteId], diff --git a/packages/app/src/components/trivet/TestSuite.tsx b/packages/app/src/components/trivet/TestSuite.tsx index 25af7b8d4..74657d267 100644 --- a/packages/app/src/components/trivet/TestSuite.tsx +++ b/packages/app/src/components/trivet/TestSuite.tsx @@ -1,7 +1,7 @@ import { type FC, useCallback, useMemo } from 'react'; import { TestCaseTable } from './TestCaseTable'; import { InlineEditableTextfield } from '@atlaskit/inline-edit'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { savedGraphsState } from '../../state/savedGraphs'; import { keyBy } from 'lodash-es'; import { type GraphId, type NodeGraph } from '@ironclad/rivet-core'; @@ -123,7 +123,7 @@ const styles = css` `; export const TestSuiteRenderer: FC<{ tryRunTests: TryRunTests }> = ({ tryRunTests }) => { - const { testSuites, selectedTestSuiteId } = useRecoilValue(trivetState); + const { testSuites, selectedTestSuiteId } = useAtomValue(trivetState); const testSuite = useMemo( () => testSuites.find((ts) => ts.id === selectedTestSuiteId), @@ -147,9 +147,8 @@ export const TestSuiteRenderer: FC<{ tryRunTests: TryRunTests }> = ({ tryRunTest }; export const TestSuite: FC<{ testSuite: TrivetTestSuite; tryRunTests: TryRunTests }> = ({ testSuite, tryRunTests }) => { - const [{ selectedTestSuiteId, editingTestCaseId, recentTestResults, runningTests }, setState] = - useRecoilState(trivetState); - const savedGraphs = useRecoilValue(savedGraphsState); + const [{ selectedTestSuiteId, editingTestCaseId, recentTestResults, runningTests }, setState] = useAtom(trivetState); + const savedGraphs = useAtomValue(savedGraphsState); const { addTestCase, updateTestSuite, testGraph, setEditingTestCase, deleteTestCase, duplicateTestCase } = useTestSuite(testSuite.id); diff --git a/packages/app/src/components/trivet/Trivet.tsx b/packages/app/src/components/trivet/Trivet.tsx index 518ae32d5..9047a8147 100644 --- a/packages/app/src/components/trivet/Trivet.tsx +++ b/packages/app/src/components/trivet/Trivet.tsx @@ -1,4 +1,4 @@ -import { useRecoilState } from 'recoil'; +import { useAtom } from 'jotai'; import { trivetState } from '../../state/trivet'; import { type FC, useCallback, useMemo } from 'react'; import { css } from '@emotion/react'; @@ -44,7 +44,7 @@ const styles = css` `; export const TrivetRenderer: FC<{ tryRunTests: TryRunTests }> = ({ tryRunTests }) => { - const [openOverlay, setOpenOverlay] = useRecoilState(overlayOpenState); + const [openOverlay, setOpenOverlay] = useAtom(overlayOpenState); if (openOverlay !== 'trivet') return null; @@ -57,7 +57,7 @@ export type TrivetContainerProps = { }; export const TrivetContainer: FC = ({ tryRunTests, onClose }) => { - const [{ testSuites, selectedTestSuiteId, runningTests, recentTestResults }, setState] = useRecoilState(trivetState); + const [{ testSuites, selectedTestSuiteId, runningTests, recentTestResults }, setState] = useAtom(trivetState); const selectedTestSuite = useMemo( () => testSuites.find((ts) => ts.id === selectedTestSuiteId), [testSuites, selectedTestSuiteId], diff --git a/packages/app/src/hooks/useCanvasHotkeys.ts b/packages/app/src/hooks/useCanvasHotkeys.ts index 7fe576113..07aaa8c4d 100644 --- a/packages/app/src/hooks/useCanvasHotkeys.ts +++ b/packages/app/src/hooks/useCanvasHotkeys.ts @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { type CanvasPosition, canvasPositionState, @@ -12,12 +12,12 @@ import { useViewportBounds } from './useViewportBounds'; import { useCanvasPositioning } from './useCanvasPositioning'; export function useCanvasHotkeys() { - const [canvasPosition, setCanvasPosition] = useRecoilState(canvasPositionState); + const [canvasPosition, setCanvasPosition] = useAtom(canvasPositionState); const viewportBounds = useViewportBounds(); const { canvasToClientPosition } = useCanvasPositioning(); - const setSearching = useSetRecoilState(searchingGraphState); - const setEditingNode = useSetRecoilState(editingNodeState); - const hoveringNode = useRecoilValue(hoveringNodeState); + const setSearching = useSetAtom(searchingGraphState); + const setEditingNode = useSetAtom(editingNodeState); + const hoveringNode = useAtomValue(hoveringNodeState); const latestHandler = useLatest((e: KeyboardEvent) => { // If we're in an input, don't do anything diff --git a/packages/app/src/hooks/useCanvasPositioning.ts b/packages/app/src/hooks/useCanvasPositioning.ts index 4ee8a7407..3e55ebe37 100644 --- a/packages/app/src/hooks/useCanvasPositioning.ts +++ b/packages/app/src/hooks/useCanvasPositioning.ts @@ -1,4 +1,4 @@ -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { canvasPositionState } from '../state/graphBuilder.js'; import { useCallback } from 'react'; @@ -17,7 +17,7 @@ export const clientToCanvasPosition = }; export function useCanvasPositioning() { - const canvasPosition = useRecoilValue(canvasPositionState); + const canvasPosition = useAtomValue(canvasPositionState); const canvasToClientPositionLocal = useCallback( (x: number, y: number) => canvasToClientPosition(canvasPosition)(x, y), diff --git a/packages/app/src/hooks/useCenterViewOnGraph.ts b/packages/app/src/hooks/useCenterViewOnGraph.ts index 82eeb5ee2..b7d23378a 100644 --- a/packages/app/src/hooks/useCenterViewOnGraph.ts +++ b/packages/app/src/hooks/useCenterViewOnGraph.ts @@ -1,11 +1,11 @@ import { type NodeGraph } from '@ironclad/rivet-core'; import { canvasPositionState, sidebarOpenState } from '../state/graphBuilder'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useSetAtom, useAtomValue } from 'jotai'; import { fitBoundsToViewport } from './useViewportBounds'; export function useCenterViewOnGraph() { - const sidebarOpen = useRecoilValue(sidebarOpenState); - const setPosition = useSetRecoilState(canvasPositionState); + const sidebarOpen = useAtomValue(sidebarOpenState); + const setPosition = useSetAtom(canvasPositionState); return (graph: NodeGraph) => { if (graph.nodes.length === 0) { diff --git a/packages/app/src/hooks/useCheckForUpdate.tsx b/packages/app/src/hooks/useCheckForUpdate.tsx index 09af7c5a3..243d508a4 100644 --- a/packages/app/src/hooks/useCheckForUpdate.tsx +++ b/packages/app/src/hooks/useCheckForUpdate.tsx @@ -1,10 +1,9 @@ import { useEffect } from 'react'; import { checkUpdate, installUpdate, onUpdaterEvent } from '@tauri-apps/api/updater'; -import useAsyncEffect from 'use-async-effect'; import { toast } from 'react-toastify'; import { css } from '@emotion/react'; import { isInTauri } from '../utils/tauri'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { checkForUpdatesState, skippedMaxVersionState, updateModalOpenState } from '../state/settings'; import { gt, lt, lte } from 'semver'; import { getVersion } from '@tauri-apps/api/app'; @@ -44,9 +43,9 @@ export function useCheckForUpdate({ notifyNoUpdates = false, force = false, }: { notifyNoUpdates?: boolean; force?: boolean } = {}) { - const setUpdateModalOpen = useSetRecoilState(updateModalOpenState); - const checkForUpdates = useRecoilValue(checkForUpdatesState); - const [skippedMaxVersion, setSkippedMaxVersion] = useRecoilState(skippedMaxVersionState); + const setUpdateModalOpen = useSetAtom(updateModalOpenState); + const checkForUpdates = useAtomValue(checkForUpdatesState); + const [skippedMaxVersion, setSkippedMaxVersion] = useAtom(skippedMaxVersionState); return async () => { if (!checkForUpdates || !isInTauri()) { diff --git a/packages/app/src/hooks/useChooseHistoricalGraph.ts b/packages/app/src/hooks/useChooseHistoricalGraph.ts index b06ca9ffb..bee132929 100644 --- a/packages/app/src/hooks/useChooseHistoricalGraph.ts +++ b/packages/app/src/hooks/useChooseHistoricalGraph.ts @@ -1,12 +1,12 @@ import { type GraphId, type NodeGraph } from '@ironclad/rivet-core'; import { type CalculatedRevision } from '../utils/ProjectRevisionCalculator'; -import { useSetRecoilState } from 'recoil'; +import { useSetAtom } from 'jotai'; import { graphState, historicalGraphState, isReadOnlyGraphState } from '../state/graph'; export function useChooseHistoricalGraph(revision: CalculatedRevision) { - const setGraph = useSetRecoilState(graphState); - const setIsReadOnlyGraph = useSetRecoilState(isReadOnlyGraphState); - const setHistoricalGraph = useSetRecoilState(historicalGraphState); + const setGraph = useSetAtom(graphState); + const setIsReadOnlyGraph = useSetAtom(isReadOnlyGraphState); + const setHistoricalGraph = useSetAtom(historicalGraphState); return (graphId: GraphId) => { const nodesBefore = revision.projectAtRevision!.graphs[graphId]?.nodes ?? []; diff --git a/packages/app/src/hooks/useContextMenuAddNodeConfiguration.ts b/packages/app/src/hooks/useContextMenuAddNodeConfiguration.ts index 72e33441b..0e58d948f 100644 --- a/packages/app/src/hooks/useContextMenuAddNodeConfiguration.ts +++ b/packages/app/src/hooks/useContextMenuAddNodeConfiguration.ts @@ -8,7 +8,7 @@ import useAsyncEffect from 'use-async-effect'; import { toast } from 'react-toastify'; import { isNotNull } from '../utils/genericUtilFunctions'; import { orderBy, uniqBy } from 'lodash-es'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { nodeConstructorsState } from '../state/graph'; export const addContextMenuGroups = [ @@ -61,7 +61,7 @@ export const addContextMenuGroups = [ }; export function useContextMenuAddNodeConfiguration() { - const constructors = useRecoilValue(nodeConstructorsState); + const constructors = useAtomValue(nodeConstructorsState); const builtInImages = useBuiltInNodeImages(); const getUIContext = useGetRivetUIContext(); diff --git a/packages/app/src/hooks/useContextMenuCommands.ts b/packages/app/src/hooks/useContextMenuCommands.ts index 16910d0fe..f048f972f 100644 --- a/packages/app/src/hooks/useContextMenuCommands.ts +++ b/packages/app/src/hooks/useContextMenuCommands.ts @@ -1,11 +1,11 @@ import { useMemo } from 'react'; import { projectGraphInfoState } from '../state/savedGraphs.js'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { type ContextMenuItem } from './useContextMenuConfiguration.js'; import { values } from '../../../core/src/utils/typeSafety'; export function useContextMenuCommands() { - const projectInfo = useRecoilValue(projectGraphInfoState); + const projectInfo = useAtomValue(projectGraphInfoState); const commands = useMemo(() => { const goToGraphCommands = values(projectInfo.graphs).map( diff --git a/packages/app/src/hooks/useContextMenuConfiguration.ts b/packages/app/src/hooks/useContextMenuConfiguration.ts index a1978e723..a6c07a96f 100644 --- a/packages/app/src/hooks/useContextMenuConfiguration.ts +++ b/packages/app/src/hooks/useContextMenuConfiguration.ts @@ -8,10 +8,10 @@ import CopyIcon from '../assets/icons/copy-icon.svg?react'; import PasteIcon from '../assets/icons/paste-icon.svg?react'; import PlusIcon from 'majesticons/line/plus-line.svg?react'; import { type NodeId } from '@ironclad/rivet-core'; -import { useRecoilValue } from 'recoil'; import { selectedNodesState } from '../state/graphBuilder.js'; import { useContextMenuCommands } from './useContextMenuCommands.js'; import { clipboardState } from '../state/clipboard'; +import { useAtomValue } from 'jotai'; export type ContextMenuConfig = { contexts: ContextMenuContextConfig; @@ -49,8 +49,8 @@ const type = () => undefined! as T; export function useContextMenuConfiguration() { const addMenuConfig = useContextMenuAddNodeConfiguration(); const commands = useContextMenuCommands(); - const selectedNodeIds = useRecoilValue(selectedNodesState); - const clipboard = useRecoilValue(clipboardState); + const selectedNodeIds = useAtomValue(selectedNodesState); + const clipboard = useAtomValue(clipboardState); const config = useMemo( () => diff --git a/packages/app/src/hooks/useCopyNodes.ts b/packages/app/src/hooks/useCopyNodes.ts index 72a8f36f6..8d99730f0 100644 --- a/packages/app/src/hooks/useCopyNodes.ts +++ b/packages/app/src/hooks/useCopyNodes.ts @@ -1,15 +1,15 @@ import { type NodeId } from '@ironclad/rivet-core'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; import { selectedNodesState } from '../state/graphBuilder'; import { connectionsState, nodesByIdState } from '../state/graph'; import { clipboardState } from '../state/clipboard'; import { isNotNull } from '../utils/genericUtilFunctions'; export function useCopyNodes() { - const selectedNodeIds = useRecoilValue(selectedNodesState); - const nodesById = useRecoilValue(nodesByIdState); - const connections = useRecoilValue(connectionsState); - const setClipboard = useSetRecoilState(clipboardState); + const selectedNodeIds = useAtomValue(selectedNodesState); + const nodesById = useAtomValue(nodesByIdState); + const connections = useAtomValue(connectionsState); + const setClipboard = useSetAtom(clipboardState); return (additionalNodeId?: NodeId) => { const nodeIds = ( diff --git a/packages/app/src/hooks/useCopyNodesHotkeys.ts b/packages/app/src/hooks/useCopyNodesHotkeys.ts index a09454646..71f4d8c4d 100644 --- a/packages/app/src/hooks/useCopyNodesHotkeys.ts +++ b/packages/app/src/hooks/useCopyNodesHotkeys.ts @@ -1,4 +1,4 @@ -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { editingNodeState, lastMousePositionState, selectedNodesState } from '../state/graphBuilder'; import { useLatest } from 'ahooks'; import { useEffect } from 'react'; @@ -7,10 +7,10 @@ import { usePasteNodes } from './usePasteNodes'; import { useDuplicateNode } from './useDuplicateNode'; export function useCopyNodesHotkeys() { - const selectedNodeIds = useRecoilValue(selectedNodesState); - const editingNodeId = useRecoilValue(editingNodeState); + const selectedNodeIds = useAtomValue(selectedNodesState); + const editingNodeId = useAtomValue(editingNodeState); - const mousePosition = useRecoilValue(lastMousePositionState); + const mousePosition = useAtomValue(lastMousePositionState); const copyNodes = useCopyNodes(); const pasteNodes = usePasteNodes(); diff --git a/packages/app/src/hooks/useCurrentExecution.ts b/packages/app/src/hooks/useCurrentExecution.ts index 11fd5faf6..f6118d04e 100644 --- a/packages/app/src/hooks/useCurrentExecution.ts +++ b/packages/app/src/hooks/useCurrentExecution.ts @@ -12,7 +12,7 @@ import { } from '@ironclad/rivet-core'; import { produce } from 'immer'; import { cloneDeep, mapValues } from 'lodash-es'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; import { type NodeRunData, graphPausedState, @@ -130,18 +130,18 @@ function convertToRef(value: DataValue): DataValueWithRefs { } export function useCurrentExecution() { - const setLastRunData = useSetRecoilState(lastRunDataByNodeState); - const setSelectedPage = useSetRecoilState(selectedProcessPageNodesState); - const setUserInputQuestions = useSetRecoilState(userInputModalQuestionsState); - const setGraphRunning = useSetRecoilState(graphRunningState); - const setGraphPaused = useSetRecoilState(graphPausedState); - const setRunningGraphsState = useSetRecoilState(runningGraphsState); - const setLastRecordingState = useSetRecoilState(lastRecordingState); - const trivetRunning = useRecoilValue(trivetTestsRunningState); + const setLastRunData = useSetAtom(lastRunDataByNodeState); + const setSelectedPage = useSetAtom(selectedProcessPageNodesState); + const setUserInputQuestions = useSetAtom(userInputModalQuestionsState); + const setGraphRunning = useSetAtom(graphRunningState); + const setGraphPaused = useSetAtom(graphPausedState); + const setRunningGraphsState = useSetAtom(runningGraphsState); + const setLastRecordingState = useSetAtom(lastRecordingState); + const trivetRunning = useAtomValue(trivetTestsRunningState); const trivetRunningLatest = useLatest(trivetRunning); - const setRootGraph = useSetRecoilState(rootGraphState); - const previousDataPerNodeToKeep = useRecoilValue(previousDataPerNodeToKeepState); - const setGraphStartTime = useSetRecoilState(graphStartTimeState); + const setRootGraph = useSetAtom(rootGraphState); + const previousDataPerNodeToKeep = useAtomValue(previousDataPerNodeToKeepState); + const setGraphStartTime = useSetAtom(graphStartTimeState); const setDataForNode = (nodeId: NodeId, processId: ProcessId, data: Partial) => { setLastRunData((prev) => diff --git a/packages/app/src/hooks/useDatasets.ts b/packages/app/src/hooks/useDatasets.ts index d6b6ea750..a8a28a99b 100644 --- a/packages/app/src/hooks/useDatasets.ts +++ b/packages/app/src/hooks/useDatasets.ts @@ -3,11 +3,11 @@ import { useEffect, useState } from 'react'; import { datasetProvider } from '../utils/globals'; import { toast } from 'react-toastify'; import { datasetsState } from '../state/dataStudio'; -import { useRecoilState } from 'recoil'; +import { useAtom } from 'jotai'; import { useStableCallback } from './useStableCallback'; export function useDatasets(projectId: ProjectId) { - const [datasets, updateDatasets] = useRecoilState(datasetsState); + const [datasets, setDatasets] = useAtom(datasetsState); const initDatasets = useStableCallback(async () => { try { @@ -21,7 +21,7 @@ export function useDatasets(projectId: ProjectId) { const reloadDatasets = async () => { try { const datasets = await datasetProvider.getDatasetsForProject(projectId); - updateDatasets(datasets); + setDatasets(datasets); } catch (err) { toast.error(getError(err).message); } diff --git a/packages/app/src/hooks/useDeleteGraph.ts b/packages/app/src/hooks/useDeleteGraph.ts index 3115c9a45..68a6d90a1 100644 --- a/packages/app/src/hooks/useDeleteGraph.ts +++ b/packages/app/src/hooks/useDeleteGraph.ts @@ -1,20 +1,17 @@ -import { useSetRecoilState } from 'recoil'; +import { useSetAtom } from 'jotai'; import { type NodeGraph, emptyNodeGraph } from '@ironclad/rivet-core'; import { graphState } from '../state/graph.js'; import { savedGraphsState } from '../state/savedGraphs.js'; import { useCallback } from 'react'; export function useDeleteGraph() { - const setGraph = useSetRecoilState(graphState); - const setSavedGraphs = useSetRecoilState(savedGraphsState); + const setGraph = useSetAtom(graphState); + const setSavedGraphs = useSetAtom(savedGraphsState); return useCallback( (savedGraph: NodeGraph) => { if (savedGraph.metadata?.id) { - setSavedGraphs((savedGraphs) => { - const newSavedGraphs = savedGraphs.filter((g) => g.metadata?.id !== savedGraph.metadata?.id); - return newSavedGraphs; - }); + setSavedGraphs((prev) => prev.filter((g) => g.metadata?.id !== savedGraph.metadata?.id)); setGraph(emptyNodeGraph()); } }, diff --git a/packages/app/src/hooks/useDependsOnPlugins.ts b/packages/app/src/hooks/useDependsOnPlugins.ts index 7e159962d..7a49ac0c6 100644 --- a/packages/app/src/hooks/useDependsOnPlugins.ts +++ b/packages/app/src/hooks/useDependsOnPlugins.ts @@ -1,9 +1,9 @@ -import { useRecoilValue } from 'recoil'; import { pluginRefreshCounterState } from '../state/plugins'; import { globalRivetNodeRegistry } from '@ironclad/rivet-core'; +import { useAtomValue } from 'jotai'; export function useDependsOnPlugins() { - useRecoilValue(pluginRefreshCounterState); + useAtomValue(pluginRefreshCounterState); return globalRivetNodeRegistry.getPlugins(); } diff --git a/packages/app/src/hooks/useDraggingNode.ts b/packages/app/src/hooks/useDraggingNode.ts index 75b06483a..883fd78bb 100644 --- a/packages/app/src/hooks/useDraggingNode.ts +++ b/packages/app/src/hooks/useDraggingNode.ts @@ -2,17 +2,17 @@ import { type DragStartEvent, type DragEndEvent } from '@dnd-kit/core'; import { produce } from 'immer'; import { useCallback } from 'react'; import { type ChartNode, type NodeId } from '@ironclad/rivet-core'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { canvasPositionState, draggingNodesState, selectedNodesState } from '../state/graphBuilder.js'; import { isNotNull } from '../utils/genericUtilFunctions.js'; import { nodesByIdState, nodesState } from '../state/graph.js'; export const useDraggingNode = (onNodesChanged: (nodes: ChartNode[]) => void) => { - const selectedNodeIds = useRecoilValue(selectedNodesState); - const [draggingNodes, setDraggingNodes] = useRecoilState(draggingNodesState); - const canvasPosition = useRecoilValue(canvasPositionState); - const nodes = useRecoilValue(nodesState); - const nodesById = useRecoilValue(nodesByIdState); + const selectedNodeIds = useAtomValue(selectedNodesState); + const [draggingNodes, setDraggingNodes] = useAtom(draggingNodesState); + const canvasPosition = useAtomValue(canvasPositionState); + const nodes = useAtomValue(nodesState); + const nodesById = useAtomValue(nodesByIdState); const onNodeStartDrag = useCallback( (e: DragStartEvent) => { diff --git a/packages/app/src/hooks/useDraggingWire.ts b/packages/app/src/hooks/useDraggingWire.ts index 463a52801..55c397a42 100644 --- a/packages/app/src/hooks/useDraggingWire.ts +++ b/packages/app/src/hooks/useDraggingWire.ts @@ -1,16 +1,16 @@ import { useCallback, useEffect } from 'react'; import { type NodeConnection, type NodeId, type PortId } from '@ironclad/rivet-core'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { connectionsState, ioDefinitionsState, nodesByIdState } from '../state/graph.js'; import { draggingWireClosestPortState, draggingWireState } from '../state/graphBuilder.js'; import { useLatest } from 'ahooks'; export const useDraggingWire = (onConnectionsChanged: (connections: NodeConnection[]) => void) => { - const [draggingWire, setDraggingWire] = useRecoilState(draggingWireState); - const ioByNode = useRecoilValue(ioDefinitionsState); - const connections = useRecoilValue(connectionsState); - const nodesById = useRecoilValue(nodesByIdState); - const [closestPortToDraggingWire, setClosestPortToDraggingWire] = useRecoilState(draggingWireClosestPortState); + const [draggingWire, setDraggingWire] = useAtom(draggingWireState); + const ioByNode = useAtomValue(ioDefinitionsState); + const connections = useAtomValue(connectionsState); + const nodesById = useAtomValue(nodesByIdState); + const [closestPortToDraggingWire, setClosestPortToDraggingWire] = useAtom(draggingWireClosestPortState); const isDragging = !!draggingWire; const latestClosestPort = useLatest(closestPortToDraggingWire); diff --git a/packages/app/src/hooks/useDuplicateGraph.ts b/packages/app/src/hooks/useDuplicateGraph.ts index 9a16c4986..0accdef74 100644 --- a/packages/app/src/hooks/useDuplicateGraph.ts +++ b/packages/app/src/hooks/useDuplicateGraph.ts @@ -1,19 +1,20 @@ import { type NodeGraph } from '@ironclad/rivet-core'; import { useLoadGraph } from './useLoadGraph.js'; import { useStableCallback } from './useStableCallback.js'; -import { useSetRecoilState } from 'recoil'; +import { useSetAtom, useAtomValue } from 'jotai'; import { savedGraphsState } from '../state/savedGraphs.js'; import { duplicateGraph } from '../utils/duplicateGraph'; export function useDuplicateGraph() { const loadGraph = useLoadGraph(); - const setSavedGraphs = useSetRecoilState(savedGraphsState); + const setSavedGraphs = useSetAtom(savedGraphsState); + const savedGraphs = useAtomValue(savedGraphsState); return useStableCallback((savedGraph: NodeGraph) => { const duplicatedGraph = duplicateGraph(savedGraph); loadGraph(duplicatedGraph); - setSavedGraphs((savedGraphs) => [...savedGraphs, duplicatedGraph]); + setSavedGraphs([...savedGraphs, duplicatedGraph]); }); } diff --git a/packages/app/src/hooks/useDuplicateNode.ts b/packages/app/src/hooks/useDuplicateNode.ts index 3da993234..044bdcfb1 100644 --- a/packages/app/src/hooks/useDuplicateNode.ts +++ b/packages/app/src/hooks/useDuplicateNode.ts @@ -1,11 +1,11 @@ import { globalRivetNodeRegistry, type NodeId } from '@ironclad/rivet-core'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { connectionsState, nodesByIdState, nodesState } from '../state/graph'; export function useDuplicateNode() { - const nodesById = useRecoilValue(nodesByIdState); - const setNodes = useSetRecoilState(nodesState); - const [connections, setConnections] = useRecoilState(connectionsState); + const nodesById = useAtomValue(nodesByIdState); + const setNodes = useSetAtom(nodesState); + const setConnections = useSetAtom(connectionsState); return (nodeId: NodeId) => { const node = nodesById[nodeId]; @@ -25,14 +25,16 @@ export function useDuplicateNode() { newNode.description = node.description; newNode.isSplitRun = node.isSplitRun; newNode.splitRunMax = node.splitRunMax; - setNodes((nodes) => [...nodes, newNode]); + setNodes((prev) => [...prev, newNode]); - // Copy the connections to the input ports - const oldNodeConnections = connections.filter((c) => c.inputNodeId === nodeId); - const newNodeConnections = oldNodeConnections.map((c) => ({ - ...c, - inputNodeId: newNode.id, - })); - setConnections([...connections, ...newNodeConnections]); + setConnections((prev) => { + const oldNodeConnections = prev.filter((c) => c.inputNodeId === nodeId); + const newNodeConnections = oldNodeConnections.map((c) => ({ + ...c, + inputNodeId: newNode.id, + })); + console.log('newNodeConnections', newNodeConnections); + return [...prev, ...newNodeConnections]; + }); }; } diff --git a/packages/app/src/hooks/useFactorIntoSubgraph.ts b/packages/app/src/hooks/useFactorIntoSubgraph.ts index 4cb936a46..13602aed5 100644 --- a/packages/app/src/hooks/useFactorIntoSubgraph.ts +++ b/packages/app/src/hooks/useFactorIntoSubgraph.ts @@ -1,4 +1,4 @@ -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { useStableCallback } from './useStableCallback.js'; import { selectedNodesState } from '../state/graphBuilder.js'; import { connectionsState, nodesByIdState } from '../state/graph.js'; @@ -20,11 +20,11 @@ import { nanoid } from 'nanoid/non-secure'; import { useLoadGraph } from './useLoadGraph.js'; export function useFactorIntoSubgraph() { - const project = useRecoilValue(projectState); - const selectedNodeIds = useRecoilValue(selectedNodesState); - const connections = useRecoilValue(connectionsState); + const project = useAtomValue(projectState); + const selectedNodeIds = useAtomValue(selectedNodesState); + const connections = useAtomValue(connectionsState); const loadGraph = useLoadGraph(); - const nodesById = useRecoilValue(nodesByIdState); + const nodesById = useAtomValue(nodesByIdState); return useStableCallback(() => { if (selectedNodeIds.length === 0) { diff --git a/packages/app/src/hooks/useFocusOnNodes.ts b/packages/app/src/hooks/useFocusOnNodes.ts index 4caedf711..abfea3835 100644 --- a/packages/app/src/hooks/useFocusOnNodes.ts +++ b/packages/app/src/hooks/useFocusOnNodes.ts @@ -1,13 +1,13 @@ import { type NodeId } from '@ironclad/rivet-core'; import { useStableCallback } from './useStableCallback'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { canvasPositionState } from '../state/graphBuilder'; import { graphState } from '../state/graph'; import { fitBoundsToViewport } from './useViewportBounds'; export function useFocusOnNodes() { - const setPosition = useSetRecoilState(canvasPositionState); - const graph = useRecoilValue(graphState); + const setPosition = useSetAtom(canvasPositionState); + const graph = useAtomValue(graphState); return useStableCallback((nodeIds: NodeId[]) => { const node = graph.nodes.filter((n) => nodeIds.includes(n.id))!; diff --git a/packages/app/src/hooks/useGetAdHocInternalProcessContext.ts b/packages/app/src/hooks/useGetAdHocInternalProcessContext.ts index 3ce7a0ad9..fcd879942 100644 --- a/packages/app/src/hooks/useGetAdHocInternalProcessContext.ts +++ b/packages/app/src/hooks/useGetAdHocInternalProcessContext.ts @@ -10,13 +10,13 @@ import { GptTokenizerTokenizer } from '../../../core/src/integrations/GptTokeniz import { fillMissingSettingsFromEnvironmentVariables } from '../utils/tauri'; import { TauriNativeApi } from '../model/native/TauriNativeApi'; import { nanoid } from 'nanoid/non-secure'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { settingsState } from '../state/settings'; import { useDependsOnPlugins } from './useDependsOnPlugins'; import { audioProvider, datasetProvider } from '../utils/globals'; export function useGetAdHocInternalProcessContext() { - const settings = useRecoilValue(settingsState); + const settings = useAtomValue(settingsState); const plugins = useDependsOnPlugins(); return useCallback( diff --git a/packages/app/src/hooks/useGetConnectionsForNode.ts b/packages/app/src/hooks/useGetConnectionsForNode.ts index 653d1801f..e6d43a03e 100644 --- a/packages/app/src/hooks/useGetConnectionsForNode.ts +++ b/packages/app/src/hooks/useGetConnectionsForNode.ts @@ -1,10 +1,10 @@ -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { connectionsState } from '../state/graph.js'; import { type ChartNode } from '@ironclad/rivet-core'; import { useCallback } from 'react'; export function useGetConnectionsForNode() { - const connections = useRecoilValue(connectionsState); + const connections = useAtomValue(connectionsState); return useCallback( (node: ChartNode) => { diff --git a/packages/app/src/hooks/useGetNodeIO.ts b/packages/app/src/hooks/useGetNodeIO.ts index df05dc65d..045a1e34b 100644 --- a/packages/app/src/hooks/useGetNodeIO.ts +++ b/packages/app/src/hooks/useGetNodeIO.ts @@ -1,7 +1,7 @@ -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { ioDefinitionsForNodeState } from '../state/graph.js'; import { type NodeId } from '@ironclad/rivet-core'; export function useNodeIO(nodeId: NodeId | undefined) { - return useRecoilValue(ioDefinitionsForNodeState(nodeId)); + return useAtomValue(ioDefinitionsForNodeState(nodeId)); } diff --git a/packages/app/src/hooks/useGetRivetUIContext.ts b/packages/app/src/hooks/useGetRivetUIContext.ts index 317f1a2cd..0d882f2bd 100644 --- a/packages/app/src/hooks/useGetRivetUIContext.ts +++ b/packages/app/src/hooks/useGetRivetUIContext.ts @@ -1,6 +1,5 @@ import { type ChartNode, getPluginConfig, globalRivetNodeRegistry } from '@ironclad/rivet-core'; import { datasetProvider } from '../utils/globals'; -import { useRecoilValue } from 'recoil'; import { selectedExecutorState } from '../state/execution'; import { type RivetUIContext } from '../../../core/src/model/RivetUIContext'; import { settingsState } from '../state/settings'; @@ -9,13 +8,14 @@ import { useDependsOnPlugins } from './useDependsOnPlugins'; import { projectState } from '../state/savedGraphs'; import { graphState } from '../state/graph'; import { useStableCallback } from './useStableCallback'; +import { useAtomValue } from 'jotai'; export function useGetRivetUIContext() { - const selectedExecutor = useRecoilValue(selectedExecutorState); - const settings = useRecoilValue(settingsState); + const selectedExecutor = useAtomValue(selectedExecutorState); + const settings = useAtomValue(settingsState); const plugins = useDependsOnPlugins(); - const project = useRecoilValue(projectState); - const graph = useRecoilValue(graphState); + const project = useAtomValue(projectState); + const graph = useAtomValue(graphState); return useStableCallback(async ({ node }: { node?: ChartNode }) => { let getPluginConfigFn: RivetUIContext['getPluginConfig'] = () => undefined; diff --git a/packages/app/src/hooks/useGoToNode.ts b/packages/app/src/hooks/useGoToNode.ts index 0a59b104c..16a8c1583 100644 --- a/packages/app/src/hooks/useGoToNode.ts +++ b/packages/app/src/hooks/useGoToNode.ts @@ -1,14 +1,14 @@ import { type NodeId } from '@ironclad/rivet-core'; import { useStableCallback } from './useStableCallback'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useLoadGraph } from './useLoadGraph'; import { projectState } from '../state/savedGraphs'; import { canvasPositionState } from '../state/graphBuilder'; +import { useAtomValue, useSetAtom } from 'jotai'; export function useGoToNode() { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const loadGraph = useLoadGraph(); - const setPosition = useSetRecoilState(canvasPositionState); + const setPosition = useSetAtom(canvasPositionState); return useStableCallback((nodeId: NodeId) => { const graphForNode = Object.values(project.graphs).find((graph) => graph.nodes.some((n) => n.id === nodeId)); diff --git a/packages/app/src/hooks/useGraphBuilderContextMenuHandler.ts b/packages/app/src/hooks/useGraphBuilderContextMenuHandler.ts index 37a9746e3..db5637260 100644 --- a/packages/app/src/hooks/useGraphBuilderContextMenuHandler.ts +++ b/packages/app/src/hooks/useGraphBuilderContextMenuHandler.ts @@ -7,7 +7,6 @@ import { type GraphId, type ChartNode, } from '@ironclad/rivet-core'; -import { useRecoilValue, useRecoilState, useSetRecoilState } from 'recoil'; import { type ContextMenuContext } from '../components/ContextMenu'; import { editingNodeState, selectedNodesState } from '../state/graphBuilder'; import { projectState } from '../state/savedGraphs'; @@ -20,20 +19,21 @@ import { nodesByIdState, nodesState } from '../state/graph'; import { useCopyNodes } from './useCopyNodes'; import { useDuplicateNode } from './useDuplicateNode'; import { useRemoveNodes } from './useRemoveNodes'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; export function useGraphBuilderContextMenuHandler() { - const [nodes, setNodes] = useRecoilState(nodesState); + const [nodes, setNodes] = useAtom(nodesState); const { clientToCanvasPosition } = useCanvasPositioning(); const loadGraph = useLoadGraph(); - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const { tryRunGraph } = useGraphExecutor(); const pasteNodes = usePasteNodes(); const copyNodes = useCopyNodes(); const duplicateNode = useDuplicateNode(); const factorIntoSubgraph = useFactorIntoSubgraph(); - const setEditingNodeId = useSetRecoilState(editingNodeState); - const [selectedNodeIds, setSelectedNodeIds] = useRecoilState(selectedNodesState); - const nodesById = useRecoilValue(nodesByIdState); + const setEditingNodeId = useSetAtom(editingNodeState); + const [selectedNodeIds, setSelectedNodeIds] = useAtom(selectedNodesState); + const nodesById = useAtomValue(nodesByIdState); const removeNodes = useRemoveNodes(); const nodesChanged = useStableCallback((newNodes: ChartNode[]) => { diff --git a/packages/app/src/hooks/useGraphExecutor.ts b/packages/app/src/hooks/useGraphExecutor.ts index 850f97e40..85c5adc31 100644 --- a/packages/app/src/hooks/useGraphExecutor.ts +++ b/packages/app/src/hooks/useGraphExecutor.ts @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { selectedExecutorState } from '../state/execution'; import { useExecutorSidecar } from './useExecutorSidecar'; import { useLocalExecutor } from './useLocalExecutor'; @@ -13,7 +13,7 @@ import { useRemoteExecutor } from './useRemoteExecutor'; * @returns */ export function useGraphExecutor() { - const selectedExecutor = useRecoilValue(selectedExecutorState); + const selectedExecutor = useAtomValue(selectedExecutorState); const localExecutor = useLocalExecutor(); const remoteExecutor = useRemoteExecutor(); diff --git a/packages/app/src/hooks/useGraphHistoryNavigation.ts b/packages/app/src/hooks/useGraphHistoryNavigation.ts index 3b819ed38..f1952e02b 100644 --- a/packages/app/src/hooks/useGraphHistoryNavigation.ts +++ b/packages/app/src/hooks/useGraphHistoryNavigation.ts @@ -1,13 +1,13 @@ import { useCallback } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; import { graphNavigationStackState } from '../state/graphBuilder.js'; import { projectState } from '../state/savedGraphs.js'; import { useLoadGraph } from '../hooks/useLoadGraph.js'; +import { useAtom, useAtomValue } from 'jotai'; export const useGraphHistoryNavigation = () => { - const [graphNavigationStack, setGraphNavigationStack] = useRecoilState(graphNavigationStackState); + const [graphNavigationStack, setGraphNavigationStack] = useAtom(graphNavigationStackState); const loadGraph = useLoadGraph(); - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const hasForward = graphNavigationStack.index != null && graphNavigationStack.index < graphNavigationStack.stack.length - 1; diff --git a/packages/app/src/hooks/useGraphRevisions.ts b/packages/app/src/hooks/useGraphRevisions.ts index ba221d9a2..f32838c34 100644 --- a/packages/app/src/hooks/useGraphRevisions.ts +++ b/packages/app/src/hooks/useGraphRevisions.ts @@ -1,4 +1,4 @@ -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { loadedProjectState } from '../state/savedGraphs'; import { Command } from '@tauri-apps/api/shell'; import useAsyncEffect from 'use-async-effect'; @@ -13,7 +13,7 @@ const revisionCalculators = new Map(); const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); export function useProjectRevisions(options?: { max?: number }) { - const projectState = useRecoilValue(loadedProjectState); + const projectState = useAtomValue(loadedProjectState); const [isLoading, setIsLoading] = useState(true); const [revisions, setRevisions] = useState([]); @@ -76,7 +76,7 @@ export function useProjectRevisions(options?: { max?: number }) { export function useGraphRevisions(options?: { max?: number }) { const { revisions, isLoading, stop, numTotalRevisions, numProcessedRevisions, resume } = useProjectRevisions(options); - const graph = useRecoilValue(graphState); + const graph = useAtomValue(graphState); if (!graph) { return { @@ -100,7 +100,7 @@ export function useGraphRevisions(options?: { max?: number }) { } export function useHasGitHistory() { - const projectPath = useRecoilValue(loadedProjectState).path; + const projectPath = useAtomValue(loadedProjectState).path; const [hasHistory, setHasHistory] = useState(false); diff --git a/packages/app/src/hooks/useHistoricalNodeChangeInfo.ts b/packages/app/src/hooks/useHistoricalNodeChangeInfo.ts index e40abbba0..4b1dd4503 100644 --- a/packages/app/src/hooks/useHistoricalNodeChangeInfo.ts +++ b/packages/app/src/hooks/useHistoricalNodeChangeInfo.ts @@ -1,5 +1,5 @@ import { type ChartNode, type NodeId } from '@ironclad/rivet-core'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { graphState, historicalGraphState } from '../state/graph'; import isEqual from 'fast-deep-equal'; @@ -14,8 +14,8 @@ export type HistoricalNodeChangeInfo = }; export function useHistoricalNodeChangeInfo(nodeId: NodeId): HistoricalNodeChangeInfo | undefined { - const historicalGraph = useRecoilValue(historicalGraphState); - const graph = useRecoilValue(graphState); + const historicalGraph = useAtomValue(historicalGraphState); + const graph = useAtomValue(graphState); if (historicalGraph == null) { return undefined; diff --git a/packages/app/src/hooks/useImportGraph.ts b/packages/app/src/hooks/useImportGraph.ts index b49df6da1..7addba7f9 100644 --- a/packages/app/src/hooks/useImportGraph.ts +++ b/packages/app/src/hooks/useImportGraph.ts @@ -1,4 +1,4 @@ -import { useSetRecoilState } from 'recoil'; +import { useSetAtom } from 'jotai'; import { ioProvider } from '../utils/globals'; import { graphState } from '../state/graph'; import { duplicateGraph } from '../utils/duplicateGraph'; @@ -6,8 +6,8 @@ import { savedGraphsState } from '../state/savedGraphs'; import { useCenterViewOnGraph } from './useCenterViewOnGraph'; export function useImportGraph() { - const setGraphData = useSetRecoilState(graphState); - const setSavedGraphs = useSetRecoilState(savedGraphsState); + const setGraphData = useSetAtom(graphState); + const setSavedGraphs = useSetAtom(savedGraphsState); const centerViewOnGraph = useCenterViewOnGraph(); return () => { @@ -15,7 +15,7 @@ export function useImportGraph() { // Duplicate so that we get a fresh set of IDs for the imported graph const duplicated = duplicateGraph(data); setGraphData(duplicated); - setSavedGraphs((savedGraphs) => [...savedGraphs, duplicated]); + setSavedGraphs((prev) => [...prev, duplicated]); centerViewOnGraph(duplicated); }); }; diff --git a/packages/app/src/hooks/useInitializeGraphNavigationStack.ts b/packages/app/src/hooks/useInitializeGraphNavigationStack.ts index 88afb6de8..a7de6b65e 100644 --- a/packages/app/src/hooks/useInitializeGraphNavigationStack.ts +++ b/packages/app/src/hooks/useInitializeGraphNavigationStack.ts @@ -1,13 +1,13 @@ -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { savedGraphsState } from '../state/savedGraphs'; import { graphState } from '../state/graph'; import { graphNavigationStackState } from '../state/graphBuilder'; import { useEffect } from 'react'; export function useInitializeGraphNavigationStack() { - const savedGraphs = useRecoilValue(savedGraphsState); - const graph = useRecoilValue(graphState); - const [graphNavigationStack, setGraphNavigationStack] = useRecoilState(graphNavigationStackState); + const savedGraphs = useAtomValue(savedGraphsState); + const graph = useAtomValue(graphState); + const [graphNavigationStack, setGraphNavigationStack] = useAtom(graphNavigationStackState); useEffect(() => { if ( diff --git a/packages/app/src/hooks/useIsLoggedInToCommunity.ts b/packages/app/src/hooks/useIsLoggedInToCommunity.ts index 72a31c5e9..c81def9dd 100644 --- a/packages/app/src/hooks/useIsLoggedInToCommunity.ts +++ b/packages/app/src/hooks/useIsLoggedInToCommunity.ts @@ -1,12 +1,12 @@ import { useQuery } from '@tanstack/react-query'; -import { useRecoilState } from 'recoil'; +import { useAtom } from 'jotai'; import { isLoggedInToCommunityState } from '../state/community'; import { getCommunityApi } from '../utils/getCommunityApi'; import { useEffect } from 'react'; export function useIsLoggedInToCommunity() { const profileUrl = getCommunityApi('/profile'); - const [isLoggedInToCommunity, setIsLoggedIntoCommunity] = useRecoilState(isLoggedInToCommunityState); + const [isLoggedInToCommunity, setIsLoggedIntoCommunity] = useAtom(isLoggedInToCommunityState); const { refetch } = useQuery({ queryKey: ['community-profile'], diff --git a/packages/app/src/hooks/useLoadGraph.ts b/packages/app/src/hooks/useLoadGraph.ts index 7a79a82ed..e2332f049 100644 --- a/packages/app/src/hooks/useLoadGraph.ts +++ b/packages/app/src/hooks/useLoadGraph.ts @@ -1,4 +1,4 @@ -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { type NodeGraph, emptyNodeGraph } from '@ironclad/rivet-core'; import { graphState, historicalGraphState, isReadOnlyGraphState } from '../state/graph.js'; import { useSaveCurrentGraph } from './useSaveCurrentGraph.js'; @@ -13,16 +13,16 @@ import { useStableCallback } from './useStableCallback.js'; import { useCenterViewOnGraph } from './useCenterViewOnGraph'; export function useLoadGraph() { - const [graph, setGraph] = useRecoilState(graphState); + const [graph, setGraph] = useAtom(graphState); - const setPosition = useSetRecoilState(canvasPositionState); + const setPosition = useSetAtom(canvasPositionState); const saveCurrentGraph = useSaveCurrentGraph(); - const lastSavedPositions = useRecoilValue(lastCanvasPositionByGraphState); - const setGraphNavigationStack = useSetRecoilState(graphNavigationStackState); - const setSelectedNodes = useSetRecoilState(selectedNodesState); + const lastSavedPositions = useAtomValue(lastCanvasPositionByGraphState); + const setGraphNavigationStack = useSetAtom(graphNavigationStackState); + const setSelectedNodes = useSetAtom(selectedNodesState); const centerViewOnGraph = useCenterViewOnGraph(); - const setHistoricalGraph = useSetRecoilState(historicalGraphState); - const setIsReadOnlyGraph = useSetRecoilState(isReadOnlyGraphState); + const setHistoricalGraph = useSetAtom(historicalGraphState); + const setIsReadOnlyGraph = useSetAtom(isReadOnlyGraphState); return useStableCallback((savedGraph: NodeGraph, { pushHistory = true }: { pushHistory?: boolean } = {}) => { if (graph.nodes.length > 0 || graph.metadata?.name !== emptyNodeGraph().metadata!.name) { diff --git a/packages/app/src/hooks/useLoadProject.ts b/packages/app/src/hooks/useLoadProject.ts index 1e8de3d7f..e4456f9b3 100644 --- a/packages/app/src/hooks/useLoadProject.ts +++ b/packages/app/src/hooks/useLoadProject.ts @@ -1,4 +1,3 @@ -import { useSetRecoilState } from 'recoil'; import { type OpenedProjectInfo, loadedProjectState, projectState } from '../state/savedGraphs.js'; import { emptyNodeGraph, getError } from '@ironclad/rivet-core'; import { graphState, historicalGraphState, isReadOnlyGraphState } from '../state/graph.js'; @@ -7,16 +6,17 @@ import { trivetState } from '../state/trivet.js'; import { useSetStaticData } from './useSetStaticData'; import { toast } from 'react-toastify'; import { graphNavigationStackState } from '../state/graphBuilder'; +import { useSetAtom } from 'jotai'; export function useLoadProject() { - const setProject = useSetRecoilState(projectState); - const setLoadedProjectState = useSetRecoilState(loadedProjectState); - const setGraphData = useSetRecoilState(graphState); - const setTrivetState = useSetRecoilState(trivetState); + const setProject = useSetAtom(projectState); + const setLoadedProjectState = useSetAtom(loadedProjectState); + const setGraphData = useSetAtom(graphState); + const setTrivetState = useSetAtom(trivetState); const setStaticData = useSetStaticData(); - const setNavigationStack = useSetRecoilState(graphNavigationStackState); - const setIsReadOnlyGraph = useSetRecoilState(isReadOnlyGraphState); - const setHistoricalGraph = useSetRecoilState(historicalGraphState); + const setNavigationStack = useSetAtom(graphNavigationStackState); + const setIsReadOnlyGraph = useSetAtom(isReadOnlyGraphState); + const setHistoricalGraph = useSetAtom(historicalGraphState); return async (projectInfo: OpenedProjectInfo) => { try { diff --git a/packages/app/src/hooks/useLoadProjectWithFileBrowser.ts b/packages/app/src/hooks/useLoadProjectWithFileBrowser.ts index fc8dc80e8..799f2375c 100644 --- a/packages/app/src/hooks/useLoadProjectWithFileBrowser.ts +++ b/packages/app/src/hooks/useLoadProjectWithFileBrowser.ts @@ -1,5 +1,5 @@ -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; -import { loadedProjectState, openedProjectsState, projectState, projectsState } from '../state/savedGraphs.js'; +import { useAtom, useSetAtom } from 'jotai'; +import { loadedProjectState, projectState, projectsState } from '../state/savedGraphs.js'; import { type NodeGraph, emptyNodeGraph, getError } from '@ironclad/rivet-core'; import { graphState } from '../state/graph.js'; import { ioProvider } from '../utils/globals.js'; @@ -10,13 +10,13 @@ import { graphNavigationStackState } from '../state/graphBuilder'; import { useCenterViewOnGraph } from './useCenterViewOnGraph'; export function useLoadProjectWithFileBrowser() { - const setProject = useSetRecoilState(projectState); - const setLoadedProjectState = useSetRecoilState(loadedProjectState); - const setGraph = useSetRecoilState(graphState); - const setTrivetState = useSetRecoilState(trivetState); + const setProject = useSetAtom(projectState); + const setLoadedProjectState = useSetAtom(loadedProjectState); + const [projects, setProjects] = useAtom(projectsState); + const setGraph = useSetAtom(graphState); + const setTrivetState = useSetAtom(trivetState); const setStaticData = useSetStaticData(); - const setNavigationStack = useSetRecoilState(graphNavigationStackState); - const [projects, setProjects] = useRecoilState(projectsState); + const setNavigationStack = useSetAtom(graphNavigationStackState); const centerViewOnGraph = useCenterViewOnGraph(); return async () => { @@ -77,15 +77,15 @@ export function useLoadProjectWithFileBrowser() { runningTests: false, }); - setProjects((projects) => ({ + setProjects((prev) => ({ openedProjects: { - ...projects.openedProjects, + ...prev.openedProjects, [project.metadata.id]: { project: projectData, fsPath: path, }, }, - openedProjectsSortedIds: [...projects.openedProjectsSortedIds, project.metadata.id], + openedProjectsSortedIds: [...prev.openedProjectsSortedIds, project.metadata.id], })); }); } catch (err) { diff --git a/packages/app/src/hooks/useLoadRecording.ts b/packages/app/src/hooks/useLoadRecording.ts index 2ba9bcb38..01212bf58 100644 --- a/packages/app/src/hooks/useLoadRecording.ts +++ b/packages/app/src/hooks/useLoadRecording.ts @@ -1,9 +1,9 @@ -import { useSetRecoilState } from 'recoil'; +import { useSetAtom } from 'jotai'; import { loadedRecordingState } from '../state/execution.js'; import { ioProvider } from '../utils/globals.js'; export function useLoadRecording() { - const setLoadedRecording = useSetRecoilState(loadedRecordingState); + const setLoadedRecording = useSetAtom(loadedRecordingState); return { loadRecording: () => { diff --git a/packages/app/src/hooks/useLoadStaticData.ts b/packages/app/src/hooks/useLoadStaticData.ts index eafc946ba..7a2621bc8 100644 --- a/packages/app/src/hooks/useLoadStaticData.ts +++ b/packages/app/src/hooks/useLoadStaticData.ts @@ -1,12 +1,12 @@ -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useSetAtom } from 'jotai'; import { loadedProjectState, projectDataState } from '../state/savedGraphs'; import { useEffect } from 'react'; import { useStaticDataDatabase } from './useStaticDataDatabase'; import { type DataId } from '@ironclad/rivet-core'; export function useLoadStaticData() { - const [data, setData] = useRecoilState(projectDataState); - const setProjectData = useSetRecoilState(projectDataState); + const [data, setData] = useAtom(projectDataState); + const setProjectData = useSetAtom(projectDataState); const database = useStaticDataDatabase(); diff --git a/packages/app/src/hooks/useLocalExecutor.ts b/packages/app/src/hooks/useLocalExecutor.ts index 9cb94bf37..acaad57af 100644 --- a/packages/app/src/hooks/useLocalExecutor.ts +++ b/packages/app/src/hooks/useLocalExecutor.ts @@ -17,7 +17,6 @@ import { TauriNativeApi } from '../model/native/TauriNativeApi'; import { useStableCallback } from './useStableCallback'; import { useSaveCurrentGraph } from './useSaveCurrentGraph'; import { useCurrentExecution } from './useCurrentExecution'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { userInputModalQuestionsState, userInputModalSubmitState } from '../state/userInput'; import { projectContextState, projectDataState, projectState } from '../state/savedGraphs'; import { recordExecutionsState, settingsState } from '../state/settings'; @@ -28,24 +27,25 @@ import { trivetState } from '../state/trivet'; import { runTrivet } from '@ironclad/trivet'; import { audioProvider, datasetProvider } from '../utils/globals'; import { entries } from '../../../core/src/utils/typeSafety'; -import { type RunDataByNodeId, lastRunData, lastRunDataByNodeState } from '../state/dataFlow'; +import { type RunDataByNodeId, lastRunDataByNodeState } from '../state/dataFlow'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; export function useLocalExecutor() { - const project = useRecoilValue(projectState); - const graph = useRecoilValue(graphState); + const project = useAtomValue(projectState); + const graph = useAtomValue(graphState); const currentProcessor = useRef(null); const saveGraph = useSaveCurrentGraph(); const currentExecution = useCurrentExecution(); - const setUserInputModalSubmit = useSetRecoilState(userInputModalSubmitState); - const setUserInputQuestions = useSetRecoilState(userInputModalQuestionsState); - const savedSettings = useRecoilValue(settingsState); - const loadedRecording = useRecoilValue(loadedRecordingState); - const setLastRecordingState = useSetRecoilState(lastRecordingState); - const [{ testSuites }, setTrivetState] = useRecoilState(trivetState); - const recordExecutions = useRecoilValue(recordExecutionsState); - const projectData = useRecoilValue(projectDataState); - const projectContext = useRecoilValue(projectContextState(project.metadata.id)); - const lastRunData = useRecoilValue(lastRunDataByNodeState); + const setUserInputModalSubmit = useSetAtom(userInputModalSubmitState); + const setUserInputQuestions = useSetAtom(userInputModalQuestionsState); + const savedSettings = useAtomValue(settingsState); + const loadedRecording = useAtomValue(loadedRecordingState); + const setLastRecordingState = useSetAtom(lastRecordingState); + const [{ testSuites }, setTrivetState] = useAtom(trivetState); + const recordExecutions = useAtomValue(recordExecutionsState); + const projectData = useAtomValue(projectDataState); + const projectContext = useAtomValue(projectContextState(project.metadata.id)); + const lastRunData = useAtomValue(lastRunDataByNodeState); function attachGraphEvents(processor: GraphProcessor) { processor.on('nodeStart', currentExecution.onNodeStart); diff --git a/packages/app/src/hooks/useMenuCommands.ts b/packages/app/src/hooks/useMenuCommands.ts index 0be80bedc..63adf42bb 100644 --- a/packages/app/src/hooks/useMenuCommands.ts +++ b/packages/app/src/hooks/useMenuCommands.ts @@ -3,7 +3,6 @@ import { useSaveProject } from './useSaveProject.js'; import { window } from '@tauri-apps/api'; import { match } from 'ts-pattern'; import { useLoadProjectWithFileBrowser } from './useLoadProjectWithFileBrowser.js'; -import { useRecoilState, useSetRecoilState } from 'recoil'; import { settingsModalOpenState } from '../components/SettingsModal.js'; import { graphState } from '../state/graph.js'; import { useLoadRecording } from './useLoadRecording.js'; @@ -13,6 +12,7 @@ import { helpModalOpenState, newProjectModalOpenState } from '../state/ui'; import { useToggleRemoteDebugger } from '../components/DebuggerConnectPanel'; import { lastRunDataByNodeState } from '../state/dataFlow'; import { useImportGraph } from './useImportGraph'; +import { useAtom, useSetAtom } from 'jotai'; export type MenuIds = | 'settings' @@ -58,16 +58,16 @@ export function useMenuCommands( onRunGraph?: () => void; } = {}, ) { - const [graphData, setGraphData] = useRecoilState(graphState); + const [graphData, setGraphData] = useAtom(graphState); const { saveProject, saveProjectAs } = useSaveProject(); - const setNewProjectModalOpen = useSetRecoilState(newProjectModalOpenState); + const setNewProjectModalOpen = useSetAtom(newProjectModalOpenState); const loadProject = useLoadProjectWithFileBrowser(); - const setSettingsOpen = useSetRecoilState(settingsModalOpenState); + const setSettingsOpen = useSetAtom(settingsModalOpenState); const { loadRecording } = useLoadRecording(); const toggleRemoteDebugger = useToggleRemoteDebugger(); - const setLastRunData = useSetRecoilState(lastRunDataByNodeState); + const setLastRunData = useSetAtom(lastRunDataByNodeState); const importGraph = useImportGraph(); - const setHelpModalOpen = useSetRecoilState(helpModalOpenState); + const setHelpModalOpen = useSetAtom(helpModalOpenState); useEffect(() => { const handler: (e: { payload: MenuIds }) => void = ({ payload }) => { diff --git a/packages/app/src/hooks/useMonitorUpdateStatus.ts b/packages/app/src/hooks/useMonitorUpdateStatus.ts index 2206acde1..3514da6e9 100644 --- a/packages/app/src/hooks/useMonitorUpdateStatus.ts +++ b/packages/app/src/hooks/useMonitorUpdateStatus.ts @@ -1,12 +1,12 @@ import { isInTauri } from '../utils/tauri'; import { onUpdaterEvent } from '@tauri-apps/api/updater'; -import { useSetRecoilState } from 'recoil'; +import { useSetAtom } from 'jotai'; import { updateStatusState } from '../state/settings'; import { match } from 'ts-pattern'; import useAsyncEffect from 'use-async-effect'; export function useMonitorUpdateStatus() { - const setUpdateStatus = useSetRecoilState(updateStatusState); + const setUpdateStatus = useSetAtom(updateStatusState); useAsyncEffect(async () => { let unlisten: any | undefined = undefined; diff --git a/packages/app/src/hooks/useNewProject.ts b/packages/app/src/hooks/useNewProject.ts index 0c7105947..bf0323d9e 100644 --- a/packages/app/src/hooks/useNewProject.ts +++ b/packages/app/src/hooks/useNewProject.ts @@ -1,25 +1,27 @@ -import { useSetRecoilState } from 'recoil'; +import { useSetAtom, useAtomValue } from 'jotai'; import { loadedProjectState, openedProjectsSortedIdsState, openedProjectsState, projectState, + type OpenedProjectInfo, } from '../state/savedGraphs.js'; -import { emptyNodeGraph } from '@ironclad/rivet-core'; +import { emptyNodeGraph, type ProjectId } from '@ironclad/rivet-core'; import { graphState } from '../state/graph.js'; import { trivetState } from '../state/trivet'; import { blankProject } from '../utils/blankProject'; import { canvasPositionState } from '../state/graphBuilder'; export function useNewProject() { - const setProject = useSetRecoilState(projectState); - const setLoadedProject = useSetRecoilState(loadedProjectState); - const setGraphData = useSetRecoilState(graphState); - const setTrivetData = useSetRecoilState(trivetState); - const setPosition = useSetRecoilState(canvasPositionState); + const setProject = useSetAtom(projectState); + const setLoadedProject = useSetAtom(loadedProjectState); + const currentIds = useAtomValue(openedProjectsSortedIdsState); + const setOpenedProjectsSortedIds = useSetAtom(openedProjectsSortedIdsState); + const setOpenedProjects = useSetAtom(openedProjectsState); - const setOpenedProjectsSortedIds = useSetRecoilState(openedProjectsSortedIdsState); - const setOpenedProjects = useSetRecoilState(openedProjectsState); + const setGraphData = useSetAtom(graphState); + const setTrivetData = useSetAtom(trivetState); + const setPosition = useSetAtom(canvasPositionState); return ({ title, @@ -38,14 +40,14 @@ export function useNewProject() { setPosition({ x: 0, y: 0, zoom: 1 }); - setOpenedProjects((projects) => ({ - ...projects, + const newOpenedProjects: Record = { [project.metadata.id]: { project, fsPath: null, }, - })); - setOpenedProjectsSortedIds((ids) => [...ids, project.metadata.id]); + }; + setOpenedProjects(newOpenedProjects); + setOpenedProjectsSortedIds([...currentIds, project.metadata.id]); setGraphData(emptyNodeGraph()); setTrivetData({ diff --git a/packages/app/src/hooks/useNewProjectFromTemplate.ts b/packages/app/src/hooks/useNewProjectFromTemplate.ts index d824a8d22..d7dcdaf72 100644 --- a/packages/app/src/hooks/useNewProjectFromTemplate.ts +++ b/packages/app/src/hooks/useNewProjectFromTemplate.ts @@ -1,4 +1,4 @@ -import { useSetRecoilState } from 'recoil'; +import { useSetAtom } from 'jotai'; import { loadedProjectState, projectState } from '../state/savedGraphs.js'; import { type GraphId, @@ -14,10 +14,10 @@ import { duplicateGraph } from '../utils/duplicateGraph'; import { produce } from 'immer'; export function useNewProjectFromTemplate() { - const setProject = useSetRecoilState(projectState); - const setLoadedProject = useSetRecoilState(loadedProjectState); - const setGraphData = useSetRecoilState(graphState); - const setTrivetData = useSetRecoilState(trivetState); + const setProject = useSetAtom(projectState); + const setLoadedProject = useSetAtom(loadedProjectState); + const setGraphData = useSetAtom(graphState); + const setTrivetData = useSetAtom(trivetState); return (template: unknown) => { let [project] = deserializeProject(template); diff --git a/packages/app/src/hooks/useNodeBodyHeight.tsx b/packages/app/src/hooks/useNodeBodyHeight.tsx index 4713d6bd2..1d05674a2 100644 --- a/packages/app/src/hooks/useNodeBodyHeight.tsx +++ b/packages/app/src/hooks/useNodeBodyHeight.tsx @@ -1,6 +1,6 @@ import { type NodeId } from '@ironclad/rivet-core'; import { useCallback, useEffect, useMemo, useRef } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { nodesState } from '../state/graph'; const GARBAGE_COLLECTION_INTERVAL = 100; @@ -19,7 +19,7 @@ export interface HeightCache { * is first rendered in the dragging list. */ export const useNodeHeightCache = (): HeightCache => { - const nodes = useRecoilValue(nodesState); + const nodes = useAtomValue(nodesState); const ref = useRef>({}); const garbageCollectionCount = useRef(0); diff --git a/packages/app/src/hooks/useNodePortPositions.ts b/packages/app/src/hooks/useNodePortPositions.ts index ebc7c7336..09dfa9754 100644 --- a/packages/app/src/hooks/useNodePortPositions.ts +++ b/packages/app/src/hooks/useNodePortPositions.ts @@ -1,7 +1,7 @@ import { type PortId, type NodeId } from '@ironclad/rivet-core'; import { useState, useLayoutEffect, useRef, useCallback } from 'react'; import { type PortPositions } from '../components/NodeCanvas'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { nodesByIdState } from '../state/graph'; /** @@ -12,7 +12,7 @@ import { nodesByIdState } from '../state/graph'; */ export function useNodePortPositions({ enabled, isDraggingNode }: { enabled: boolean; isDraggingNode: boolean }) { const [nodePortPositions, setNodePortPositions] = useState({}); - const nodesById = useRecoilValue(nodesByIdState); + const nodesById = useAtomValue(nodesByIdState); const canvasRef = useRef(null); const recalculate = useCallback(() => { diff --git a/packages/app/src/hooks/useNodeTypes.ts b/packages/app/src/hooks/useNodeTypes.ts index 9a4e800fb..a5fe416d0 100644 --- a/packages/app/src/hooks/useNodeTypes.ts +++ b/packages/app/src/hooks/useNodeTypes.ts @@ -16,13 +16,13 @@ import { commentNodeDescriptor } from '../components/nodes/CommentNode'; import { imageNodeDescriptor } from '../components/nodes/ImageNode'; import { audioNodeDescriptor } from '../components/nodes/AudioNode'; import { appendToDatasetNodeDescriptor } from '../components/nodes/AppendToDatasetNode'; -import { useRecoilValue } from 'recoil'; import { pluginRefreshCounterState } from '../state/plugins'; import { loadDatasetNodeDescriptor } from '../components/nodes/LoadDatasetNode'; import { datasetNearestNeighborsNodeDescriptor } from '../components/nodes/DatasetNearestNeighborsNode'; import { getDatasetRowNodeDescriptor } from '../components/nodes/GetDatasetRowNode'; import { replaceDatasetNodeDescriptor } from '../components/nodes/ReplaceDatasetNode'; import { type InputsOrOutputsWithRefs } from '../state/dataFlow'; +import { useAtomValue } from 'jotai'; export type UnknownNodeComponentDescriptor = { Body?: FC<{ node: ChartNode }>; @@ -66,7 +66,7 @@ const overriddenDescriptors: Partial = { }; export function useNodeTypes(): NodeComponentDescriptors { - const counter = useRecoilValue(pluginRefreshCounterState); + const counter = useAtomValue(pluginRefreshCounterState); return useMemo(() => { if (Number.isNaN(counter)) { diff --git a/packages/app/src/hooks/usePasteNodes.ts b/packages/app/src/hooks/usePasteNodes.ts index 9d4ddfdb0..34096ff0a 100644 --- a/packages/app/src/hooks/usePasteNodes.ts +++ b/packages/app/src/hooks/usePasteNodes.ts @@ -1,18 +1,18 @@ -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { clipboardState } from '../state/clipboard'; -import { connectionsState, graphState, nodesState } from '../state/graph'; +import { connectionsState, nodesState } from '../state/graph'; import { type NodeId, newId, type NodeConnection } from '@ironclad/rivet-core'; import { useCanvasPositioning } from './useCanvasPositioning'; import { produce } from 'immer'; import { selectedNodesState } from '../state/graphBuilder'; import { isNotNull } from '../utils/genericUtilFunctions'; +import { useAtom, useSetAtom, useAtomValue } from 'jotai'; export function usePasteNodes() { - const clipboard = useRecoilValue(clipboardState); + const clipboard = useAtomValue(clipboardState); const { clientToCanvasPosition } = useCanvasPositioning(); - const setNodes = useSetRecoilState(nodesState); - const setSelectedNodeIds = useSetRecoilState(selectedNodesState); - const setConnections = useSetRecoilState(connectionsState); + const [nodes, setNodes] = useAtom(nodesState); + const setSelectedNodeIds = useSetAtom(selectedNodesState); + const [connections, setConnections] = useAtom(connectionsState); const pasteNodes = (mousePosition: { x: number; y: number }) => { if (clipboard?.type !== 'nodes') { @@ -53,7 +53,7 @@ export function usePasteNodes() { }); }); - setNodes((nodes) => [...nodes, ...newNodes]); + setNodes((prev) => [...prev, ...newNodes]); setSelectedNodeIds(newNodes.map((node) => node.id)); const newConnections: NodeConnection[] = clipboard.connections @@ -73,7 +73,7 @@ export function usePasteNodes() { }) .filter(isNotNull); - setConnections((connections) => [...connections, ...newConnections]); + setConnections([...connections, ...newConnections]); }; return pasteNodes; diff --git a/packages/app/src/hooks/useProjectPlugins.ts b/packages/app/src/hooks/useProjectPlugins.ts index 526bb7362..964573931 100644 --- a/packages/app/src/hooks/useProjectPlugins.ts +++ b/packages/app/src/hooks/useProjectPlugins.ts @@ -1,4 +1,4 @@ -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { projectPluginsState } from '../state/savedGraphs'; import { globalRivetNodeRegistry, resetGlobalRivetNodeRegistry, plugins as rivetPlugins } from '@ironclad/rivet-core'; import { pluginRefreshCounterState, pluginsState } from '../state/plugins'; @@ -11,9 +11,9 @@ import { useLoadPackagePlugin } from './useLoadPackagePlugin'; import useAsyncEffect from 'use-async-effect'; export function useProjectPlugins() { - const pluginSpecs = useRecoilValue(projectPluginsState); - const [plugins, setPlugins] = useRecoilState(pluginsState); - const setPluginRefreshCounter = useSetRecoilState(pluginRefreshCounterState); + const pluginSpecs = useAtomValue(projectPluginsState); + const [plugins, setPlugins] = useAtom(pluginsState); + const setPluginRefreshCounter = useSetAtom(pluginRefreshCounterState); const { loadPackagePlugin } = useLoadPackagePlugin({ onLog: (message) => console.log(message), }); diff --git a/packages/app/src/hooks/useRemoteDebugger.ts b/packages/app/src/hooks/useRemoteDebugger.ts index c00335bd6..e2376d9e7 100644 --- a/packages/app/src/hooks/useRemoteDebugger.ts +++ b/packages/app/src/hooks/useRemoteDebugger.ts @@ -1,5 +1,5 @@ import { useLatest } from 'ahooks'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtomValue, useAtom } from 'jotai'; import { remoteDebuggerState, selectedExecutorState } from '../state/execution.js'; import { useRef, useState } from 'react'; import { set } from 'lodash-es'; @@ -16,7 +16,7 @@ export function setCurrentDebuggerMessageHandler(handler: (message: string, data let manuallyDisconnecting = false; export function useRemoteDebugger(options: { onConnect?: () => void; onDisconnect?: () => void } = {}) { - const [remoteDebugger, setRemoteDebuggerState] = useRecoilState(remoteDebuggerState); + const [remoteDebugger, setRemoteDebuggerState] = useAtom(remoteDebuggerState); const onConnectLatest = useLatest(options.onConnect ?? (() => {})); const onDisconnectLatest = useLatest(options.onDisconnect ?? (() => {})); const [retryDelay, setRetryDelay] = useState(0); diff --git a/packages/app/src/hooks/useRemoteExecutor.ts b/packages/app/src/hooks/useRemoteExecutor.ts index 709d7f1f1..68669a620 100644 --- a/packages/app/src/hooks/useRemoteExecutor.ts +++ b/packages/app/src/hooks/useRemoteExecutor.ts @@ -11,7 +11,6 @@ import { type Outputs, } from '@ironclad/rivet-core'; import { useCurrentExecution } from './useCurrentExecution'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { graphState } from '../state/graph'; import { settingsState } from '../state/settings'; import { setCurrentDebuggerMessageHandler, useRemoteDebugger } from './useRemoteDebugger'; @@ -28,6 +27,7 @@ import { entries } from '../../../core/src/utils/typeSafety'; import { selectedExecutorState } from '../state/execution'; import { datasetProvider } from '../utils/globals'; import { type RunDataByNodeId, lastRunDataByNodeState } from '../state/dataFlow'; +import { useAtomValue, useSetAtom, useAtom } from 'jotai'; // TODO: This allows us to retrieve the GraphOutputs from the remote debugger. // If the remote debugger events had a unique ID for each run, this would feel a lot less hacky. @@ -39,17 +39,18 @@ let graphExecutionPromise: { }; export function useRemoteExecutor() { + const project = useAtomValue(projectState); + const projectData = useAtomValue(projectDataState); + const projectContext = useAtomValue(projectContextState(project.metadata.id)); + const currentExecution = useCurrentExecution(); - const graph = useRecoilValue(graphState); - const savedSettings = useRecoilValue(settingsState); - const project = useRecoilValue(projectState); - const projectData = useRecoilValue(projectDataState); - const [{ testSuites }, setTrivetState] = useRecoilState(trivetState); - const setUserInputModalSubmit = useSetRecoilState(userInputModalSubmitState); - const setUserInputQuestions = useSetRecoilState(userInputModalQuestionsState); - const selectedExecutor = useRecoilValue(selectedExecutorState); - const projectContext = useRecoilValue(projectContextState(project.metadata.id)); - const lastRunData = useRecoilValue(lastRunDataByNodeState); + const graph = useAtomValue(graphState); + const savedSettings = useAtomValue(settingsState); + const [{ testSuites }, setTrivetState] = useAtom(trivetState); + const setUserInputModalSubmit = useSetAtom(userInputModalSubmitState); + const setUserInputQuestions = useSetAtom(userInputModalQuestionsState); + const selectedExecutor = useAtomValue(selectedExecutorState); + const lastRunData = useAtomValue(lastRunDataByNodeState); const remoteDebugger = useRemoteDebugger({ onDisconnect: () => { diff --git a/packages/app/src/hooks/useRemoveNodes.ts b/packages/app/src/hooks/useRemoveNodes.ts index 43fcb2ff0..ca1202aa2 100644 --- a/packages/app/src/hooks/useRemoveNodes.ts +++ b/packages/app/src/hooks/useRemoveNodes.ts @@ -1,11 +1,11 @@ import { type ChartNode, type NodeId } from '@ironclad/rivet-core'; import { useStableCallback } from './useStableCallback'; -import { useRecoilState } from 'recoil'; +import { useAtom } from 'jotai'; import { connectionsState, nodesState } from '../state/graph'; export const useRemoveNodes = () => { - const [nodes, setNodes] = useRecoilState(nodesState); - const [connections, setConnections] = useRecoilState(connectionsState); + const [nodes, setNodes] = useAtom(nodesState); + const [connections, setConnections] = useAtom(connectionsState); const nodesChanged = useStableCallback((newNodes: ChartNode[]) => { setNodes?.(newNodes); diff --git a/packages/app/src/hooks/useSaveCurrentGraph.ts b/packages/app/src/hooks/useSaveCurrentGraph.ts index ca36ea339..67ed7462a 100644 --- a/packages/app/src/hooks/useSaveCurrentGraph.ts +++ b/packages/app/src/hooks/useSaveCurrentGraph.ts @@ -1,13 +1,13 @@ import { produce } from 'immer'; import { nanoid } from 'nanoid/non-secure'; -import { type GraphId } from '@ironclad/rivet-core'; -import { useRecoilState, useSetRecoilState } from 'recoil'; +import { type GraphId, type NodeGraph } from '@ironclad/rivet-core'; +import { useAtom } from 'jotai'; import { graphState } from '../state/graph.js'; import { savedGraphsState } from '../state/savedGraphs.js'; export function useSaveCurrentGraph() { - const [graphData, setGraphData] = useRecoilState(graphState); - const setSavedGraphs = useSetRecoilState(savedGraphsState); + const [graphData, setGraphData] = useAtom(graphState); + const [savedGraphs, setSavedGraphs] = useAtom(savedGraphsState); return () => { const currentGraph = produce(graphData, (draft) => { @@ -26,14 +26,12 @@ export function useSaveCurrentGraph() { setGraphData(currentGraph); - setSavedGraphs((savedGraphs) => { - const existingGraph = savedGraphs.find((g) => g.metadata?.id === currentGraph.metadata?.id); - if (existingGraph) { - return savedGraphs.map((g) => (g.metadata?.id === currentGraph.metadata?.id ? currentGraph : g)); - } else { - return [...savedGraphs, currentGraph]; - } - }); + const existingGraph = savedGraphs.find((g) => g.metadata?.id === currentGraph.metadata?.id); + if (existingGraph) { + setSavedGraphs(savedGraphs.map((g) => (g.metadata?.id === currentGraph.metadata?.id ? currentGraph : g))); + } else { + setSavedGraphs([...savedGraphs, currentGraph]); + } return currentGraph; }; diff --git a/packages/app/src/hooks/useSaveProject.ts b/packages/app/src/hooks/useSaveProject.ts index 78ae86519..29f1d914d 100644 --- a/packages/app/src/hooks/useSaveProject.ts +++ b/packages/app/src/hooks/useSaveProject.ts @@ -1,4 +1,4 @@ -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { loadedProjectState, openedProjectsState, projectState } from '../state/savedGraphs.js'; import { useSaveCurrentGraph } from './useSaveCurrentGraph.js'; import { produce } from 'immer'; @@ -8,17 +8,17 @@ import { trivetState } from '../state/trivet.js'; export function useSaveProject() { const saveGraph = useSaveCurrentGraph(); - const project = useRecoilValue(projectState); - const [loadedProject, setLoadedProject] = useRecoilState(loadedProjectState); - const { testSuites } = useRecoilValue(trivetState); - const setOpenedProjects = useSetRecoilState(openedProjectsState); + const project = useAtomValue(projectState); + const [loadedProject, setLoadedProject] = useAtom(loadedProjectState); + const { testSuites } = useAtomValue(trivetState); + const setOpenedProjects = useSetAtom(openedProjectsState); async function saveProject() { if (!loadedProject.loaded || !loadedProject.path) { return saveProjectAs(); } - const savedGraph = saveGraph(); // TODO stupid react rerendering... project will still be stale and not have this graph + const savedGraph = saveGraph(); const newProject = produce(project, (draft) => { draft.graphs[savedGraph.metadata!.id!] = savedGraph; @@ -50,7 +50,7 @@ export function useSaveProject() { } async function saveProjectAs() { - const savedGraph = saveGraph(); // TODO stupid react rerendering... project will still be stale and not have this graph + const savedGraph = saveGraph(); const newProject = produce(project, (draft) => { draft.graphs[savedGraph.metadata!.id!] = savedGraph; @@ -76,13 +76,12 @@ export function useSaveProject() { loaded: true, path: filePath, }); - setOpenedProjects((projects) => ({ - ...projects, + setOpenedProjects({ [project.metadata.id]: { project, fsPath: filePath, }, - })); + }); } } catch (cause) { clearTimeout(savingTimeout); diff --git a/packages/app/src/hooks/useSaveRecording.ts b/packages/app/src/hooks/useSaveRecording.ts index 3a1c7781c..1eb256203 100644 --- a/packages/app/src/hooks/useSaveRecording.ts +++ b/packages/app/src/hooks/useSaveRecording.ts @@ -1,10 +1,10 @@ -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { lastRecordingState } from '../state/execution'; import { ioProvider } from '../utils/globals'; import { useCallback } from 'react'; export function useSaveRecording() { - const recording = useRecoilValue(lastRecordingState); + const recording = useAtomValue(lastRecordingState); return useCallback(async () => { if (!recording) { diff --git a/packages/app/src/hooks/useSearchGraph.ts b/packages/app/src/hooks/useSearchGraph.ts index 0ca507e02..a912a1a79 100644 --- a/packages/app/src/hooks/useSearchGraph.ts +++ b/packages/app/src/hooks/useSearchGraph.ts @@ -1,4 +1,4 @@ -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; import { nodesState } from '../state/graph'; import { useEffect, useMemo } from 'react'; import { entries } from '../../../core/src/utils/typeSafety'; @@ -10,8 +10,8 @@ import { useNodeTypes } from './useNodeTypes'; import { useDependsOnPlugins } from './useDependsOnPlugins'; export function useSearchGraph() { - const graphNodes = useRecoilValue(nodesState); - const setSearchMatchingNodes = useSetRecoilState(searchMatchingNodeIdsState); + const graphNodes = useAtomValue(nodesState); + const setSearchMatchingNodes = useSetAtom(searchMatchingNodeIdsState); useDependsOnPlugins(); const focusOnNodes = useFocusOnNodes(); @@ -37,7 +37,7 @@ export function useSearchGraph() { }); }, [graphNodes, nodeTypes]); - const searchState = useRecoilValue(searchingGraphState); + const searchState = useAtomValue(searchingGraphState); const searchedNodes = useFuseSearch( searchableNodes, diff --git a/packages/app/src/hooks/useSetStaticData.ts b/packages/app/src/hooks/useSetStaticData.ts index de59eb480..7d2be935d 100644 --- a/packages/app/src/hooks/useSetStaticData.ts +++ b/packages/app/src/hooks/useSetStaticData.ts @@ -1,18 +1,20 @@ -import { useSetRecoilState } from 'recoil'; +import { useSetAtom } from 'jotai'; import { useStaticDataDatabase } from './useStaticDataDatabase'; import { projectDataState } from '../state/savedGraphs'; import { type DataId } from '@ironclad/rivet-core'; import { entries } from '../../../core/src/utils/typeSafety'; export function useSetStaticData() { - const setProjectData = useSetRecoilState(projectDataState); + const setProjectData = useSetAtom(projectDataState); const database = useStaticDataDatabase(); return async (data: Record) => { - setProjectData((existingData) => ({ - ...existingData, - ...data, - })); + setProjectData((prev) => { + return { + ...prev, + ...data, + }; + }); for (const [id, dataValue] of entries(data)) { try { diff --git a/packages/app/src/hooks/useSyncCurrentStateIntoOpenedProjects.ts b/packages/app/src/hooks/useSyncCurrentStateIntoOpenedProjects.ts index 98ff3e6fb..74ec39192 100644 --- a/packages/app/src/hooks/useSyncCurrentStateIntoOpenedProjects.ts +++ b/packages/app/src/hooks/useSyncCurrentStateIntoOpenedProjects.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { graphState } from '../state/graph'; import { loadedProjectState, @@ -10,50 +10,50 @@ import { } from '../state/savedGraphs'; export function useSyncCurrentStateIntoOpenedProjects() { - const [openedProjects, setOpenedProjects] = useRecoilState(openedProjectsState); - const [openedProjectsSortedIds, setOpenedProjectsSortedIds] = useRecoilState(openedProjectsSortedIdsState); + const [openedProjects, setOpenedProjects] = useAtom(openedProjectsState); + const [openedProjectsSortedIds, setOpenedProjectsSortedIds] = useAtom(openedProjectsSortedIdsState); - const currentProject = useRecoilValue(projectState); - const loadedProject = useRecoilValue(loadedProjectState); - const currentGraph = useRecoilValue(graphState); + const currentProject = useAtomValue(projectState); + const loadedProject = useAtomValue(loadedProjectState); + const currentGraph = useAtomValue(graphState); // Make sure current opened project is in opened projects useEffect(() => { if (currentProject && openedProjects[currentProject.metadata.id] == null) { - setOpenedProjects((openedProjects) => ({ + setOpenedProjects({ ...openedProjects, [currentProject.metadata.id]: { project: currentProject, fsPath: null, } satisfies OpenedProjectInfo, - })); + }); } if (loadedProject.path && !openedProjects[currentProject.metadata.id]?.fsPath) { - setOpenedProjects((openedProjects) => ({ + setOpenedProjects({ ...openedProjects, [currentProject.metadata.id]: { project: currentProject, fsPath: loadedProject.path, } satisfies OpenedProjectInfo, - })); + }); } if (currentProject && openedProjectsSortedIds.includes(currentProject.metadata.id) === false) { - setOpenedProjectsSortedIds((ids) => [...ids, currentProject.metadata.id]); + setOpenedProjectsSortedIds([...openedProjectsSortedIds, currentProject.metadata.id]); } // eslint-disable-next-line react-hooks/exhaustive-deps -- intentional }, [currentProject, loadedProject, setOpenedProjects]); // Sync current project into opened projects useEffect(() => { - setOpenedProjects((openedProjects) => ({ + setOpenedProjects({ ...openedProjects, [currentProject.metadata.id]: { ...openedProjects[currentProject.metadata.id], project: currentProject, } satisfies OpenedProjectInfo, - })); + }); // eslint-disable-next-line react-hooks/exhaustive-deps -- intentional }, [currentProject]); @@ -88,14 +88,14 @@ export function useSyncCurrentStateIntoOpenedProjects() { prevProjectState.project.metadata.id !== currentProject.metadata.id && openedProjects[prevProjectState.project.metadata.id] ) { - setOpenedProjects((openedProjects) => ({ + setOpenedProjects({ ...openedProjects, [prevProjectState.project.metadata.id]: { ...openedProjects[prevProjectState.project.metadata.id], project: prevProjectState.project, openedGraph: prevProjectState.openedGraph, } satisfies OpenedProjectInfo, - })); + }); // Update prevProjectState, so that we track changes to it setPrevProjectState({ project: currentProject, diff --git a/packages/app/src/hooks/useTestSuite.ts b/packages/app/src/hooks/useTestSuite.ts index beb695790..90d2ae53c 100644 --- a/packages/app/src/hooks/useTestSuite.ts +++ b/packages/app/src/hooks/useTestSuite.ts @@ -2,18 +2,18 @@ import { useCallback, useMemo } from 'react'; import { useStableCallback } from './useStableCallback'; import { type TrivetTestSuite } from '@ironclad/trivet'; import { nanoid } from 'nanoid/non-secure'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { trivetState } from '../state/trivet'; import { type GraphInputNode, type GraphOutputNode, type NodeGraph } from '@ironclad/rivet-core'; import { keyBy } from 'lodash-es'; import { savedGraphsState } from '../state/savedGraphs'; export function useTestSuite(testSuiteId: string | undefined) { - const [{ testSuites }, setState] = useRecoilState(trivetState); + const [{ testSuites }, setState] = useAtom(trivetState); const testSuite = testSuites.find((ts) => ts.id === testSuiteId); - const savedGraphs = useRecoilValue(savedGraphsState); + const savedGraphs = useAtomValue(savedGraphsState); const graphsById = useMemo>( () => keyBy(savedGraphs, (g) => g.metadata?.id as string), diff --git a/packages/app/src/hooks/useTotalRunCost.ts b/packages/app/src/hooks/useTotalRunCost.ts index 3341613da..717ceccfa 100644 --- a/packages/app/src/hooks/useTotalRunCost.ts +++ b/packages/app/src/hooks/useTotalRunCost.ts @@ -7,16 +7,16 @@ import { type NodeGraph, } from '@ironclad/rivet-core'; import { useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { lastRunDataByNodeState } from '../state/dataFlow'; import { graphState } from '../state/graph'; import { projectState } from '../state/savedGraphs'; import { entries } from '../../../core/src/utils/typeSafety'; export function useTotalRunCost() { - const lastRunData = useRecoilValue(lastRunDataByNodeState); - const project = useRecoilValue(projectState); - const graph = useRecoilValue(graphState); + const lastRunData = useAtomValue(lastRunDataByNodeState); + const project = useAtomValue(projectState); + const graph = useAtomValue(graphState); const allNodesById = useMemo(() => { if (!project) { diff --git a/packages/app/src/hooks/useUpdateNode.ts b/packages/app/src/hooks/useUpdateNode.ts index 78b4666c7..635cac12c 100644 --- a/packages/app/src/hooks/useUpdateNode.ts +++ b/packages/app/src/hooks/useUpdateNode.ts @@ -1,19 +1,19 @@ -import { useSetRecoilState } from 'recoil'; import { nodesState } from '../state/graph.js'; import { useCallback } from 'react'; import { type ChartNode } from '@ironclad/rivet-core'; +import { useSetAtom } from 'jotai'; export function useUpdateNode() { - const setNodes = useSetRecoilState(nodesState); + const setNodes = useSetAtom(nodesState); return useCallback( (node: ChartNode) => { - setNodes((nodes) => { - const nodeIndex = nodes.findIndex((n) => n.id === node.id); + setNodes((prevNodes) => { + const nodeIndex = prevNodes.findIndex((n) => n.id === node.id); if (nodeIndex === -1) { - return nodes; + return prevNodes; } - return [...nodes.slice(0, nodeIndex), node, ...nodes.slice(nodeIndex + 1)]; + return [...prevNodes.slice(0, nodeIndex), node, ...prevNodes.slice(nodeIndex + 1)]; }); }, [setNodes], diff --git a/packages/app/src/hooks/useUploadNewTemplate.ts b/packages/app/src/hooks/useUploadNewTemplate.ts index 309b00db8..6bd4f6875 100644 --- a/packages/app/src/hooks/useUploadNewTemplate.ts +++ b/packages/app/src/hooks/useUploadNewTemplate.ts @@ -1,4 +1,4 @@ -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectState } from '../state/savedGraphs'; import { type GraphId, serializeProject } from '@ironclad/rivet-core'; import { type UseMutationResult, useMutation } from '@tanstack/react-query'; @@ -18,7 +18,7 @@ export function useUploadNewTemplate({ onCompleted }: { onCompleted: () => void }, unknown > { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const plugins = useDependsOnPlugins(); const mutation = useMutation({ diff --git a/packages/app/src/hooks/useUploadNewTemplateVersion.ts b/packages/app/src/hooks/useUploadNewTemplateVersion.ts index 697f4178b..6ab9f404e 100644 --- a/packages/app/src/hooks/useUploadNewTemplateVersion.ts +++ b/packages/app/src/hooks/useUploadNewTemplateVersion.ts @@ -1,4 +1,3 @@ -import { useRecoilValue } from 'recoil'; import { projectState } from '../state/savedGraphs'; import { type GraphId, serializeProject } from '@ironclad/rivet-core'; import { type UseMutationResult, useMutation, useQueryClient } from '@tanstack/react-query'; @@ -6,6 +5,7 @@ import { getCommunityApi } from '../utils/getCommunityApi'; import { type PutTemplateVersionBody } from '../utils/communityApi'; import { useDependsOnPlugins } from './useDependsOnPlugins'; import { toast } from 'react-toastify'; +import { useAtomValue } from 'jotai'; export type UseUploadNewTemplateVersionParams = { version: string; @@ -21,7 +21,7 @@ export function useUploadNewTemplateVersion({ templateId: string; onCompleted: () => void; }): UseMutationResult { - const project = useRecoilValue(projectState); + const project = useAtomValue(projectState); const plugins = useDependsOnPlugins(); const queryClient = useQueryClient(); diff --git a/packages/app/src/hooks/useWindowTitle.ts b/packages/app/src/hooks/useWindowTitle.ts index 642b59988..81b4ee316 100644 --- a/packages/app/src/hooks/useWindowTitle.ts +++ b/packages/app/src/hooks/useWindowTitle.ts @@ -1,12 +1,12 @@ import { getVersion } from '@tauri-apps/api/app'; import { appWindow } from '@tauri-apps/api/window'; import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { projectState, loadedProjectState } from '../state/savedGraphs'; export function useWindowTitle() { - const project = useRecoilValue(projectState); - const loadedProject = useRecoilValue(loadedProjectState); + const project = useAtomValue(projectState); + const loadedProject = useAtomValue(loadedProjectState); useEffect(() => { (async () => { diff --git a/packages/app/src/hooks/useWireDragScrolling.ts b/packages/app/src/hooks/useWireDragScrolling.ts index 963196dbc..7b4f71059 100644 --- a/packages/app/src/hooks/useWireDragScrolling.ts +++ b/packages/app/src/hooks/useWireDragScrolling.ts @@ -1,4 +1,4 @@ -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useAtom, useAtomValue } from 'jotai'; import { canvasPositionState, draggingWireState } from '../state/graphBuilder'; import { useViewportBounds } from './useViewportBounds'; import { useCanvasPositioning } from './useCanvasPositioning'; @@ -6,10 +6,10 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { useLatest } from 'ahooks'; export function useWireDragScrolling() { - const draggingWire = useRecoilValue(draggingWireState); + const draggingWire = useAtomValue(draggingWireState); const viewport = useViewportBounds(); const { clientToCanvasPosition } = useCanvasPositioning(); - const [canvasPosition, setCanvasPosition] = useRecoilState(canvasPositionState); + const [canvasPosition, setCanvasPosition] = useAtom(canvasPositionState); const draggingWireLatest = useLatest(draggingWire); const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); diff --git a/packages/app/src/state/clipboard.ts b/packages/app/src/state/clipboard.ts index 14842f4f5..6246588fc 100644 --- a/packages/app/src/state/clipboard.ts +++ b/packages/app/src/state/clipboard.ts @@ -1,5 +1,5 @@ import { type NodeConnection, type ChartNode } from '@ironclad/rivet-core'; -import { atom } from 'recoil'; +import { atom } from 'jotai'; export type NodesClipboardItem = { type: 'nodes'; @@ -9,7 +9,4 @@ export type NodesClipboardItem = { export type ClipboardItem = NodesClipboardItem; -export const clipboardState = atom({ - key: 'clipboard', - default: undefined, -}); +export const clipboardState = atom(undefined); diff --git a/packages/app/src/state/community.ts b/packages/app/src/state/community.ts index ec89d7337..733077c86 100644 --- a/packages/app/src/state/community.ts +++ b/packages/app/src/state/community.ts @@ -1,6 +1,3 @@ -import { atom } from 'recoil'; +import { atom } from 'jotai'; -export const isLoggedInToCommunityState = atom({ - key: 'isLoggedInToCommunity', - default: undefined, -}); +export const isLoggedInToCommunityState = atom(undefined); diff --git a/packages/app/src/state/dataFlow.ts b/packages/app/src/state/dataFlow.ts index 1c3d92127..8a24d1cad 100644 --- a/packages/app/src/state/dataFlow.ts +++ b/packages/app/src/state/dataFlow.ts @@ -1,4 +1,5 @@ -import { atom, selectorFamily } from 'recoil'; +import { atom } from 'jotai'; +import { atomFamily } from 'jotai/utils'; import { type PortId, type GraphId, @@ -63,67 +64,41 @@ export type DataValueWithRefs = { }; }[DataType]; +export type PageValue = number | 'latest'; + +export type PageUpdater = (prev: PageValue) => PageValue; + export type ScalarDataValueWithRefs = Extract; -export const lastRunDataByNodeState = atom({ - key: 'lastData', - default: {}, -}); - -export const runningGraphsState = atom({ - key: 'runningGraphs', - default: [], -}); - -export const rootGraphState = atom({ - key: 'rootGraph', - default: undefined, -}); - -export const lastRunData = selectorFamily({ - key: 'lastRunData', - get: - (nodeId: NodeId) => - ({ get }) => { - return get(lastRunDataByNodeState)[nodeId]; - }, -}); - -export const graphRunningState = atom({ - key: 'graphRunning', - default: false, -}); - -export const graphStartTimeState = atom({ - key: 'graphStartTime', - default: undefined, -}); - -export const graphPausedState = atom({ - key: 'graphPaused', - default: false, -}); - -export const selectedProcessPageNodesState = atom>({ - key: 'selectedProcessPage', - default: {}, -}); - -export const selectedProcessPage = selectorFamily({ - key: 'selectedProcessPage', - get: - (nodeId: NodeId) => - ({ get }) => { - return get(selectedProcessPageNodesState)[nodeId] ?? 0; - }, - set: - (nodeId: NodeId) => - ({ set }, newValue) => { +export const lastRunDataByNodeState = atom({}); + +export const lastRunDataState = atomFamily((nodeId: NodeId) => atom((get) => get(lastRunDataByNodeState)[nodeId])); + +export const runningGraphsState = atom([]); + +export const rootGraphState = atom(undefined); + +export const graphRunningState = atom(false); + +export const graphStartTimeState = atom(undefined); + +export const graphPausedState = atom(false); + +export const selectedProcessPageNodesState = atom>({}); + +export const selectedProcessPageState = atomFamily((nodeId: NodeId) => + atom( + (get) => get(selectedProcessPageNodesState)[nodeId] ?? 0, + (get, set, newValue: PageValue | PageUpdater) => { set(selectedProcessPageNodesState, (oldValue) => { + const currentValue = oldValue[nodeId] ?? 0; + const nextValue = typeof newValue === 'function' ? (newValue as PageUpdater)(currentValue) : newValue; + return { ...oldValue, - [nodeId]: newValue, + [nodeId]: nextValue, }; }); }, -}); + ), +); diff --git a/packages/app/src/state/dataStudio.ts b/packages/app/src/state/dataStudio.ts index c13984a79..4b56a40e6 100644 --- a/packages/app/src/state/dataStudio.ts +++ b/packages/app/src/state/dataStudio.ts @@ -1,12 +1,6 @@ import { Dataset, type DatasetId, type DatasetMetadata } from '@ironclad/rivet-core'; -import { atom } from 'recoil'; +import { atom } from 'jotai'; -export const datasetsState = atom({ - key: 'datasets', - default: [], -}); +export const datasetsState = atom([]); -export const selectedDatasetState = atom({ - key: 'selectedDataset', - default: undefined, -}); +export const selectedDatasetState = atom(undefined); diff --git a/packages/app/src/state/execution.ts b/packages/app/src/state/execution.ts index fec142e60..507c28d11 100644 --- a/packages/app/src/state/execution.ts +++ b/packages/app/src/state/execution.ts @@ -1,19 +1,17 @@ -import { atom } from 'recoil'; +import { atom } from 'jotai'; +import { atomWithStorage } from 'jotai/utils'; import { type ExecutionRecorder } from '@ironclad/rivet-core'; -import { recoilPersist } from 'recoil-persist'; import { defaultExecutorState } from './settings'; +import { createStorage } from './storage.js'; -const { persistAtom } = recoilPersist({ key: 'execution' }); +const storage = createStorage('execution'); -export const remoteUploadAllowedState = atom({ - key: 'remoteUploadAllowed', - default: false, -}); +export const remoteUploadAllowedState = atom(false); -export const selectedExecutorState = atom<'browser' | 'nodejs'>({ - key: 'selectedExecutor', - default: defaultExecutorState, -}); +export const selectedExecutorState = atom( + (get) => get(defaultExecutorState), + (get, set, value: 'browser' | 'nodejs') => set(defaultExecutorState, value), +); export type RemoteDebuggerState = { socket: WebSocket | null; @@ -24,9 +22,9 @@ export type RemoteDebuggerState = { isInternalExecutor: boolean; }; -export const remoteDebuggerState = atom({ - key: 'remoteDebuggerState', - default: { +export const remoteDebuggerState = atomWithStorage( + 'remoteDebuggerState', + { socket: null, started: false, reconnecting: false, @@ -34,18 +32,12 @@ export const remoteDebuggerState = atom({ remoteUploadAllowed: false, isInternalExecutor: false, }, - effects: [persistAtom], -}); + storage, +); export const loadedRecordingState = atom<{ path: string; recorder: ExecutionRecorder; -} | null>({ - key: 'loadedRecording', - default: null, -}); +} | null>(null); -export const lastRecordingState = atom({ - key: 'lastRecording', - default: undefined, -}); +export const lastRecordingState = atom(undefined); diff --git a/packages/app/src/state/graph.ts b/packages/app/src/state/graph.ts index 50bf7a43f..a18eb2007 100644 --- a/packages/app/src/state/graph.ts +++ b/packages/app/src/state/graph.ts @@ -1,4 +1,5 @@ -import { DefaultValue, atom, selector, selectorFamily } from 'recoil'; +import { atom } from 'jotai'; +import { atomWithStorage, atomFamily } from 'jotai/utils'; import { type ChartNode, type NodeConnection, @@ -10,220 +11,133 @@ import { emptyNodeGraph, globalRivetNodeRegistry, } from '@ironclad/rivet-core'; -import { recoilPersist } from 'recoil-persist'; import { mapValues } from 'lodash-es'; import { projectState } from './savedGraphs'; import { pluginRefreshCounterState } from './plugins'; import { type CalculatedRevision } from '../utils/ProjectRevisionCalculator'; +import { createStorage } from './storage.js'; -const { persistAtom } = recoilPersist({ key: 'graph' }); +const storage = createStorage('graph'); -export const historicalGraphState = atom({ - key: 'historicalGraph', - default: null, -}); +// Basic atoms +export const historicalGraphState = atom(null); +export const isReadOnlyGraphState = atom(false); +export const historicalChangedNodesState = atom>(new Set()); -export const isReadOnlyGraphState = atom({ - key: 'isReadOnlyGraph', - default: false, -}); +export const graphState = atomWithStorage('graphState', emptyNodeGraph(), storage); -export const historicalChangedNodesState = atom>({ - key: 'historicalChangedNodes', - default: new Set(), -}); +// Derived atoms +export const graphMetadataState = atom( + (get) => get(graphState).metadata, + (get, set, newValue: NodeGraph['metadata']) => { + const currentGraph = get(graphState); + set(graphState, { ...currentGraph, metadata: newValue }); + }, +); -export const graphState = atom({ - key: 'graphState', - default: emptyNodeGraph(), - effects: [persistAtom], -}); +export const nodesState = atom( + (get) => get(graphState).nodes, + (get, set, newValue: ChartNode[] | ((prev: ChartNode[]) => ChartNode[])) => { + const currentGraph = get(graphState); + const currentNodes = currentGraph.nodes; -export const graphMetadataState = selector({ - key: 'graphMetadataState', - get: ({ get }) => { - return get(graphState).metadata; - }, - set: ({ set }, newValue) => { - set(graphState, (oldValue) => { - return { - ...oldValue, - metadata: newValue instanceof DefaultValue ? emptyNodeGraph().metadata : newValue, - }; - }); - }, -}); + const nextNodes = typeof newValue === 'function' ? newValue(currentNodes) : newValue; -export const nodesState = selector({ - key: 'nodesState', - get: ({ get }) => { - return get(graphState).nodes; - }, - set: ({ set }, newValue) => { - set(graphState, (oldValue) => { - return { - ...oldValue, - nodes: newValue instanceof DefaultValue ? [] : newValue, - }; - }); + set(graphState, { ...currentGraph, nodes: nextNodes }); }, -}); +); -export const connectionsState = selector({ - key: 'connectionsState', - get: ({ get }) => { - return get(graphState).connections; - }, - set: ({ set }, newValue) => { - set(graphState, (oldValue) => { - return { - ...oldValue, - connections: newValue instanceof DefaultValue ? [] : newValue, - }; - }); - }, -}); +export const connectionsState = atom( + (get) => get(graphState).connections, + (get, set, newValue: NodeConnection[] | ((prev: NodeConnection[]) => NodeConnection[])) => { + const currentGraph = get(graphState); + const currentConnections = currentGraph.connections; -export const nodesByIdState = selector({ - key: 'nodesByIdState', - get: ({ get }) => { - return get(nodesState).reduce( - (acc, node) => { - acc[node.id] = node; - return acc; - }, - {} as Record, - ); - }, -}); + const nextConnections = typeof newValue === 'function' ? newValue(currentConnections) : newValue; -export const nodesForConnectionState = selector({ - key: 'nodesForConnectionSelector', - get: ({ get }) => { - const nodesById = get(nodesByIdState); - return get(connectionsState).map((connection) => ({ - inputNode: nodesById[connection.inputNodeId], - outputNode: nodesById[connection.outputNodeId], - })); + set(graphState, { ...currentGraph, connections: nextConnections }); }, -}); +); -export const connectionsForNodeState = selector({ - key: 'connectionsForNodeSelector', - get: ({ get }) => { - return get(connectionsState).reduce( - (acc, connection) => { - acc[connection.inputNodeId] ??= []; - acc[connection.inputNodeId]!.push(connection); - - acc[connection.outputNodeId] ??= []; - acc[connection.outputNodeId]!.push(connection); - return acc; - }, - {} as Record, - ); - }, -}); - -/** Gets connections attached to a single node */ -export const connectionsForSingleNodeState = selectorFamily({ - key: 'connectionsForSingleNodeSelector', - get: - (nodeId: NodeId) => - ({ get }) => { - return get(connectionsForNodeState)[nodeId]; +export const nodesByIdState = atom((get) => + get(nodesState).reduce( + (acc, node) => { + acc[node.id] = node; + return acc; }, -}); - -/** Gets a single node by its ID */ -export const nodeByIdState = selectorFamily({ - key: 'nodeByIdSelector', - get: - (nodeId) => - ({ get }) => { - return get(nodesByIdState)[nodeId]; + {} as Record, + ), +); + +export const nodesForConnectionState = atom((get) => { + const nodesById = get(nodesByIdState); + return get(connectionsState).map((connection) => ({ + inputNode: nodesById[connection.inputNodeId], + outputNode: nodesById[connection.outputNodeId], + })); +}); + +export const connectionsForNodeState = atom((get) => + get(connectionsState).reduce( + (acc, connection) => { + acc[connection.inputNodeId] ??= []; + acc[connection.inputNodeId]!.push(connection); + + acc[connection.outputNodeId] ??= []; + acc[connection.outputNodeId]!.push(connection); + return acc; }, -}); + {} as Record, + ), +); -/** Node instances by node ID */ -export const nodeInstancesState = selector | undefined>>({ - key: 'nodeInstances', - get: ({ get }) => { - const nodesById = get(nodesByIdState); - get(pluginRefreshCounterState); - - return mapValues(nodesById, (node) => { - try { - return globalRivetNodeRegistry.createDynamicImpl(node); - } catch (err) { - return undefined; - } - }); - }, -}); +export const connectionsForSingleNodeState = atomFamily((nodeId: NodeId) => + atom((get) => get(connectionsForNodeState)[nodeId]), +); -/** Gets a single node instance by a node ID */ -export const nodeInstanceByIdState = selectorFamily | undefined, NodeId>({ - key: 'nodeInstanceById', - get: - (nodeId) => - ({ get }) => { - return get(nodeInstancesState)?.[nodeId]; - }, -}); +export const nodeByIdState = atomFamily((nodeId: NodeId) => atom((get) => get(nodesByIdState)[nodeId])); + +export const nodeInstancesState = atom((get) => { + const nodesById = get(nodesByIdState); + get(pluginRefreshCounterState); // Keep dependency -export const ioDefinitionsState = selector< - Record< - NodeId, - { - inputDefinitions: NodeInputDefinition[]; - outputDefinitions: NodeOutputDefinition[]; + return mapValues(nodesById, (node) => { + try { + return globalRivetNodeRegistry.createDynamicImpl(node); + } catch (err) { + return undefined; } - > ->({ - key: 'ioDefinitions', - get: ({ get }) => { - const nodeInstances = get(nodeInstancesState); - const connectionsForNode = get(connectionsForNodeState); - const nodesById = get(nodesByIdState); - const project = get(projectState); - - return mapValues(nodesById, (node) => { - const connections = connectionsForNode[node.id] ?? []; - - const inputDefinitions = nodeInstances[node.id]?.getInputDefinitions(connections, nodesById, project); - const outputDefinitions = nodeInstances[node.id]?.getOutputDefinitions(connections, nodesById, project); - - return inputDefinitions && outputDefinitions - ? { - inputDefinitions, - outputDefinitions, - } - : { inputDefinitions: [], outputDefinitions: [] }; - }); - }, + }); }); -export const ioDefinitionsForNodeState = selectorFamily< - { - inputDefinitions: NodeInputDefinition[]; - outputDefinitions: NodeOutputDefinition[]; - }, - NodeId | undefined ->({ - key: 'ioDefinitionsForNode', - get: - (nodeId) => - ({ get }) => { - return nodeId ? get(ioDefinitionsState)[nodeId]! : { inputDefinitions: [], outputDefinitions: [] }; - }, +export const nodeInstanceByIdState = atomFamily((nodeId: NodeId) => atom((get) => get(nodeInstancesState)?.[nodeId])); + +export const ioDefinitionsState = atom((get) => { + const nodeInstances = get(nodeInstancesState); + const connectionsForNode = get(connectionsForNodeState); + const nodesById = get(nodesByIdState); + const project = get(projectState); + + return mapValues(nodesById, (node) => { + const connections = connectionsForNode[node.id] ?? []; + + const inputDefinitions = nodeInstances[node.id]?.getInputDefinitions(connections, nodesById, project); + const outputDefinitions = nodeInstances[node.id]?.getOutputDefinitions(connections, nodesById, project); + + return inputDefinitions && outputDefinitions + ? { + inputDefinitions, + outputDefinitions, + } + : { inputDefinitions: [], outputDefinitions: [] }; + }); }); -export const nodeConstructorsState = selector({ - key: 'nodeConstructorsState', - get: ({ get }) => { - get(pluginRefreshCounterState); +export const ioDefinitionsForNodeState = atomFamily((nodeId: NodeId | undefined) => + atom((get) => (nodeId ? get(ioDefinitionsState)[nodeId]! : { inputDefinitions: [], outputDefinitions: [] })), +); - return globalRivetNodeRegistry.getNodeConstructors(); - }, +export const nodeConstructorsState = atom((get) => { + get(pluginRefreshCounterState); + return globalRivetNodeRegistry.getNodeConstructors(); }); diff --git a/packages/app/src/state/graphBuilder.ts b/packages/app/src/state/graphBuilder.ts index 67766a8ba..7b57faf86 100644 --- a/packages/app/src/state/graphBuilder.ts +++ b/packages/app/src/state/graphBuilder.ts @@ -1,4 +1,5 @@ -import { atom, selector, selectorFamily } from 'recoil'; +import { atom } from 'jotai'; +import { atomWithStorage, atomFamily } from 'jotai/utils'; import { type ChartNode, type GraphId, @@ -8,113 +9,73 @@ import { type DataType, type NodeGraph, } from '@ironclad/rivet-core'; -import { recoilPersist } from 'recoil-persist'; import { type WireDef } from '../components/WireLayer.js'; +import { createStorage } from './storage.js'; -const { persistAtom } = recoilPersist({ key: 'graphBuilder' }); +const storage = createStorage('graphBuilder'); -export const viewingNodeChangesState = atom({ - key: 'viewingNodeChanges', - default: undefined, -}); +export const viewingNodeChangesState = atom(undefined); -export const selectedNodesState = atom({ - key: 'selectedNodeState', - default: [], -}); +export const selectedNodesState = atom([]); -export const editingNodeState = atom({ - key: 'editingNodeState', - default: null, -}); +export const editingNodeState = atom(null); export type CanvasPosition = { x: number; y: number; zoom: number; fromSaved?: boolean }; export const canvasPositionState = atom({ - key: 'canvasPosition', - default: { x: 0, y: 0, zoom: 1 }, + x: 0, + y: 0, + zoom: 1, }); -export const lastCanvasPositionByGraphState = atom>({ - key: 'lastCanvasPositionByGraph', - default: {}, - effects: [persistAtom], -}); +export const lastCanvasPositionByGraphState = atomWithStorage>( + 'lastCanvasPositionByGraph', + {}, + storage, +); -export const draggingNodesState = atom({ - key: 'draggingNode', - default: [], -}); +export const draggingNodesState = atom([]); export const lastMousePositionState = atom<{ x: number; y: number }>({ - key: 'lastMousePosition', - default: { x: 0, y: 0 }, + x: 0, + y: 0, }); -export const sidebarOpenState = atom({ - key: 'sidebarOpen', - default: true, -}); +export const sidebarOpenState = atom(true); export type DraggingWireDef = WireDef & { readonly dataType: DataType | Readonly }; -export const draggingWireState = atom({ - key: 'draggingWire', - default: undefined, -}); +export const draggingWireState = atom(undefined); -export const isDraggingWireState = selector({ - key: 'isDraggingWire', - get: ({ get }) => { - return get(draggingWireState) !== undefined; - }, -}); +export const isDraggingWireState = atom((get) => get(draggingWireState) !== undefined); export const draggingWireClosestPortState = atom< - { nodeId: NodeId; portId: PortId; element: HTMLElement; definition: NodeInputDefinition } | undefined ->({ - key: 'draggingWireClosestPort', - default: undefined, -}); + | { + nodeId: NodeId; + portId: PortId; + element: HTMLElement; + definition: NodeInputDefinition; + } + | undefined +>(undefined); export const graphNavigationStackState = atom<{ stack: GraphId[]; index?: number; }>({ - key: 'graphNavigationStack', - default: { - stack: [], - index: undefined, - }, + stack: [], + index: undefined, }); -export const pinnedNodesState = atom({ - key: 'pinnedNodes', - default: [], -}); +export const pinnedNodesState = atom([]); -export const isPinnedState = selectorFamily({ - key: 'isPinned', - get: - (nodeId) => - ({ get }) => - get(pinnedNodesState).includes(nodeId), -}); +export const isPinnedState = atomFamily((nodeId: NodeId) => atom((get) => get(pinnedNodesState).includes(nodeId))); export const searchingGraphState = atom({ - key: 'searchingGraph', - default: { - searching: false, - query: '', - }, + searching: false, + query: '', }); -export const searchMatchingNodeIdsState = atom({ - key: 'searchMatchingNodeIds', - default: [], -}); +export const searchMatchingNodeIdsState = atom([]); -export const hoveringNodeState = atom({ - key: 'hoveringNode', - default: undefined, -}); +export const hoveringNodeState = atom(undefined); diff --git a/packages/app/src/state/persist.ts b/packages/app/src/state/persist.ts deleted file mode 100644 index cf69241b3..000000000 --- a/packages/app/src/state/persist.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { recoilPersist } from 'recoil-persist'; - -const { persistAtom } = recoilPersist(); - -export { persistAtom }; diff --git a/packages/app/src/state/plugins.ts b/packages/app/src/state/plugins.ts index 2d6fac411..81e908d8b 100644 --- a/packages/app/src/state/plugins.ts +++ b/packages/app/src/state/plugins.ts @@ -1,4 +1,4 @@ -import { atom } from 'recoil'; +import { atom } from 'jotai'; import { type PluginLoadSpec } from '../../../core/src/model/PluginLoadSpec'; export type PluginState = { @@ -7,12 +7,6 @@ export type PluginState = { spec: PluginLoadSpec; }; -export const pluginRefreshCounterState = atom({ - key: 'pluginRefreshCounterState', - default: 0, -}); +export const pluginRefreshCounterState = atom(0); -export const pluginsState = atom({ - key: 'pluginsState', - default: [], -}); +export const pluginsState = atom([]); diff --git a/packages/app/src/state/promptDesigner.ts b/packages/app/src/state/promptDesigner.ts index 3ecd81e87..caab9e044 100644 --- a/packages/app/src/state/promptDesigner.ts +++ b/packages/app/src/state/promptDesigner.ts @@ -1,4 +1,4 @@ -import { atom } from 'recoil'; +import { atom } from 'jotai'; import { type ChatMessage, type ChatNodeConfigData, type NodeId, type ProcessId } from '@ironclad/rivet-core'; export type PromptDesignerMessagesState = { @@ -6,34 +6,25 @@ export type PromptDesignerMessagesState = { }; export const promptDesignerMessagesState = atom({ - key: 'promptDesignerMessagesState', - default: { - messages: [], - }, + messages: [], }); export type PromptDesignerResponseState = { response?: string; }; -export const promptDesignerResponseState = atom({ - key: 'promptDesignerResponseState', - default: {}, -}); +export const promptDesignerResponseState = atom({}); export type PromptDesignerConfigurationState = { data: ChatNodeConfigData; }; export const promptDesignerConfigurationState = atom({ - key: 'promptDesignerConfigurationState', - default: { - data: { - model: 'gpt-4', - maxTokens: 1024, - temperature: 0.2, - useTopP: false, - }, + data: { + model: 'gpt-4', + maxTokens: 1024, + temperature: 0.2, + useTopP: false, }, }); @@ -43,20 +34,14 @@ export const promptDesignerAttachedChatNodeState = atom< processId: ProcessId; } | undefined ->({ - key: 'promptDesignerAttachedChatNodeState', - default: undefined, -}); +>(undefined); export type PromptDesignerState = { samples: number; }; export const promptDesignerState = atom({ - key: 'promptDesignerState', - default: { - samples: 10, - }, + samples: 10, }); export type PromptDesignerTestGroupResults = { @@ -72,7 +57,4 @@ export type PromptDesignerTestGroupResultsByNodeIdState = { [nodeId: string]: PromptDesignerTestGroupResults[]; }; -export const promptDesignerTestGroupResultsByNodeIdState = atom({ - key: 'promptDesignerTestGroupResultsByNodeIdState', - default: {}, -}); +export const promptDesignerTestGroupResultsByNodeIdState = atom({}); diff --git a/packages/app/src/state/savedGraphs.ts b/packages/app/src/state/savedGraphs.ts index 2316aa059..41c37977d 100644 --- a/packages/app/src/state/savedGraphs.ts +++ b/packages/app/src/state/savedGraphs.ts @@ -1,4 +1,5 @@ -import { DefaultValue, atom, atomFamily, selector, selectorFamily } from 'recoil'; +import { atom } from 'jotai'; +import { atomWithStorage, atomFamily } from 'jotai/utils'; import { nanoid } from 'nanoid/non-secure'; import { produce } from 'immer'; import { @@ -12,15 +13,23 @@ import { type DataValue, } from '@ironclad/rivet-core'; import { blankProject } from '../utils/blankProject.js'; -import { recoilPersist } from 'recoil-persist'; import { entries, values } from '../../../core/src/utils/typeSafety'; +import { createStorage } from './storage.js'; +/** Project context values stored in the IDE and not in the project file. Available in Context nodes. */ +export type ProjectContext = Record< + string, + { + value: DataValue; + secret: boolean; + } +>; -const { persistAtom } = recoilPersist({ key: 'project' }); +const storage = createStorage('project'); // What's the data of the last loaded project? -export const projectState = atom>({ - key: 'projectState', - default: { +export const projectState = atomWithStorage>( + 'projectState', + { metadata: { id: nanoid() as ProjectId, description: '', @@ -29,77 +38,58 @@ export const projectState = atom>({ graphs: {}, plugins: [], }, - effects: [persistAtom], -}); + storage, +); -export const projectDataState = atom | undefined>({ - key: 'projectDataState', - default: undefined, -}); +export const projectDataState = atom | undefined>(undefined); -export const projectMetadataState = selector({ - key: 'projectMetadataState', - get: ({ get }) => { - return get(projectState).metadata; - }, - set: ({ set }, newValue) => { - set(projectState, (oldValue) => { - return { - ...oldValue, - metadata: newValue instanceof DefaultValue ? blankProject().metadata : newValue, - }; +export const projectMetadataState = atom( + (get) => get(projectState).metadata, + (get, set, newValue: Project['metadata'] | undefined) => { + set(projectState, { + ...get(projectState), + metadata: newValue ?? blankProject().metadata, }); }, -}); - -export const projectGraphInfoState = selector({ - key: 'projectGraphInfoState', - get: ({ get }) => { - const project = get(projectState); - return { - graphs: Object.fromEntries( - entries(project.graphs).map(([id, graph]) => [ +); + +export const projectGraphInfoState = atom((get) => { + const project = get(projectState); + return { + graphs: Object.fromEntries( + entries(project.graphs).map(([id, graph]) => [ + id, + { id, - { - id, - name: graph.metadata!.name, - description: graph.metadata!.description, - }, - ]), - ), - metadata: project.metadata, - }; - }, + name: graph.metadata!.name, + description: graph.metadata!.description, + }, + ]), + ), + metadata: project.metadata, + }; }); // Which project file was loaded last and where is it? -export const loadedProjectState = atom<{ - path: string; - loaded: boolean; -}>({ - key: 'loadedProjectState', - default: { +export const loadedProjectState = atomWithStorage( + 'loadedProjectState', + { path: '', loaded: false, }, - effects: [persistAtom], -}); + storage, +); -export const savedGraphsState = selector({ - key: 'savedGraphsState', - get: ({ get }) => { +export const savedGraphsState = atom( + (get) => values(get(projectState).graphs ?? {}), + (get, set, newValue: NodeGraph[] | ((prev: NodeGraph[]) => NodeGraph[])) => { const project = get(projectState); - return values(project.graphs); - }, - set: ({ set, get }, newValue) => { - if (newValue instanceof DefaultValue) { - return; - } + const currentGraphs = Object.values(project.graphs ?? {}); + const nextGraphs = typeof newValue === 'function' ? newValue(currentGraphs) : newValue; - const project = get(projectState); const newProject = produce(project, (draft) => { draft.graphs = {}; - for (const graph of newValue) { + for (const graph of nextGraphs) { if (graph.metadata == null) { graph.metadata = { id: nanoid() as GraphId, @@ -116,22 +106,22 @@ export const savedGraphsState = selector({ set(projectState, newProject); }, -}); +); -export const projectPluginsState = selector({ - key: 'projectPluginsState', - get: ({ get }) => { - return get(projectState).plugins ?? []; - }, - set: ({ set }, newValue) => { - set(projectState, (oldValue) => { - return { - ...oldValue, - plugins: newValue instanceof DefaultValue ? blankProject().plugins : newValue, - }; +export const projectPluginsState = atom( + (get) => get(projectState).plugins ?? [], + (get, set, newValue: Project['plugins'] | ((prev: Project['plugins']) => Project['plugins']) | undefined) => { + const currentProject = get(projectState); + const currentPlugins = currentProject.plugins ?? blankProject().plugins; + + const nextPlugins = typeof newValue === 'function' ? newValue(currentPlugins) : newValue ?? blankProject().plugins; + + set(projectState, { + ...currentProject, + plugins: nextPlugins, }); }, -}); +); export type OpenedProjectInfo = { project: Project; @@ -144,56 +134,40 @@ export type OpenedProjectsInfo = { openedProjectsSortedIds: ProjectId[]; }; -export const projectsState = atom({ - key: 'projectsState', - default: { +export const projectsState = atomWithStorage( + 'projectsState', + { openedProjects: {}, openedProjectsSortedIds: [], }, - effects: [persistAtom], -}); - -export const openedProjectsState = selector({ - key: 'openedProjectsState', - get: ({ get }) => { - return get(projectsState).openedProjects; - }, - set: ({ set }, newValue) => { - set(projectsState, (oldValue) => { - return { - ...oldValue, - openedProjects: newValue instanceof DefaultValue ? {} : newValue, - }; + storage, +); + +export const openedProjectsState = atom( + (get) => get(projectsState).openedProjects, + (get, set, newValue: Record | undefined) => { + set(projectsState, { + ...get(projectsState), + openedProjects: newValue ?? {}, }); }, -}); +); -export const openedProjectsSortedIdsState = selector({ - key: 'openedProjectsSortedIdsState', - get: ({ get }) => { - return get(projectsState).openedProjectsSortedIds; - }, - set: ({ set }, newValue) => { - set(projectsState, (oldValue) => { - return { - ...oldValue, - openedProjectsSortedIds: newValue instanceof DefaultValue ? [] : newValue, - }; +export const openedProjectsSortedIdsState = atom( + (get) => get(projectsState).openedProjectsSortedIds, + (get, set, newValue: ProjectId[] | ((prev: ProjectId[]) => ProjectId[]) | undefined) => { + const currentProjects = get(projectsState); + const currentIds = currentProjects.openedProjectsSortedIds ?? []; + + const nextIds = typeof newValue === 'function' ? newValue(currentIds) : newValue ?? []; + + set(projectsState, { + ...currentProjects, + openedProjectsSortedIds: nextIds, }); }, -}); +); -/** Project context values stored in the IDE and not in the project file. Available in Context nodes. */ -export type ProjectContext = Record< - string, - { - value: DataValue; - secret: boolean; - } ->; - -export const projectContextState = atomFamily({ - key: 'projectContext', - default: {}, - effects: [persistAtom], -}); +export const projectContextState = atomFamily((projectId: ProjectId) => + atomWithStorage(`projectContext__"${projectId}"`, {}, storage), +); diff --git a/packages/app/src/state/settings.ts b/packages/app/src/state/settings.ts index 11b3c7b87..1adb31878 100644 --- a/packages/app/src/state/settings.ts +++ b/packages/app/src/state/settings.ts @@ -1,12 +1,16 @@ -import { atom } from 'recoil'; -import { persistAtom } from './persist.js'; +import { atom } from 'jotai'; +import { atomWithStorage } from 'jotai/utils'; import { type Settings } from '@ironclad/rivet-core'; import { isInTauri } from '../utils/tauri'; import { DEFAULT_CHAT_NODE_TIMEOUT } from '../../../core/src/utils/defaults'; +import { createStorage } from './storage.js'; -export const settingsState = atom({ - key: 'settings', - default: { +// Legacy storage key for recoil-persist to avoid breaking existing users' settings +const storage = createStorage('recoil-persist'); + +export const settingsState = atomWithStorage( + 'settings', + { recordingPlaybackLatency: 1000, openAiKey: '', @@ -17,8 +21,8 @@ export const settingsState = atom({ pluginEnv: {}, pluginSettings: {}, }, - effects: [persistAtom], -}); + storage, +); export const themes = [ { @@ -37,23 +41,11 @@ export const themes = [ export type Theme = (typeof themes)[number]['value']; -export const themeState = atom({ - key: 'theme', - default: 'molten', - effects: [persistAtom], -}); +export const themeState = atomWithStorage('theme', 'molten', storage); -export const recordExecutionsState = atom({ - key: 'recordExecutions', - default: true, - effects: [persistAtom], -}); +export const recordExecutionsState = atomWithStorage('recordExecutions', true, storage); -export const defaultExecutorState = atom<'browser' | 'nodejs'>({ - key: 'defaultExecutor', - default: 'browser', - effects: [persistAtom], -}); +export const defaultExecutorState = atomWithStorage<'browser' | 'nodejs'>('defaultExecutor', 'browser', storage); export const executorOptions = isInTauri() ? ([ @@ -62,48 +54,18 @@ export const executorOptions = isInTauri() ] as const) : ([{ label: 'Browser', value: 'browser' }] as const); -export const previousDataPerNodeToKeepState = atom({ - key: 'previousDataPerNodeToKeep', - default: -1, - effects: [persistAtom], -}); - -export const preservePortTextCaseState = atom({ - key: 'preservePortTextCase', - default: false, - effects: [persistAtom], -}); - -export const checkForUpdatesState = atom({ - key: 'checkForUpdates', - default: true, - effects: [persistAtom], -}); - -export const skippedMaxVersionState = atom({ - key: 'skippedMaxVersion', - default: undefined, - effects: [persistAtom], -}); - -export const updateModalOpenState = atom({ - key: 'updateModalOpen', - default: false, -}); - -export const updateStatusState = atom({ - key: 'updateStatus', - default: undefined, -}); - -export const zoomSensitivityState = atom({ - key: 'zoomSensitivity', - default: 0.25, - effects: [persistAtom], -}); - -export const debuggerDefaultUrlState = atom({ - key: 'debuggerDefaultUrl', - default: 'ws://localhost:21888', - effects: [persistAtom], -}); +export const previousDataPerNodeToKeepState = atomWithStorage('previousDataPerNodeToKeep', -1, storage); + +export const preservePortTextCaseState = atomWithStorage('preservePortTextCase', false, storage); + +export const checkForUpdatesState = atomWithStorage('checkForUpdates', true, storage); + +export const skippedMaxVersionState = atomWithStorage('skippedMaxVersion', undefined, storage); + +export const updateModalOpenState = atom(false); + +export const updateStatusState = atom(undefined); + +export const zoomSensitivityState = atomWithStorage('zoomSensitivity', 0.25, storage); + +export const debuggerDefaultUrlState = atomWithStorage('debuggerDefaultUrl', 'ws://localhost:21888', storage); diff --git a/packages/app/src/state/storage.ts b/packages/app/src/state/storage.ts new file mode 100644 index 000000000..28261631a --- /dev/null +++ b/packages/app/src/state/storage.ts @@ -0,0 +1,27 @@ +import { createJSONStorage } from 'jotai/utils'; +import type { SyncStorage } from 'jotai/vanilla/utils/atomWithStorage'; + +export const createStorage = (mainKey?: string): SyncStorage => { + const storage = createJSONStorage(() => localStorage); + + if (!mainKey) { + return storage; + } + + return { + getItem: (key: string, initialValue) => { + const mainObject = storage.getItem(mainKey, {}); + return mainObject[key] ?? initialValue; + }, + setItem: (key: string, value): void => { + const mainObject = storage.getItem(mainKey, {}); + mainObject[key] = value; + storage.setItem(mainKey, mainObject); + }, + removeItem: (key: string): void => { + const mainObject = storage.getItem(mainKey, {}); + delete mainObject[key]; + storage.setItem(mainKey, mainObject); + }, + }; +}; diff --git a/packages/app/src/state/trivet.ts b/packages/app/src/state/trivet.ts index 95940e718..f40aab414 100644 --- a/packages/app/src/state/trivet.ts +++ b/packages/app/src/state/trivet.ts @@ -1,8 +1,7 @@ import { type TrivetResults, type TrivetTestSuite } from '@ironclad/trivet'; -import { atom, selector } from 'recoil'; -import { recoilPersist } from 'recoil-persist'; - -const { persistAtom } = recoilPersist({ key: 'trivet' }); +import { atom } from 'jotai'; +import { atomWithStorage } from 'jotai/utils'; +import { createStorage } from './storage.js'; export type TrivetState = { testSuites: TrivetTestSuite[]; @@ -12,16 +11,17 @@ export type TrivetState = { runningTests: boolean; }; -export const trivetState = atom({ - key: 'trivetState', - default: { +const storage = createStorage('trivet'); + +// Convert to persisted atom using atomWithStorage +export const trivetState = atomWithStorage( + 'trivetState', + { testSuites: [], runningTests: false, }, - effects: [persistAtom], -}); + storage, +); -export const trivetTestsRunningState = selector({ - key: 'trivetTestsRunningState', - get: ({ get }) => get(trivetState).runningTests, -}); +// Convert selector to derived atom +export const trivetTestsRunningState = atom((get) => get(trivetState).runningTests); diff --git a/packages/app/src/state/ui.ts b/packages/app/src/state/ui.ts index 284b9a9a8..ce20620b4 100644 --- a/packages/app/src/state/ui.ts +++ b/packages/app/src/state/ui.ts @@ -1,34 +1,17 @@ -import { atom } from 'recoil'; -import { recoilPersist } from 'recoil-persist'; +import { atom } from 'jotai'; +import { atomWithStorage } from 'jotai/utils'; +import { createStorage } from './storage.js'; -const { persistAtom } = recoilPersist({ - key: 'ui', -}); +const storage = createStorage('ui'); -export const debuggerPanelOpenState = atom({ - key: 'debuggerPanelOpenState', - default: false, -}); +export const debuggerPanelOpenState = atom(false); export type OverlayKey = 'promptDesigner' | 'trivet' | 'chatViewer' | 'dataStudio' | 'plugins' | 'community'; -export const overlayOpenState = atom({ - key: 'overlayOpenState', - default: undefined, -}); +export const overlayOpenState = atom(undefined); -export const newProjectModalOpenState = atom({ - key: 'newProjectModalOpenState', - default: false, -}); +export const newProjectModalOpenState = atom(false); -export const expandedFoldersState = atom>({ - key: 'expandedFoldersState', - default: {}, - effects: [persistAtom], -}); +export const expandedFoldersState = atomWithStorage>('expandedFoldersState', {}, storage); -export const helpModalOpenState = atom({ - key: 'helpModalOpenState', - default: false, -}); +export const helpModalOpenState = atom(false); diff --git a/packages/app/src/state/userInput.ts b/packages/app/src/state/userInput.ts index 7c1e39179..c30ebd84c 100644 --- a/packages/app/src/state/userInput.ts +++ b/packages/app/src/state/userInput.ts @@ -1,13 +1,11 @@ -import { atom } from 'recoil'; +import { atom } from 'jotai'; +import { atomWithStorage } from 'jotai/utils'; import { type ArrayDataValue, type NodeId, type ProcessId, type StringDataValue } from '@ironclad/rivet-core'; -import { recoilPersist } from 'recoil-persist'; +import { createStorage } from './storage.js'; -const { persistAtom } = recoilPersist({ key: 'userInput' }); +const storage = createStorage('userInput'); -export const userInputModalOpenState = atom({ - key: 'userInputModalOpenState', - default: false, -}); +export const userInputModalOpenState = atom(false); export type ProcessQuestions = { nodeId: NodeId; @@ -15,20 +13,12 @@ export type ProcessQuestions = { questions: string[]; }; -export const userInputModalQuestionsState = atom>({ - key: 'usetInputModalQuestionsState', - default: {}, -}); +export const userInputModalQuestionsState = atom>({}); export const userInputModalSubmitState = atom<{ submit: (nodeId: NodeId, answers: ArrayDataValue) => void; }>({ - key: 'userInputModalSubmitState', - default: { submit: () => {} }, + submit: () => {}, }); -export const lastAnswersState = atom>({ - key: 'lastAnswers', - default: {}, - effects: [persistAtom], -}); +export const lastAnswersState = atomWithStorage>('lastAnswers', {}, storage); diff --git a/packages/app/src/utils/deserializeProject.ts b/packages/app/src/utils/deserializeProject.ts index ad431d156..d4ded3875 100644 --- a/packages/app/src/utils/deserializeProject.ts +++ b/packages/app/src/utils/deserializeProject.ts @@ -45,7 +45,7 @@ export function deserializeProjectAsync(serializedProject: unknown): Promise=17.0.0" + react: ">=17.0.0" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + checksum: 607c2ab78301291719635f566c2d3c4fa0794a783f429fd07deffb5dd0920b198d25aebc82bc4f6894bc07aff3287fd06a02de394e0802b0ed2ad85f503eac09 + languageName: node + linkType: hard + "js-cookie@npm:^2.x.x": version: 2.2.1 resolution: "js-cookie@npm:2.2.1" @@ -16183,31 +16190,6 @@ __metadata: languageName: node linkType: hard -"recoil-persist@npm:^5.1.0": - version: 5.1.0 - resolution: "recoil-persist@npm:5.1.0" - peerDependencies: - recoil: ^0.7.2 - checksum: 979a199862d6aad63f7917700a23528264801b048ae5c7c6805b468e8d1b485d205802caf9170fc0dcdc7aac084643fe0074340b9607aaacef6097e4283f100a - languageName: node - linkType: hard - -"recoil@npm:^0.7.7": - version: 0.7.7 - resolution: "recoil@npm:0.7.7" - dependencies: - hamt_plus: "npm:1.0.2" - peerDependencies: - react: ">=16.13.1" - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - checksum: 4ac9dfeddda2795f213b14290de09767b04fb98f1a760bcabe796c53830229f51de63a02cf54c18991c557f25cfeb9571a129ae0a19d734bd98cfc45eebbc82d - languageName: node - linkType: hard - "recursive-readdir@npm:^2.2.2": version: 2.2.3 resolution: "recursive-readdir@npm:2.2.3"