Skip to content

Commit

Permalink
feat: put measurements actions in a toolbar and add button to delete …
Browse files Browse the repository at this point in the history
…measurements (#426)
  • Loading branch information
Sebastien-Ahkrin committed Nov 30, 2022
1 parent f0720f3 commit 308048e
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 62 deletions.
1 change: 1 addition & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ extends:
- zakodium/unicorn
rules:
'react/no-unknown-property': ['error', { ignore: ['css'] }]
'@typescript-eslint/no-dynamic-delete': 'off'
1 change: 0 additions & 1 deletion src/app-data/loaders/biologicLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ function setDefault(
// populate key, if there is no data there.
if (!variables[storeAt]) {
variables[storeAt] = variables[varum];
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete variables[varum];
} else {
// if the storeAt is already occupied, swap the two
Expand Down
3 changes: 2 additions & 1 deletion src/app-data/state/appStateActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ export type AppStateAction =
measurement: MeasurementKindAndId;
display: Partial<MeasurementAppView>;
}
>;
>
| ActionType<'REMOVE_SELECTED_MEASUREMENTS', { kind: MeasurementKind }>;
2 changes: 2 additions & 0 deletions src/app-data/state/producers/appStateProducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
setSelectedMeasurementVisibility,
setMeasurementVisibility,
selectOrUnselectAllMeasurements,
removeSelectedMeasurements,
} from './measurements';
import { plotZoom, plotZoomOut } from './plot-view/plot-view';
import { ActionType, AppStateProducer } from './types';
Expand Down Expand Up @@ -44,6 +45,7 @@ const producers: Record<ActionType, AppStateProducer<any>> = {
CHANGE_MEASUREMENT_DISPLAY: changeMeasurementDisplay,
CHANGE_MEASUREMENTS_DISPLAY: changeMeasurementsDisplay,
LOAD_FULL_STATE: loadFullState,
REMOVE_SELECTED_MEASUREMENTS: removeSelectedMeasurements,
};

export function appStateProducer(
Expand Down
25 changes: 25 additions & 0 deletions src/app-data/state/producers/measurements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,28 @@ export const changeMeasurementsDisplay: AppStateProducer<
};
}
};

export const removeSelectedMeasurements: AppStateProducer<
'REMOVE_SELECTED_MEASUREMENTS'
> = (draft, action) => {
const {
payload: { kind },
} = action;

const selectedMeasurements = draft.view.selectedMeasurements[kind];
if (!selectedMeasurements) {
return;
}

delete draft.view.selectedMeasurements[kind];

draft.data.measurements[kind].entries = draft.data.measurements[
kind
].entries.filter((measurement) => {
return !selectedMeasurements.includes(measurement.id);
});

for (const id of selectedMeasurements) {
delete draft.view.measurements[id];
}
};
143 changes: 83 additions & 60 deletions src/app/panels/measurements/MeasurementsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import { FaTrash } from 'react-icons/fa';

