Skip to content

Commit

Permalink
feat: added refresh button
Browse files Browse the repository at this point in the history
feat: adjusted app to work as core-registry-ui child application
fix: data table key error
  • Loading branch information
wwills2 committed Nov 7, 2024
1 parent 3426f13 commit 4c8dc2c
Show file tree
Hide file tree
Showing 18 changed files with 332 additions and 101 deletions.
128 changes: 102 additions & 26 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,135 @@ import { IntlProvider } from 'react-intl';
import { loadLocaleData } from '@/translations';
import '@/App.css';
import { AppNavigator } from '@/routes';
import { resetApiHost, setConfigFileLoaded, setHost, setLocale } from '@/store/slices/app';
import { resetApiHost, setConfigLoaded, setHost, setIsCoreRegistryUiApp, setLocale } from '@/store/slices/app';
import { ComponentCenteredSpinner } from '@/components';
import { useGetThemeColorsQuery, useGetUiConfigQuery } from '@/api';
import {
getParentSettings,
isIframe,
notifyParentOfAppLoad,
ParentSettings,
reconcileSavedUrl,
} from '@/utils/unified-ui-utils';

/**
* @returns app react component to be rendered by electron as the UI
*/
function App() {
const isCoreRegistryUiChildApp = isIframe();
let settingsFromParentApp: ParentSettings | null = null;
if (isCoreRegistryUiChildApp) {
notifyParentOfAppLoad();
settingsFromParentApp = getParentSettings();
}

reconcileSavedUrl();

const dispatch = useDispatch();
const appStore = useSelector((state: any) => state.app);
const [translationTokens, setTranslationTokens] = useState<object>();
const [appLoading, setAppLoading] = useState(true);
const { data: fetchedConfig, isLoading: configFileLoading } = useGetUiConfigQuery();
const { data: fetchedThemeColors, isLoading: themeColorsFileLoading } = useGetThemeColorsQuery();
const { data: fetchedConfig, isLoading: configFileLoading } = useGetUiConfigQuery(undefined, {
skip: isCoreRegistryUiChildApp,
});
const { data: fetchedThemeColors, isLoading: themeColorsFileLoading } = useGetThemeColorsQuery(undefined, {
skip: isCoreRegistryUiChildApp,
});

if (isCoreRegistryUiChildApp !== appStore.isCoreRegistryUiApp) {
dispatch(setIsCoreRegistryUiApp({ isCoreRegistryUiApp: isCoreRegistryUiChildApp }));
}

const setThemeColors = (colors: any) => {
// apply loaded theme colors via changing css property values (see App.css)
Object.entries(colors).forEach(([key, value]) => {
document.documentElement.style.setProperty(`--color-${key}`, value as string);
});
};

const setConfig = ({ apiHost, apiKey }: { apiHost: string; apiKey?: string }) => {
if (apiHost) {
if (apiKey) {
dispatch(setHost({ apiHost, apiKey }));
} else {
dispatch(setHost({ apiHost }));
}
dispatch(setConfigLoaded({ configLoaded: true }));
} else if (appStore.configFileLoaded) {
dispatch(resetApiHost());
dispatch(setConfigLoaded({ configLoaded: false }));
}
};

useEffect(() => {
if (appStore.locale) {
const processTranslationTokens = async () => {
const tokens = loadLocaleData(appStore.locale);
setTranslationTokens(tokens);
};

processTranslationTokens();
setTranslationTokens(loadLocaleData(appStore.locale));
} else {
dispatch(setLocale(navigator.language));
}
}, [appStore.locale, dispatch]);

// handle messages from parent
useEffect(() => {
if (fetchedThemeColors) {
// apply loaded theme colors via changing css property values (see App.css)
Object.entries(fetchedThemeColors).forEach(([key, value]) => {
document.documentElement.style.setProperty(`--color-${key}`, value as string);
});
const parentAppMessageListener = (event: MessageEvent) => {
if (event.origin === window.origin) {
const message = event.data;
if (message?.selectedLocale && message.selectedLocale !== appStore.locale) {
dispatch(setLocale(message.selectedLocale));
}
}
};

window.addEventListener('message', parentAppMessageListener);

return () => window.removeEventListener('message', parentAppMessageListener);
}, [appStore.locale, dispatch]);

/*
2 different loading scenarios:
- as a stand-alone app fetching files
- as a child app getting connection settings from parent local storage. in this case the config file is ignored
*/

// handle setting the theme colors when fetched as standalone app
useEffect(() => {
if (fetchedThemeColors && !isCoreRegistryUiChildApp) {
setThemeColors(fetchedThemeColors);
}
}, [fetchedThemeColors]);
}, [fetchedThemeColors, isCoreRegistryUiChildApp]);

