Skip to content

Commit

Permalink
Merge pull request #104 from valory-xyz/josh/xdai-requirement
Browse files Browse the repository at this point in the history
feat: state persistence
  • Loading branch information
truemiller authored May 24, 2024
2 parents a9b1fad + 16fe2f3 commit 87d85c9
Show file tree
Hide file tree
Showing 27 changed files with 2,177 additions and 1,921 deletions.
3 changes: 3 additions & 0 deletions electron/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const { killProcesses } = require('./processes');
const { isPortAvailable, findAvailablePort } = require('./ports');
const { PORT_RANGE, isWindows, isMac } = require('./constants');
const { macUpdater } = require('./update');
const { setupStoreIpc } = require('./store');

// Configure environment variables
dotenv.config();
Expand Down Expand Up @@ -255,6 +256,8 @@ const createMainWindow = () => {
mainWindow.hide();
});

setupStoreIpc(ipcMain, mainWindow);

if (isDev) {
mainWindow.webContents.openDevTools();
}
Expand Down
14 changes: 13 additions & 1 deletion electron/preload.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
const { contextBridge, ipcRenderer } = require('electron/renderer');

contextBridge.exposeInMainWorld('electronAPI', {
setTrayIcon: (status) => ipcRenderer.send('tray', status),
closeApp: () => ipcRenderer.send('close-app'),
minimizeApp: () => ipcRenderer.send('minimize-app'),
setTrayIcon: (status) => ipcRenderer.send('tray', status),
ipcRenderer: {
send: (channel, data) => ipcRenderer.send(channel, data),
on: (channel, func) =>
ipcRenderer.on(channel, (event, ...args) => func(...args)),
invoke: (channel, data) => ipcRenderer.invoke(channel, data),
},
store: {
store: () => ipcRenderer.invoke('store'),
get: (key) => ipcRenderer.invoke('store-get', key),
set: (key, value) => ipcRenderer.invoke('store-set', key, value),
delete: (key) => ipcRenderer.invoke('store-delete', key),
},
setAppHeight: (height) => ipcRenderer.send('set-height', height),
showNotification: (title, description) =>
ipcRenderer.send('show-notification', title, description),
Expand Down
29 changes: 29 additions & 0 deletions electron/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// set schema to validate store data
const schema = {
isInitialFunded: {
type: 'boolean',
default: false,
},
};

const setupStoreIpc = async (ipcChannel, mainWindow) => {
const Store = (await import('electron-store')).default;

/** @type import Store from 'electron-store' */
const store = new Store({
schema,
});

store.onDidAnyChange((data) => {
if (mainWindow?.webContents)
mainWindow.webContents.send('store-changed', data);
});

// exposed to electron browser window
ipcChannel.handle('store', () => store.store);
ipcChannel.handle('store-get', (_, key) => store.get(key));
ipcChannel.handle('store-set', (_, key, value) => store.set(key, value));
ipcChannel.handle('store-delete', (_, key) => store.delete(key));
};

module.exports = { setupStoreIpc };
1 change: 0 additions & 1 deletion frontend/common-util/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './copyToClipboard';
export * from './isDev';
export * from './numberFormatters';
export * from './setTrayIcon';
export * from './truncate';
10 changes: 0 additions & 10 deletions frontend/common-util/setTrayIcon.ts

This file was deleted.

7 changes: 3 additions & 4 deletions frontend/components/Layout/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,12 @@ const TopBarContainer = styled.div`
`;

export const TopBar = () => {
const { minimizeApp, closeApp } = useElectronApi();

const electronApi = useElectronApi();
return (
<TopBarContainer>
<TrafficLights>
<RedLight onClick={() => closeApp?.()} />
<YellowLight onClick={() => minimizeApp?.()} />
<RedLight onClick={() => electronApi?.closeApp?.()} />
<YellowLight onClick={() => electronApi?.minimizeApp?.()} />
<DisabledLight />
</TrafficLights>

Expand Down
5 changes: 2 additions & 3 deletions frontend/components/Main/MainAddFunds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ const CustomizedCardSection = styled(CardSection)<{ border?: boolean }>`

export const MainAddFunds = () => {
const [isAddFundsVisible, setIsAddFundsVisible] = useState(false);
const { masterSafeAddress, masterEoaAddress } = useWallet();
const { masterSafeAddress } = useWallet();

const fundingAddress: Address | undefined =
masterSafeAddress ?? masterEoaAddress;
const fundingAddress: Address | undefined = masterSafeAddress;

const truncatedFundingAddress: string | undefined = useMemo(
() => fundingAddress && truncateAddress(fundingAddress),
Expand Down
10 changes: 5 additions & 5 deletions frontend/components/Main/MainHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import Image from 'next/image';
import { useCallback, useEffect, useMemo, useState } from 'react';

import { Chain, DeploymentStatus } from '@/client';
import { setTrayIcon } from '@/common-util';
import { COLOR, LOW_BALANCE, SERVICE_TEMPLATES } from '@/constants';
import { useBalance, useServiceTemplates } from '@/hooks';
import { useElectronApi } from '@/hooks/useElectronApi';
Expand Down Expand Up @@ -35,6 +34,7 @@ export const MainHeader = () => {
totalEthBalance,
setIsPaused: setIsBalancePollingPaused,
} = useBalance();
const electronApi = useElectronApi();

const [serviceButtonState, setServiceButtonState] =
useState<ServiceButtonLoadingState>(ServiceButtonLoadingState.NotLoading);
Expand All @@ -46,13 +46,13 @@ export const MainHeader = () => {

useEffect(() => {
if (totalEthBalance && totalEthBalance < LOW_BALANCE) {
setTrayIcon('low-gas');
electronApi?.setTrayIcon?.('low-gas');
} else if (serviceStatus === DeploymentStatus.DEPLOYED) {
setTrayIcon('running');
electronApi?.setTrayIcon?.('running');
} else if (serviceStatus === DeploymentStatus.STOPPED) {
setTrayIcon('paused');
electronApi?.setTrayIcon?.('paused');
}
}, [totalEthBalance, serviceStatus]);
}, [totalEthBalance, serviceStatus, electronApi]);

const agentHead = useMemo(() => {
if (
Expand Down
41 changes: 35 additions & 6 deletions frontend/components/Main/MainNeedsFunds.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { Flex, Typography } from 'antd';
import { formatUnits } from 'ethers/lib/utils';
import { ReactNode, useMemo } from 'react';
import { ReactNode, useEffect, useMemo } from 'react';

import { SERVICE_TEMPLATES } from '@/constants';
import { UNICODE_SYMBOLS } from '@/constants/unicode';
import { useBalance } from '@/hooks';
import { useElectronApi } from '@/hooks/useElectronApi';
import { useStore } from '@/hooks/useStore';

import { Alert } from '../common/Alert';
import { CardSection } from '../styled/CardSection';

const { Text, Paragraph } = Typography;
const COVER_PREV_BLOCK_BORDER_STYLE = { marginTop: '-1px' };

export const useNeedsFunds = () => {
const useNeedsFunds = () => {
const serviceTemplate = SERVICE_TEMPLATES[0];
const { storeState } = useStore();
const { totalEthBalance, totalOlasBalance } = useBalance();

const isInitialFunded = storeState?.isInitialFunded as boolean | undefined;

const serviceFundRequirements = useMemo(() => {
const monthlyGasEstimate = Number(
formatUnits(`${serviceTemplate.configuration.monthly_gas_estimate}`, 18),
Expand Down Expand Up @@ -49,15 +54,27 @@ export const useNeedsFunds = () => {
[serviceFundRequirements?.olas, totalOlasBalance],
);

return { hasEnoughEth, hasEnoughOlas, serviceFundRequirements };
return {
hasEnoughEth,
hasEnoughOlas,
serviceFundRequirements,
isInitialFunded,
};
};

export const MainNeedsFunds = () => {
const { isBalanceLoaded, totalEthBalance, totalOlasBalance } = useBalance();
const { hasEnoughEth, hasEnoughOlas, serviceFundRequirements } =
useNeedsFunds();
const {
hasEnoughEth,
hasEnoughOlas,
serviceFundRequirements,
isInitialFunded,
} = useNeedsFunds();

const electronApi = useElectronApi();

const isVisible: boolean = useMemo(() => {
if (isInitialFunded) return false;
if (
[totalEthBalance, totalOlasBalance].some(
(balance) => balance === undefined,
Expand All @@ -68,7 +85,13 @@ export const MainNeedsFunds = () => {

if (hasEnoughEth && hasEnoughOlas) return false;
return true;
}, [hasEnoughEth, hasEnoughOlas, totalEthBalance, totalOlasBalance]);
}, [
hasEnoughEth,
hasEnoughOlas,
isInitialFunded,
totalEthBalance,
totalOlasBalance,
]);

const message: ReactNode = useMemo(
() => (
Expand Down Expand Up @@ -100,6 +123,12 @@ export const MainNeedsFunds = () => {
[serviceFundRequirements, hasEnoughEth, hasEnoughOlas],
);

useEffect(() => {
if (hasEnoughEth && hasEnoughOlas && !isInitialFunded) {
electronApi.store?.set?.('isInitialFunded', true);
}
}, [electronApi.store, hasEnoughEth, hasEnoughOlas, isInitialFunded]);

if (!isVisible) return null;
if (!isBalanceLoaded) return null;

Expand Down
29 changes: 0 additions & 29 deletions frontend/context/AppInfoProvider.tsx

This file was deleted.

75 changes: 58 additions & 17 deletions frontend/context/ElectronApiProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,81 @@
import { get } from 'lodash';
import { createContext, PropsWithChildren } from 'react';

import { ElectronStore, ElectronTrayIconStatus } from '@/types';

type ElectronApiContextProps = {
setAppHeight?: (height: number) => void;
closeApp?: () => void;
minimizeApp?: () => void;
setTrayIcon?: (status: ElectronTrayIconStatus) => void;
ipcRenderer?: {
send?: (channel: string, data: unknown) => void; // send messages to main process
on?: (
channel: string,
func: (event: unknown, data: unknown) => void,
) => void; // listen to messages from main process
invoke?: (channel: string, data: unknown) => Promise<unknown>; // send message to main process and get Promise response
};
store?: {
store?: () => Promise<ElectronStore>;
get?: (key: string) => Promise<unknown>;
set?: (key: string, value: unknown) => Promise<void>;
delete?: (key: string) => Promise<void>;
};
setAppHeight?: (height: unknown) => void;
notifyAgentRunning?: () => void;
showNotification?: (title: string, body?: string) => void;
};

export const ElectronApiContext = createContext<ElectronApiContextProps>({
setAppHeight: undefined,
closeApp: undefined,
minimizeApp: undefined,
showNotification: undefined,
closeApp: () => {},
minimizeApp: () => {},
setTrayIcon: () => {},
ipcRenderer: {
send: () => {},
on: () => {},
invoke: async () => {},
},
store: {
store: async () => ({}),
get: async () => {},
set: async () => {},
delete: async () => {},
},
setAppHeight: () => {},
});

const getElectronApiFunction = (functionNameInWindow: string) => {
if (typeof window === 'undefined') return;
export const ElectronApiProvider = ({ children }: PropsWithChildren) => {
const getElectronApiFunction = (functionNameInWindow: string) => {
if (typeof window === 'undefined') return;

const fn = get(window, `electronAPI.${functionNameInWindow}`);
if (!fn || typeof fn !== 'function') {
throw new Error(
`Function ${functionNameInWindow} not found in window.electronAPI`,
);
}
const fn = get(window, `electronAPI.${functionNameInWindow}`);
if (!fn || typeof fn !== 'function') {
throw new Error(
`Function ${functionNameInWindow} not found in window.electronAPI`,
);
}

return fn;
};
return fn;
};

export const ElectronApiProvider = ({ children }: PropsWithChildren) => {
return (
<ElectronApiContext.Provider
value={{
setAppHeight: getElectronApiFunction('setAppHeight'),
closeApp: getElectronApiFunction('closeApp'),
minimizeApp: getElectronApiFunction('minimizeApp'),
setTrayIcon: getElectronApiFunction('setTrayIcon'),
ipcRenderer: {
send: getElectronApiFunction('ipcRenderer.send'),
on: getElectronApiFunction('ipcRenderer.on'),
invoke: getElectronApiFunction('ipcRenderer.invoke'),
},
store: {
store: getElectronApiFunction('store.store'),
get: getElectronApiFunction('store.get'),
set: getElectronApiFunction('store.set'),
delete: getElectronApiFunction('store.delete'),
},
setAppHeight: getElectronApiFunction('setAppHeight'),
showNotification: getElectronApiFunction('showNotification'),
}}
>
Expand Down
Loading

0 comments on commit 87d85c9

Please sign in to comment.