import {
MeasurementBase,
Expand All @@ -23,7 +24,7 @@ interface MeasurementsTableRowProps {
kind: MeasurementsTableProps['kind'];
}

const measurementsTableCss = {
const styles = {
root: css`
font-size: 0.875rem;
line-height: 1.25rem;
Expand Down Expand Up @@ -74,6 +75,26 @@ const measurementsTableCss = {
cursor: default;
width: 70px;
`,
linkButton: css`
cursor: pointer;
:hover {
text-decoration: underline;
}
`,
container: css`
display: flex;
flex-direction: column;
gap: 5px;
`,
headerColumn: css`
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
padding-left: 5px;
border-bottom: 1px solid black;
`,
};

export function MeasurementsTable(props: MeasurementsTableProps) {
Expand All @@ -82,69 +103,75 @@ export function MeasurementsTable(props: MeasurementsTableProps) {
const {
data: { measurements },
} = useAppState();

return (
<table css={measurementsTableCss.root}>
<MeasurementsTableHeader kind={kind} />
<tbody css={measurementsTableCss.tbody}>
{measurements[kind].entries.map((element) => (
<MeasurementsTableRow key={element.id} item={element} kind={kind} />
))}
</tbody>
</table>
);
}

function MeasurementsTableHeader(props: {
kind: MeasurementsTableRowProps['kind'];
}) {
const dispatch = useAppDispatch();

const {
data: { measurements },
view: { selectedMeasurements },
} = useAppState();

const isChecked =
selectedMeasurements[props.kind]?.length ===
measurements[props.kind].entries.length;

function onSelectCheckbox() {
function onSelectLink(select: boolean) {
dispatch({
type: 'SELECT_ALL_MEASUREMENTS',
payload: {
kind: props.kind,
select: !isChecked,
kind,
select,
},
});
}

function onRemove() {
dispatch({ type: 'REMOVE_SELECTED_MEASUREMENTS', payload: { kind } });
}

return (
<div css={styles.container}>
<div css={styles.headerColumn}>
<MeasurementSelectedVisibilityChange kind={kind} openedEyes />
<MeasurementSelectedVisibilityChange kind={kind} openedEyes={false} />
<span css={styles.linkButton} onClick={() => onSelectLink(true)}>
Select all
</span>
<span css={styles.linkButton} onClick={() => onSelectLink(false)}>
Unselect all
</span>

<FaTrash style={{ cursor: 'pointer' }} onClick={onRemove} />
</div>

<table css={styles.root}>
<MeasurementsTableHeader />
<tbody css={styles.tbody}>
{measurements[kind].entries.map((element) => (
<MeasurementsTableRow key={element.id} item={element} kind={kind} />
))}
</tbody>
</table>
</div>
);
}

const tableHeaderStyles = {
thStyleEmpty: css`
display: flex;
gap: 5px;
align-items: center;
width: 70px;
`,
thStyleFilename: css`
width: 60%;
`,
thStyleTechnique: css`
width: 150px;
`,
tdName: css`
width: 60%;
overflow: hidden;
`,
};

function MeasurementsTableHeader() {
return (
<thead>
<tr css={measurementsTableCss.header}>
<th
style={{
display: 'flex',
gap: 5,
alignItems: 'center',
width: 70,
}}
>
<MeasurementSelectedVisibilityChange kind={props.kind} openedEyes />
<MeasurementSelectedVisibilityChange
kind={props.kind}
openedEyes={false}
/>
<MeasurementCheckbox
checked={isChecked}
onSelectCheckbox={onSelectCheckbox}
indeterminate={
!isChecked && (selectedMeasurements[props.kind] || []).length > 0
}
/>
</th>
<th style={{ width: '60%' }}>Filename</th>
<th style={{ width: 150 }}>Technique</th>
<tr css={styles.header}>
<th css={tableHeaderStyles.thStyleEmpty} />
<th css={tableHeaderStyles.thStyleFilename}>Filename</th>
<th css={tableHeaderStyles.thStyleTechnique}>Technique</th>
</tr>
</thead>
);
Expand Down Expand Up @@ -176,8 +203,8 @@ function MeasurementsTableRow(props: MeasurementsTableRowProps) {
}

return (
<tr css={measurementsTableCss.tr}>
<td css={measurementsTableCss.iconsContainer}>
<tr css={styles.tr}>
<td css={styles.iconsContainer}>
<MeasurementVisibilityToggle
id={item.id}
isVisible={measurements[item.id].visible}
Expand All @@ -192,11 +219,7 @@ function MeasurementsTableRow(props: MeasurementsTableRowProps) {
onSelectCheckbox={onSelectCheckbox}
/>
</td>
<td
onClick={onSelectRow}
style={{ width: '60%', overflow: 'hidden' }}
title={item.id}
>
<td css={tableHeaderStyles.tdName} onClick={onSelectRow} title={item.id}>
{item.info.file?.name ?? item.info.title}
</td>
<td onClick={onSelectRow}>{item.meta.technique}</td>
Expand Down
71 changes: 71 additions & 0 deletions src/components/button/ButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import { ReactNode } from 'react';

const styles = {
root: css`
display: inline-flex;
border-radius: 0.375rem;
isolation: isolate;
`,
firstButton: css`
position: relative;
display: inline-flex;
align-items: center;
font-weight: 500;
font-size: 0.875rem;
line-height: 1.25rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: 5px;
padding-bottom: 5px;
border-width: 1px;
border-top-left-radius: 0.375rem;
border-bottom-left-radius: 0.375rem;
`,
secondButton: css`
position: relative;
display: inline-flex;
align-items: center;
font-weight: 500;
font-size: 0.875rem;
line-height: 1.25rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: 5px;
padding-bottom: 5px;
border-width: 1px;
border-top-right-radius: 0.375rem;
border-bottom-right-radius: 0.375rem;
margin-left: -1px;
`,
};

interface ButtonGroupProps {
children: [ReactNode, ReactNode];
}

export function ButtonGroup(props: ButtonGroupProps) {
const { children } = props;
return <div css={styles.root}>{children}</div>;
}

interface ButtonGroupButtonProps {
position: 'first' | 'last';
label: string;
onClick: () => void;
}

ButtonGroup.Button = function ButtonGroupButton(props: ButtonGroupButtonProps) {
const { position, label, onClick } = props;

return (
<button
type="button"
onClick={onClick}
css={position === 'first' ? styles.firstButton : styles.secondButton}
>
{label}
</button>
);
};
1 change: 1 addition & 0 deletions src/components/button/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './Button';
export * from './ButtonGroup';

0 comments on commit 308048e

Please sign in to comment.