// handle setting the config when fetched as standalone app
useEffect(() => {
if (fetchedConfig) {
if (fetchedConfig?.apiHost) {
dispatch(setHost({ apiHost: fetchedConfig.apiHost }));
}
dispatch(setConfigFileLoaded({ configFileLoaded: true }));
} else if (!configFileLoading && !fetchedConfig && appStore.configFileLoaded) {
dispatch(resetApiHost());
dispatch(setConfigFileLoaded({ configFileLoaded: false }));
if (!configFileLoading && fetchedConfig?.apiHost && !isCoreRegistryUiChildApp) {
setConfig({ apiHost: fetchedConfig?.apiHost });
}
}, [appStore.apiHost, appStore.configFileLoaded, fetchedConfig, configFileLoading, dispatch]);
}, [configFileLoading, fetchedConfig, isCoreRegistryUiChildApp /* do not add setConfig */]);

//handle setting theme colors when loaded as child app
useEffect(() => {
if (isCoreRegistryUiChildApp && settingsFromParentApp?.colors) {
setThemeColors(settingsFromParentApp.colors);
}
}, [isCoreRegistryUiChildApp, settingsFromParentApp?.colors]);

//handle setting config when loaded as child app
useEffect(() => {
if (isCoreRegistryUiChildApp && settingsFromParentApp?.apiHost) {
setConfig({ apiHost: settingsFromParentApp?.apiHost, apiKey: settingsFromParentApp.apiKey });
}
}, [
isCoreRegistryUiChildApp,
settingsFromParentApp?.apiHost,
settingsFromParentApp?.apiKey,
/* do not add setConfig */
]);

useEffect(() => {
// give the setConfigFileLoaded action time to dispatch
if (!configFileLoading) setTimeout(() => setAppLoading(false), 400);
}, [configFileLoading]);
if (!configFileLoading || isCoreRegistryUiChildApp) {
setTimeout(() => setAppLoading(false), 400);
}
}, [configFileLoading, isCoreRegistryUiChildApp]);

if (!translationTokens || configFileLoading || themeColorsFileLoading || appLoading) {
return <ComponentCenteredSpinner />;
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/components/blocks/buttons/ConnectButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useGetHealthQuery } from '@/api';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '@/store';
import { resetApiHost } from '@/store/slices/app';
import { reloadApplication } from '@/utils/unified-ui-utils';

