@@ -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"