Skip to content

Commit

Permalink
Fixed favorites
Browse files Browse the repository at this point in the history
  • Loading branch information
satr committed Sep 9, 2024
1 parent 85ac55a commit a2bc691
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 66 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ lint:
npm run "lint"
npm run "lint-ts"

.PHONY: lint-fix
lint-fix:
npm run "lint-fix"

.PHONY: lint-strict
lint-strict:
Expand Down
8 changes: 8 additions & 0 deletions src/components/app-list-item/dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const testData: Array<{ description: string } & AppListItemProps> = [
description: 'App',
app: { name: 'test-app' },
handler: noop,
isLoaded: true,
name: 'some-app',
},
{
description: 'App, marked Favourite, with Job',
Expand All @@ -29,19 +31,25 @@ const testData: Array<{ description: string } & AppListItemProps> = [
handler: noop,
isFavourite: true,
showStatus: true,
isLoaded: true,
name: 'some-app',
},
{
description: 'App, marked Favourite, without Job',
app: { name: 'app-test' },
handler: noop,
isFavourite: true,
showStatus: true,
isLoaded: true,
name: 'some-app',
},
{
description: 'App, marked Placeholder',
app: { name: 'app-placeholder' },
handler: noop,
isPlaceholder: true,
isLoaded: true,
name: 'some-app',
},
];

Expand Down
34 changes: 22 additions & 12 deletions src/components/app-list-item/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ export type FavouriteClickedHandler = (
) => void;

export interface AppListItemProps {
app: Readonly<ApplicationSummary>;
app?: Readonly<ApplicationSummary>;
handler: FavouriteClickedHandler;
isPlaceholder?: boolean;
isFavourite?: boolean;
showStatus?: boolean;
name: string;
isLoaded: boolean;
}

const latestJobStatus: Partial<
Expand Down Expand Up @@ -144,7 +146,7 @@ const AppItemStatus: FunctionComponent<ApplicationSummary> = ({
)}
</AsyncResource>

{environmentActiveComponents || latestJob ? (
{(environmentActiveComponents || latestJob) && (
<EnvironmentCardStatus
title="Application status"
statusElements={{
Expand All @@ -166,10 +168,6 @@ const AppItemStatus: FunctionComponent<ApplicationSummary> = ({
}),
}}
/>
) : (
<Tooltip title="This application does not exist or it is not yet deployed">
<Icon data={error_outlined} />
</Tooltip>
)}
</div>
</div>
Expand Down Expand Up @@ -198,39 +196,51 @@ export const AppListItem: FunctionComponent<AppListItemProps> = ({
isPlaceholder,
isFavourite,
showStatus,
name,
isLoaded,
}) => (
<WElement
className={clsx('app-list-item', {
'app-list-item--placeholder': isPlaceholder,
})}
appName={app.name}
appName={name}
isPlaceholder={isPlaceholder}
>
<div className="app-list-item--area">
<div className="app-list-item--area-icon">
<AppBadge appName={app.name} size={40} />
<AppBadge appName={name} size={40} />
</div>
<div className="grid app-list-item--area-details">
<div className="app-list-item--details">
<Typography className="app-list-item--details-title" variant="h6">
{app.name}
{name}
</Typography>
<div className="app-list-item--details-favourite">
<Button variant="ghost_icon" onClick={(e) => handler(e, app.name)}>
<Button variant="ghost_icon" onClick={(e) => handler(e, name)}>
<Icon data={isFavourite ? star_filled : star_outlined} />
</Button>
</div>
</div>
{showStatus && <AppItemStatus {...app} />}
{isLoaded &&
showStatus &&
(app ? (
<AppItemStatus {...app} />
) : (
<Tooltip title="This application does not exist">
<Icon data={error_outlined} />
</Tooltip>
))}
</div>
</div>
</WElement>
);

AppListItem.propTypes = {
app: PropTypes.object.isRequired as PropTypes.Validator<ApplicationSummary>,
app: PropTypes.object as PropTypes.Validator<ApplicationSummary>,
handler: PropTypes.func.isRequired,
isPlaceholder: PropTypes.bool,
isFavourite: PropTypes.bool,
showStatus: PropTypes.bool,
isLoaded: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
};
100 changes: 50 additions & 50 deletions src/components/app-list/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Button, CircularProgress, Typography } from '@equinor/eds-core-react';
import {
Button,
CircularProgress,
Icon,
Typography,
} from '@equinor/eds-core-react';
import { type FunctionComponent, useEffect, useState } from 'react';

import { radixApi, useGetSearchApplicationsQuery } from '../../store/radix-api';
Expand All @@ -8,6 +13,7 @@ import AsyncResource from '../async-resource/async-resource';
import PageCreateApplication from '../page-create-application';

import './style.css';
import { refresh } from '@equinor/eds-icons';
import { isEqual, uniq } from 'lodash';
import useLocalStorage from '../../effects/use-local-storage';
import { pollingInterval } from '../../store/defaults';
Expand All @@ -23,22 +29,34 @@ const LoadingCards: FunctionComponent<{ amount: number }> = ({ amount }) => (
app={{ name: 'dummy' }}
handler={(e) => e.preventDefault()}
isPlaceholder
name={''}
isLoaded={false}
/>
))}
</div>
);