const ConnectButton: React.FC = () => {
const location = useLocation();
Expand All @@ -32,13 +33,13 @@ const ConnectButton: React.FC = () => {

const handleDisconnect = () => {
dispatch(resetApiHost());
setTimeout(() => window.location.reload(), 0);
setTimeout(() => reloadApplication(), 0);
};

const onClose = () => {
refetch();
setTimeout(() => setActive(false));
setTimeout(() => window.location.reload(), 0);
setTimeout(() => reloadApplication(), 0);
};

return (
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/components/blocks/forms/ConnectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { FormattedMessage, IntlShape, useIntl } from 'react-intl';
import { useUrlHash } from '@/hooks';
import { useSelector } from 'react-redux';
import { RootState } from '@/store';
import { reloadApplication } from '@/utils/unified-ui-utils';

const validationSchema = yup.object({
apiHost: yup
Expand Down Expand Up @@ -53,7 +54,7 @@ const ConnectForm: React.FC<FormProps> = ({ onSubmit, hasServerError, onClearErr

const handleRetry = useCallback(() => {
setIsActive(false);
window.location.reload();
reloadApplication();
}, [setIsActive]);

return (
Expand Down
1 change: 1 addition & 0 deletions src/renderer/components/blocks/layout/LeftNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const LeftNav = () => {
return organizationsList.map((organization) => {
return (
<Sidebar.Item
key={organization.orgUid}
style={{ cursor: 'pointer' }}
active={isActive(`${ROUTES.ORG_ACTIVITIES}/${organization.orgUid}`)}
onClick={() => navigate(`${ROUTES.ORG_ACTIVITIES}/${organization.orgUid}`)}
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/components/blocks/layout/Template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { ErrorBoundary } from '@/pages';
import { LeftNav } from './LeftNav';
import { Outlet } from 'react-router-dom';
import { Header } from '@/components';
import { useManageSavedLocation } from '@/hooks/useManageSavedLocation';

const Template = () => {
useManageSavedLocation();

return (
<ErrorBoundary>
<div id="app" className="dark:bg-gray-800 w-full h-dvh">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ const ActivityDetailsModal: React.FC<DetailsModalProps> = ({ onClose }) => {
error: activityDataError,
} = useGetActivityRecordQuery({ warehouseUnitId, coinId, actionMode });

console.log('data:', activityData, 'loading:', activityDataLoading, 'error:', activityDataError);

const modalBody = () => {
if (activityDataLoading) {
return (
Expand Down
28 changes: 21 additions & 7 deletions src/renderer/components/blocks/modals/ConnectModal.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import React, { useState } from 'react';
import { ConnectForm, Modal } from '@/components';
import { FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { setHost } from '@/store/slices/app';
import { useGetHealthImmediateMutation } from '@/api';
import { useUrlHash } from '@/hooks';

// @ts-ignore
import { BaseQueryResult, FetchBaseQueryError, SerializedError } from '@reduxjs/toolkit/dist/query/baseQueryTypes';
import { reloadApplication } from '@/utils/unified-ui-utils';
import { RootState } from '@/store';

interface ConnectModalProps {
onClose: () => void;
}

const ConnectModal: React.FC<ConnectModalProps> = ({ onClose }: ConnectModalProps) => {
const dispatch = useDispatch();
const appStore = useSelector((state: RootState) => state.app);
const [getHealth] = useGetHealthImmediateMutation();
const [serverNotFound, setServerNotFound] = useState(false);
const [, setActive] = useUrlHash('connect');
Expand All @@ -29,7 +32,7 @@ const ConnectModal: React.FC<ConnectModalProps> = ({ onClose }: ConnectModalProp

dispatch(setHost({ apiHost, apiKey }));
setActive(false);
setTimeout(() => window.location.reload(), 0);
setTimeout(() => reloadApplication(), 0);
onClose();
};

Expand All @@ -41,11 +44,22 @@ const ConnectModal: React.FC<ConnectModalProps> = ({ onClose }: ConnectModalProp
</p>
</Modal.Header>
<Modal.Body>
<ConnectForm
onSubmit={handleSubmit}
hasServerError={serverNotFound}
onClearError={() => setServerNotFound(false)}
/>
{appStore?.isCoreRegistryUiApp ? (
<div>
<p>
<FormattedMessage id="cannot-connect-to-registry-api-with-current-settings" />.
</p>
<p>
<FormattedMessage id="please-disconnect-to-edit-the-api-url-and-api-key" />.
</p>
</div>
) : (
<ConnectForm
onSubmit={handleSubmit}
hasServerError={serverNotFound}
onClearError={() => setServerNotFound(false)}
/>
)}
</Modal.Body>
</Modal>
);
Expand Down
12 changes: 10 additions & 2 deletions src/renderer/components/blocks/tables/ActivitiesListTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ const ActivitiesListTable: React.FC<TableProps> = ({
totalPages,
totalCount,
}) => {
const dataWithUniqueTableId = useMemo<(Activity & { id: string })[]>(() => {
return data?.map((activity: Activity) => {
return {
...activity,
id: activity.coin_id + activity.cw_unit.warehouseUnitId + activity.mode,
};
});
}, [data]);

const columns = useMemo(() => {
/*
note that the datatable has default rendering for attributes at the top level of the passed in data object so all
Expand Down Expand Up @@ -135,8 +144,7 @@ const ActivitiesListTable: React.FC<TableProps> = ({
onChangeOrder={setOrder}
onRowClick={onRowClick}
order={order}
data={data}
primaryKey="warehouseUnitId"
data={dataWithUniqueTableId}
isLoading={isLoading}
tableHeightOffsetPx={250}
footer={
Expand Down
14 changes: 14 additions & 0 deletions src/renderer/components/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import React from 'react';
import { AppLogo, ConnectButton } from '@/components';
import { useSelector } from 'react-redux';
import { RootState } from '@/store';

const Header: React.FC = () => {
const appStore = useSelector((state: RootState) => state.app);

if (appStore.isCoreRegistryUiApp) {
// if running as a child app, the parent app provides the header.
// return hidden connect button to show connect message if unable to connect
return (
<div className="hidden">
<ConnectButton />
</div>
);
}

return (
<div style={{ height: '64px' }}>
<div className="pt-1 pb-1 w-screen h-16 bg-[#6e7d7f] dark:bg-gray-800 dark:border-gray-600">
Expand Down
44 changes: 44 additions & 0 deletions src/renderer/hooks/useManageSavedLocation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect, useState } from 'react';
import { isIframe } from '@/utils/unified-ui-utils';
import { useLocation, useNavigate } from 'react-router-dom';

/**
* MUST be run in a react router context
* hook to manage loading the last saved location when running as a child application.
*/
const useManageSavedLocation = () => {
const isCoreRegistryUiChildApp = isIframe();
const savedUrlString = localStorage.getItem('explorerUiLocation');
const [savedUrlLoaded, setSavedUrlLoaded] = useState(false);
const navigate = useNavigate();
const location = useLocation();

//navigate to the last location saved to local storage
useEffect(() => {
if (isCoreRegistryUiChildApp && !savedUrlLoaded && savedUrlString) {
navigate(savedUrlString, { replace: true });
}
setTimeout(() => setSavedUrlLoaded(true), 200);
}, [isCoreRegistryUiChildApp, navigate, savedUrlLoaded, savedUrlString]);

// save the current location to local storage for recall when the parent app refreshes
useEffect(() => {
if (isCoreRegistryUiChildApp && location && savedUrlLoaded) {
const reactAppCurrentLocation: string = location.pathname + location.hash + location.search;

if (reactAppCurrentLocation !== '/' && reactAppCurrentLocation !== savedUrlString) {
localStorage.setItem('explorerUiLocation', reactAppCurrentLocation);
}
}
}, [
isCoreRegistryUiChildApp,
location.pathname,
location.search,
location.hash,
savedUrlString,
location,
savedUrlLoaded,
]);
};

export { useManageSavedLocation };
Loading

0 comments on commit 4c8dc2c

Please sign in to comment.