Skip to content

Commit

Permalink
Handle dataset loading and error
Browse files Browse the repository at this point in the history
  • Loading branch information
danielfdsilva committed Aug 3, 2023
1 parent 00e3c39 commit a0e284b
Show file tree
Hide file tree
Showing 10 changed files with 405 additions and 156 deletions.
2 changes: 1 addition & 1 deletion app/scripts/components/common/loading-skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const pulse = keyframes`
}
`;

const pulsingAnimation = css`
export const pulsingAnimation = css`
animation: ${pulse} 0.8s ease 0s infinite alternate;
`;

Expand Down
10 changes: 9 additions & 1 deletion app/scripts/components/exploration/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,20 @@ export enum TimeDensity {
DAY = 'day'
}

export enum TimelineDatasetStatus {
IDLE = 'idle',
LOADING = 'loading',
SUCCEEDED = 'succeeded',
ERRORED = 'errored'
}

export interface TimelineDataset {
status: 'idle' | 'loading' | 'succeeded' | 'errored';
status: TimelineDatasetStatus;
data: any;
error: any;
settings: {
// user defined settings like visibility, opacity
isVisible?: boolean;
};
}

Expand Down
79 changes: 79 additions & 0 deletions app/scripts/components/exploration/dataset-list-item-status.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react';
import styled from 'styled-components';
import { themeVal } from '@devseed-ui/theme-provider';

import { DATASET_TRACK_BLOCK_HEIGHT } from './constants';

import { pulsingAnimation } from '$components/common/loading-skeleton';

const loadingPattern = '.-.. --- .- -.. .. -. --.'
.split(' ')
.map((c) => c.split(''));

const errorPattern = '. .-. .-. --- .-. . -..'
.split(' ')
.map((c) => c.split(''));

const Track = styled.div`
display: flex;
gap: 1.5rem;
margin: auto;
padding: 0 1rem;
`;

const Item = styled.div<{ code: string }>`
height: ${DATASET_TRACK_BLOCK_HEIGHT}px;
width: ${({ code }) => (code === '.' ? '1rem' : '2rem')};
border-radius: 4px;
`;

const TrackBlock = styled.div`
display: flex;
gap: 0.25rem;
`;

const TrackLoading = styled(Track)`
${pulsingAnimation}
${Item} {
background: ${themeVal('color.base-200')};
}
`;

const TrackError = styled(Track)`
${Item} {
background: ${themeVal('color.danger-200')};
}
`;

export function DatasetTrackLoading() {
/* eslint-disable react/no-array-index-key */
return (
<TrackLoading>
{loadingPattern.map((letter, i) => (
<TrackBlock key={i}>
{letter.map((s, i2) => (
<Item key={i2} code={s} />
))}
</TrackBlock>
))}
</TrackLoading>
);
/* eslint-enable react/no-array-index-key */
}

export function DatasetTrackError() {
/* eslint-disable react/no-array-index-key */
return (
<TrackError>
{errorPattern.map((letter, i) => (
<TrackBlock key={i}>
{letter.map((s, i2) => (
<Item key={i2} code={s} />
))}
</TrackBlock>
))}
</TrackError>
);
/* eslint-enable react/no-array-index-key */
}
79 changes: 56 additions & 23 deletions app/scripts/components/exploration/dataset-list-item.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useMemo, useState } from 'react';
import React, { useMemo } from 'react';
import { useAtomValue } from 'jotai';
import { Reorder, useDragControls } from 'framer-motion';
import styled, { useTheme } from 'styled-components';
import {
Expand All @@ -14,6 +15,7 @@ import {
} from 'date-fns';
import { ScaleTime } from 'd3';
import {
CollecticonArrowSpinCw,
CollecticonEye,
CollecticonEyeDisabled,
CollecticonGripVertical
Expand All @@ -26,8 +28,14 @@ import {
DATASET_TRACK_BLOCK_HEIGHT,
HEADER_COLUMN_WIDTH,
TimeDensity,
TimelineDataset
TimelineDataset,
TimelineDatasetStatus
} from './constants';
import { useTimelineDatasetAtom, useTimelineDatasetVisibility } from './hooks';
import {
DatasetTrackError,
DatasetTrackLoading
} from './dataset-list-item-status';

import { LayerGradientGraphic } from '$components/common/mapbox/layer-legend';

Expand Down Expand Up @@ -103,45 +111,64 @@ const DatasetData = styled.div`
padding: ${glsp(0.25, 0)};
display: flex;
align-items: center;
flex-grow: 1;
`;

interface DatasetListItemProps {
dataset: TimelineDataset;
datasetId: string;
width: number;
xScaled: ScaleTime<number, number>;
}

export function DatasetListItem(props: DatasetListItemProps) {
const { dataset, width, xScaled } = props;
const { datasetId, width, xScaled } = props;

const [isVisible, setVisible] = useState(true);
const datasetAtom = useTimelineDatasetAtom(datasetId);
const dataset = useAtomValue(datasetAtom);

const [isVisible, setVisible] = useTimelineDatasetVisibility(datasetAtom);

const controls = useDragControls();

const isError = dataset.status === TimelineDatasetStatus.ERRORED;

return (
<Reorder.Item value={dataset} dragListener={false} dragControls={controls}>
<DatasetItem>
<DatasetHeader>
<CollecticonGripVertical onPointerDown={(e) => controls.start(e)} />
<DatasetInfo>
<DatasetHeadline>
<Heading as='h3' size='xsmall'>
<Heading
as='h3'
size='xsmall'
variation={isError ? 'danger' : undefined}
>
{dataset.data.title}
</Heading>
<Toolbar size='small'>
<ToolbarIconButton onClick={() => setVisible((v) => !v)}>
{isVisible ? (
<CollecticonEye
{!isError ? (
<ToolbarIconButton onClick={() => setVisible((v) => !v)}>
{isVisible ? (
<CollecticonEye
meaningful
title='Toggle dataset visibility'
/>
) : (
<CollecticonEyeDisabled
meaningful
title='Toggle dataset visibility'
/>
)}
</ToolbarIconButton>
) : (
<ToolbarIconButton variation='danger-text'>
<CollecticonArrowSpinCw
meaningful
title='Toggle dataset visibility'
title='Retry dataset loading'
/>
) : (
<CollecticonEyeDisabled
meaningful
title='Toggle dataset visibility'
/>
)}
</ToolbarIconButton>
</ToolbarIconButton>
)}
</Toolbar>
</DatasetHeadline>
<LayerGradientGraphic
Expand All @@ -154,12 +181,18 @@ export function DatasetListItem(props: DatasetListItemProps) {
</DatasetInfo>
</DatasetHeader>
<DatasetData>
<DatasetTrack
width={width}
xScaled={xScaled}
dataset={dataset}
isVisible={isVisible}
/>
{dataset.status === TimelineDatasetStatus.LOADING && (
<DatasetTrackLoading />
)}
{isError && <DatasetTrackError />}
{dataset.status === TimelineDatasetStatus.SUCCEEDED && (
<DatasetTrack
width={width}
xScaled={xScaled}
dataset={dataset}
isVisible={!!isVisible}
/>
)}
</DatasetData>
</DatasetItem>
</Reorder.Item>
Expand Down
80 changes: 69 additions & 11 deletions app/scripts/components/exploration/datasets-mock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import styled from 'styled-components';
import { Button } from '@devseed-ui/button';

import { timelineDatasetsAtom } from './atoms';
import { TimelineDataset } from './constants';
import { TimelineDataset, TimelineDatasetStatus } from './constants';

const extraDataset = {
id: 'infinity',
title: 'Daily infinity!',
timeDensity: 'day',
domain: eachDayOfInterval({
Expand All @@ -18,6 +19,7 @@ const extraDataset = {

const datasets = [
{
id: 'monthly',
title: 'Monthly dataset',
timeDensity: 'month',
domain: [
Expand All @@ -29,6 +31,7 @@ const datasets = [
]
},
{
id: 'daily',
title: 'Daily dataset',
timeDensity: 'day',
domain: [
Expand Down Expand Up @@ -96,6 +99,7 @@ const datasets = [
]
},
{
id: 'daily2',
title: 'Daily 2',
timeDensity: 'day',
domain: [
Expand All @@ -107,49 +111,103 @@ const datasets = [
]
},
{
id: 'daily3',
title: 'Daily 3',
timeDensity: 'day',
domain: eachDayOfInterval({
start: new Date('2020-01-01'),
end: new Date('2021-01-01')
})
}
].map(makeDataset);
].map((d) => makeDataset(d));

function makeDataset(data): TimelineDataset {
function makeDataset(
data,
status = TimelineDatasetStatus.SUCCEEDED,
settings: Record<string, any> = {}
): TimelineDataset {
return {
status: 'succeeded',
status,
data,
error: null,
settings: {}
settings: {
...settings,
isVisible: settings.isVisible === undefined ? true : settings.isVisible
}
};
}

function toggleDataset(dataset) {
return (d) => {
if (d.find((dd) => dd.data.id === dataset.data.id)) {
return d.filter((dd) => dd.data.id !== dataset.data.id);
}
return [...d, dataset];
};
}

const MockPanel = styled.div`
display: flex;
flex-direction: row wrap;
padding: 1rem;
gap: 1rem;
`;

export function MockControls() {
const set = useSetAtom(timelineDatasetsAtom);

return (
<MockPanel>
<Button onClick={() => set([])} variation='base-outline'>
Clear
</Button>
<Button onClick={() => set(datasets)} variation='base-outline'>
Base datasets
</Button>
<Button
onClick={() => {
set((d) => {
if (d.find((dd) => dd.data.title === extraDataset.title)) {
return d.filter((dd) => dd.data.title !== extraDataset.title);
}
return [...d, makeDataset(extraDataset)];
});
set(toggleDataset(makeDataset(extraDataset)));
}}
variation='base-outline'
>
Toggle infinity dataset
</Button>
<Button
onClick={() => {
set(
toggleDataset(
makeDataset(
{
id: 'loading',
title: 'Loading dataset'
},
TimelineDatasetStatus.LOADING
)
)
);
}}
variation='base-outline'
>
Toggle Loading dataset
</Button>
<Button
onClick={() => {
set(
toggleDataset(
makeDataset(
{
id: 'errored',
title: 'Error dataset'
},
TimelineDatasetStatus.ERRORED
)
)
);
}}
variation='base-outline'
>
Toggle Error dataset
</Button>
</MockPanel>
);
}
Loading

0 comments on commit a0e284b

Please sign in to comment.