const isArrayOfStrings = (variable: unknown): variable is string[] => {
return (
Array.isArray(variable) &&
variable.every((item) => typeof item === 'string')
);
};

export default function AppList() {
const [randomPlaceholderCount] = useState(Math.floor(Math.random() * 5) + 3);

const [favourites, setFavourites] = useLocalStorage<Array<string>>(
'favouriteApplications',
[]
[],
isArrayOfStrings
);

const [knownAppNames, setKnownAppNames] = useLocalStorage<Array<string>>(
'knownApplications',
[]
[],
isArrayOfStrings
);

const [refreshApps, appsState] =
radixApi.endpoints.showApplications.useLazyQuery({});

Expand Down Expand Up @@ -73,18 +91,9 @@ export default function AppList() {
isFavourite: favourites?.includes(appName),
}));

const favouriteApps = dataSorter(
[
...(favsData ?? [])
.filter(({ name }) => favourites?.includes(name))
.map((favApp) => ({ isFavourite: true, ...favApp }) as const),
...knownApps,
].filter(
(app, i, arr) =>
app.isFavourite && arr.findIndex((x) => x.name === app.name) === i // remove non-favourites and duplicates
),
[(x, y) => sortCompareString(x.name, y.name)]
);
const favouriteNames = dataSorter(favourites ?? [], [
(x, y) => sortCompareString(x, y),
]);

// remove from know app names previously favorite knownApps, which do not currently exist
useEffect(() => {
Expand Down Expand Up @@ -116,42 +125,28 @@ export default function AppList() {
<article className="grid grid--gap-medium">
<div className="app-list__header">
<Typography variant="body_short_bold">Favourites</Typography>
<div className="create-app">
<PageCreateApplication />
</div>
<PageCreateApplication />
</div>
<div className="app-list">
{favsState.isLoading ? (
<div>
<CircularProgress size={16} /> Loading favorites…
</div>
) : favouriteApps?.length > 0 ? (
{favouriteNames?.length > 0 ? (
<>
<div className="grid grid--gap-medium app-list--section">
<AsyncResource
asyncState={{
...favsState,
isLoading: favsState.isLoading && !(favouriteApps.length > 0),
}}
loadingContent={
<LoadingCards amount={favourites?.length ?? 0} />
}
>
<div className="app-list__list">
{favouriteApps.map((app, i) => (
<AppListItem
key={i}
app={app}
handler={(e) => {
changeFavouriteApplication(app.name, false);
e.preventDefault();
}}
isFavourite
showStatus
/>
))}
</div>
</AsyncResource>
<div className="app-list__list">
{favouriteNames.map((appName) => (
<AppListItem
key={appName}
app={favsData?.find((a) => a.name === appName)}
handler={(e) => {
changeFavouriteApplication(appName, false);
e.preventDefault();
}}
isFavourite
showStatus
isLoaded={favsState.isSuccess}
name={appName}
/>
))}
</div>
</div>
</>
) : (
Expand All @@ -167,6 +162,8 @@ export default function AppList() {
)}
<Button
className={'action--justify-end'}
variant="ghost"
color="primary"
disabled={appsState.isLoading || appsState.isFetching}
onClick={() =>
promiseHandler(
Expand All @@ -176,6 +173,7 @@ export default function AppList() {
)
}
>
<Icon data={refresh} />
Refresh list
</Button>
</div>
Expand All @@ -198,15 +196,17 @@ export default function AppList() {
}
>
<div className="app-list__list">
{knownApps.map((app, i) => (
{knownApps.map((app) => (
<AppListItem
key={i}
key={app.name}
app={app}
handler={(e) => {
changeFavouriteApplication(app.name, !app.isFavourite);
e.preventDefault();
}}
isFavourite={app.isFavourite}
name={app.name}
isLoaded={true}
/>
))}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/app-list/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
}
.app-list__header {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
align-items: center;
}
.app-list .app-list--section {
Expand Down
2 changes: 1 addition & 1 deletion src/components/page-create-application/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function PageCreateApplication() {
return (
<>
<Button
className="o-heading-page-button"
className="action--justify-end"
variant="ghost"
color="primary"
onClick={() => setVisibleScrim(true)}
Expand Down
9 changes: 7 additions & 2 deletions src/effects/use-local-storage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { useState } from 'react';
import { useInterval } from './use-interval';

export default function useLocalStorage<T>(key: string, defaultValue: T) {
export default function useLocalStorage<T>(
key: string,
defaultValue: T,
testContent?: (value: unknown) => boolean
) {
function getLocalStorageItem(itemKey: string) {
try {
const storedItem = localStorage.getItem(itemKey);
if (storedItem) {
return JSON.parse(storedItem) as T;
const data = JSON.parse(storedItem) as T;
return testContent?.(data) === false ? defaultValue : data;
}
return defaultValue; // Fallback to the default value if no data in localStorage
} catch (error) {
Expand Down
5 changes: 5 additions & 0 deletions src/style/objects.headings.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@
margin: 0;
padding-top: var(--space-2);
}

.action--justify-end {
justify-self: end;
}

0 comments on commit a2bc691

Please sign in to comment.