Skip to content

Commit

Permalink
Merge pull request #98 from developmentseed/add/mosaic-selector
Browse files Browse the repository at this point in the history
Add mosaic drawer to prime panel
  • Loading branch information
vgeorge authored Feb 16, 2024
2 parents 66c3959 + d55d7d4 commit e12468e
Show file tree
Hide file tree
Showing 7 changed files with 531 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ function CheckpointSelector() {
.map((c) => (
<CheckpointOption
key={c.id}
// disabled={disabled}
onClick={async () => {
actorRef.send({
type: 'Apply checkpoint',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,78 @@
import React, { useState } from 'react';
import { ProjectMachineContext } from '../../../../../../fsm/project';
import { EditButton } from '../../../../../../styles/button';

import styled, { css } from 'styled-components';

import { Heading } from '@devseed-ui/typography';
import { themeVal, glsp } from '@devseed-ui/theme-provider';

import { ActionButton } from '../../../../../../styles/button';
import ShadowScrollbar from '../../../../../common/shadow-scrollbar';
import {
HeadOption,
HeadOptionHeadline,
HeadOptionToolbar,
} from '../../../../../../styles/panel';
import {
Subheading,
SubheadingStrong,
} from '../../../../../../styles/type/heading';
import { Subheading } from '../../../../../../styles/type/heading';
import { MosaicSelectorModal } from './modal';

import { ProjectMachineContext } from '../../../../../../fsm/project';
import { SESSION_MODES } from '../../../../../../fsm/project/constants';
import selectors from '../../../../../../fsm/project/selectors';
import * as guards from '../../../../../../fsm/project/guards';

import { formatMosaicDateRange } from '../../../../../../utils/dates';
import { SelectorHeadOption } from '../../../selection-styles';

const Option = styled.div`
display: grid;
cursor: pointer;
background: ${themeVal('color.baseDark')};
padding: ${glsp(0.25)} 0;
h1 {
margin: 0;
padding-left: ${glsp(1.5)};
}
${({ hasSubtitle }) =>
hasSubtitle &&
css`
.subtitle {
margin: 0;
}
`}
${({ selected }) =>
selected &&
css`
border-left: ${glsp(0.25)} solid ${themeVal('color.primary')};
h1 {
color: ${themeVal('color.primary')};
padding-left: ${glsp(1.25)};
}
background: ${themeVal('color.primaryAlphaA')};
`}
${({ selected }) =>
!selected &&
css`
&:hover {
background: ${themeVal('color.baseAlphaC')};
}
`}
`;

const MosaicOption = styled(Option)`
${({ disabled }) =>
disabled &&
css`
&:hover {
background: ${themeVal('color.baseDark')};
cursor: default;
}
`}
`;

export function MosaicSelector() {
const actorRef = ProjectMachineContext.useActorRef();
const [showModal, setShowModal] = useState(false);

const sessionMode = ProjectMachineContext.useSelector(selectors.sessionMode);
Expand All @@ -30,6 +86,15 @@ export function MosaicSelector() {
const currentMosaic = ProjectMachineContext.useSelector(
selectors.currentMosaic
);
const currentTimeframe = ProjectMachineContext.useSelector(
selectors.currentTimeframe
);
const timeframesList = ProjectMachineContext.useSelector(
({ context }) => context.timeframesList
);
const mosaicsList = ProjectMachineContext.useSelector(
({ context }) => context.mosaicsList
);

let label;
let disabled = true;
Expand All @@ -49,6 +114,18 @@ export function MosaicSelector() {
disabled = false;
}

const optionsList = timeframesList?.map((t) => {
const mosaic = mosaicsList.find((m) => m.id === t.mosaic);
return {
id: t.id,
label: formatMosaicDateRange(
mosaic.mosaic_ts_start,
mosaic.mosaic_ts_end
),
timeframe: t,
};
});

return (
<>
<MosaicSelectorModal
Expand All @@ -57,34 +134,67 @@ export function MosaicSelector() {
isProjectNew={isProjectNew}
imagerySource={currentImagerySource}
/>
<HeadOption>

<SelectorHeadOption hasSubtitle>
<HeadOptionHeadline usePadding>
<Subheading>Imagery Mosaic Date Range</Subheading>
<Subheading>Mosaics</Subheading>
</HeadOptionHeadline>
<SubheadingStrong
data-cy='mosaic-selector-label'
onClick={() => !disabled && setShowModal(true)}
title={label}
disabled={disabled}

<ShadowScrollbar
style={{
minHeight: '6rem',
maxHeight: '10rem',
backgroundColor: '#121826',
padding: '0.25rem 0',
margin: '0.75rem 0',
boxShadow: 'inset 0 -1px 0 0 rgba(240, 244, 255, 0.16)',
}}
>
{label}
</SubheadingStrong>
{!disabled && (
<HeadOptionToolbar>
<EditButton
useIcon='swap-horizontal'
id='select-mosaic-trigger'
disabled={disabled}
onClick={() => {
!disabled && setShowModal(true);
}}
title='Select Imagery Mosaic'
>
Edit Mosaic Selection
</EditButton>
</HeadOptionToolbar>
)}
</HeadOption>
<MosaicOption selected data-cy='selected-timeframe-header'>
<Heading size='xsmall'>{label}</Heading>
</MosaicOption>
{!!optionsList?.length &&
optionsList
.filter((t) => t.id != currentTimeframe?.id)
.map((t) => (
<MosaicOption
key={t.id}
disabled={disabled}
onClick={async () => {
actorRef.send({
type: 'Apply existing timeframe',
data: { timeframe: { ...t.timeframe } },
});
}}
>
<Heading size='xsmall'>{t.label}</Heading>
</MosaicOption>
))}
</ShadowScrollbar>
<HeadOptionToolbar>
<ActionButton
data-cy='mosaic-selector-label'
onClick={() => !disabled && setShowModal(true)}
title={label}
disabled={disabled}
useIcon='plus'
>
{label}
</ActionButton>
<ActionButton
title='Delete Current Mosaic'
id='delete-current-mosaic'
useIcon='trash-bin'
onClick={() => {
actorRef.send({
type: 'Delete timeframe',
});
}}
>
Delete Current Mosaic
</ActionButton>
</HeadOptionToolbar>
</SelectorHeadOption>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export const MosaicContentInner = ({
const currentImagerySource = ProjectMachineContext.useSelector(
selectors.currentImagerySource
);
const timeframesList = ProjectMachineContext.useSelector(
({ context }) => context.timeframesList
);

const {
data: collection,
Expand Down Expand Up @@ -70,6 +73,20 @@ export const MosaicContentInner = ({
}
}

// Check if mosaic is already used by a timeframe
const existingTimeframe = timeframesList.find(
(timeframe) => timeframe.mosaic === mosaic.id
);

if (existingTimeframe) {
actorRef.send({
type: 'Apply existing timeframe',
data: { timeframe: existingTimeframe },
});
onMosaicCreated();
return;
}

try {
const { mosaics: mosaicsList } = await apiClient.get('mosaic');
onMosaicCreated();
Expand Down
33 changes: 30 additions & 3 deletions app/assets/scripts/fsm/project/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,33 @@ export const actions = {
currentCheckpoint: event.data.checkpoint,
})),
setCurrentTimeframe: assign((context, event) => {
const { currentCheckpoint } = context;
const checkpoint = event.data?.checkpoint || context.currentCheckpoint;
const newTimeframe = event.data.timeframe;

const retrainClasses =
newTimeframe?.classes || currentCheckpoint?.classes || [];
// Clear current prediction if timeframe is changed
if (!newTimeframe) {
return {
currentTimeframe: null,
currentMosaic: null,
retrainClasses: [],
};
}

const retrainClasses = newTimeframe?.classes || checkpoint?.classes || [];

// A timeframe is always associated with a mosaic, but currently the API
// doesn't populate it in the response
const mosaicId =
typeof newTimeframe?.mosaic === 'string'
? newTimeframe?.mosaic
: newTimeframe?.mosaic?.id;

const currentMosaic = context.mosaicsList.find((m) => m.id === mosaicId);

// Apply new timeframe and (re-)initialize retrain classes
return {
currentTimeframe: { ...newTimeframe },
currentMosaic,
retrainClasses,
};
}),
Expand Down Expand Up @@ -322,6 +340,9 @@ export const actions = {
clearCurrentPrediction: assign(() => ({
currentPrediction: null,
})),
clearCurrentTimeframe: assign(() => ({
currentTimeframe: null,
})),
setMapRef: assign((context, event) => ({
mapRef: event.data.mapRef,
})),
Expand Down Expand Up @@ -454,6 +475,12 @@ export const actions = {
disabled: false,
},
})),
enterApplyTimeframe: assign(() => ({
sessionStatusMessage: 'Applying timeframe',
globalLoading: {
disabled: false,
},
})),
exitRetrainMode: assign((context) => {
const { freehandDraw, polygonDraw } = context.mapRef;

Expand Down
16 changes: 13 additions & 3 deletions app/assets/scripts/fsm/project/guards.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,17 @@ export const isPredictionReady = (context) => {
currentModel,
currentAoi,
} = context;
return (
!!currentAoi && !!currentImagerySource && !!currentMosaic && !!currentModel
);

if (context.project.id === 'new') {
return (
!!currentAoi &&
!!currentImagerySource &&
!!currentMosaic &&
!!currentModel
);
} else {
return !!currentAoi && !!currentMosaic;
}
};

export const isProjectNew = (c) => c.project.id === 'new';
Expand All @@ -38,3 +46,5 @@ export const isLivePredictionAreaSize = ({ currentAoi, apiLimits }) =>
currentAoi.area < apiLimits.live_inference;

export const hasAois = (c) => c.aoisList?.length > 0;

export const hasTimeframe = (c) => c.currentTimeframe;
54 changes: 44 additions & 10 deletions app/assets/scripts/fsm/project/machine.js

Large diffs are not rendered by default.

Loading

0 comments on commit e12468e

Please sign in to comment.