Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 69 additions & 31 deletions src/component/panels/PeaksPanel/PeaksPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import type { Info1D, Peak1D, Peaks } from '@zakodium/nmr-types';
import type { PeaksViewState, Spectrum1D } from '@zakodium/nmrium-core';
import { SvgNmrFt, SvgNmrPeaks, SvgNmrPeaksTopLabels } from 'cheminfo-font';
import { memo, useCallback, useMemo, useRef, useState } from 'react';
import { FaThinkPeaks } from 'react-icons/fa';
import { FaCopy, FaThinkPeaks } from 'react-icons/fa';

import isInRange from '../../../data/utilities/isInRange.js';
import { ClipboardFallbackModal } from '../../../utils/clipboard/clipboardComponents.tsx';
import { useClipboard } from '../../../utils/clipboard/clipboardHooks.ts';
import { useChartData } from '../../context/ChartContext.js';
import { useDispatch } from '../../context/DispatchContext.js';
import { usePreferences } from '../../context/PreferencesContext.js';
Expand All @@ -13,6 +15,7 @@ import { useAlert } from '../../elements/Alert.js';
import { useActiveSpectrumPeaksViewState } from '../../hooks/useActiveSpectrumPeaksViewState.js';
import { useFormatNumberByNucleus } from '../../hooks/useFormatNumberByNucleus.js';
import useSpectrum from '../../hooks/useSpectrum.js';
import { EditPeakShapeModal } from '../../modal/EditPeakShapeModal.tsx';
import { booleanToString } from '../../utility/booleanToString.js';
import type { FilterType } from '../../utility/filterType.js';
import { TablePanel } from '../extra/BasicPanelStyle.js';
Expand All @@ -22,7 +25,8 @@ import DefaultPanelHeader from '../header/DefaultPanelHeader.js';
import PreferencesHeader from '../header/PreferencesHeader.js';

import PeaksPreferences from './PeaksPreferences.js';
import PeaksTable from './PeaksTable.js';
import PeaksTable, { usePeaksTableColumns } from './PeaksTable.js';
import { exportPeaksToTSV } from './peaksToTSV.ts';

interface PeaksPanelInnerProps {
peaks: Peaks;
Expand All @@ -48,7 +52,9 @@ function PeaksPanelInner(props: PeaksPanelInnerProps) {
const toaster = useToaster();

const settingRef = useRef<SettingsRef | null>(null);

const { peak, tableColumns, setEditedPeak } = usePeaksTableColumns(activeTab);
const { rawWriteWithType, shouldFallback, cleanShouldFallback, text } =
useClipboard();
const yesHandler = useCallback(() => {
dispatch({ type: 'DELETE_PEAK', payload: {} });
}, [dispatch]);
Expand Down Expand Up @@ -126,6 +132,16 @@ function PeaksPanelInner(props: PeaksPanelInnerProps) {
const { showPeaks, displayingMode, showPeaksShapes, showPeaksSum } =
peaksViewState;

function handleExportPeaksToTSV(): void {
const tsv = exportPeaksToTSV(filteredPeaks, tableColumns);
void rawWriteWithType(tsv, 'text/plain').then(() =>
toaster.show({
message: 'Peaks copied to clipboard',
intent: 'success',
}),
);
}

const leftButtons: ToolbarItemProps[] = [
{
disabled,
Expand Down Expand Up @@ -161,38 +177,60 @@ function PeaksPanelInner(props: PeaksPanelInnerProps) {
onClick: toggleDisplayingMode,
active: displayingMode === 'spread',
},
{
disabled,
icon: <FaCopy />,
tooltip: `Copy as TSV`,
onClick: handleExportPeaksToTSV,
},
];

return (
<TablePanel isFlipped={isFlipped}>
{!isFlipped && (
<DefaultPanelHeader
total={total}
counter={filteredPeaks.length}
onDelete={handleDeleteAll}
deleteToolTip="Delete All Peaks"
onFilter={handleOnFilter}
filterToolTip={
filterIsActive ? 'Show all peaks' : 'Hide peaks out of view'
}
onSettingClick={settingsPanelHandler}
leftButtons={leftButtons}
/>
)}
{isFlipped && (
<PreferencesHeader
onSave={saveSettingHandler}
onClose={settingsPanelHandler}
/>
)}
<div className="inner-container">
{!isFlipped ? (
<PeaksTable data={filteredPeaks} info={info} activeTab={activeTab} />
) : (
<PeaksPreferences ref={settingRef} />
<>
<ClipboardFallbackModal
mode={shouldFallback}
onDismiss={cleanShouldFallback}
text={text}
label="Peaks"
/>
<EditPeakShapeModal
peak={peak}
onCloseDialog={() => setEditedPeak(undefined)}
/>
<TablePanel isFlipped={isFlipped}>
{!isFlipped && (
<DefaultPanelHeader
total={total}
counter={filteredPeaks.length}
onDelete={handleDeleteAll}
deleteToolTip="Delete All Peaks"
onFilter={handleOnFilter}
filterToolTip={
filterIsActive ? 'Show all peaks' : 'Hide peaks out of view'
}
onSettingClick={settingsPanelHandler}
leftButtons={leftButtons}
/>
)}
{isFlipped && (
<PreferencesHeader
onSave={saveSettingHandler}
onClose={settingsPanelHandler}
/>
)}
</div>
</TablePanel>
<div className="inner-container">
{!isFlipped ? (
<PeaksTable
data={filteredPeaks}
info={info}
tableColumns={tableColumns}
/>
) : (
<PeaksPreferences ref={settingRef} />
)}
</div>
</TablePanel>
</>
);
}

Expand Down
52 changes: 25 additions & 27 deletions src/component/panels/PeaksPanel/PeaksTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,12 @@ import addCustomColumn, {
createActionColumn,
} from '../../elements/ReactTable/utility/addCustomColumn.js';
import { usePanelPreferences } from '../../hooks/usePanelPreferences.js';
import { EditPeakShapeModal } from '../../modal/EditPeakShapeModal.js';
import { formatNumber } from '../../utility/formatNumber.js';
import { NoDataForFid } from '../extra/placeholder/NoDataForFid.js';

import type { PeakRecord } from './PeaksPanel.js';

interface PeaksTableProps {
activeTab: string;
data: PeakRecord[];
info: Info1D;
}

function handleActiveRow(row: Row<PeakRecord>) {
return row.original.isConstantlyHighlighted;
}

function PeaksTable(props: PeaksTableProps) {
const { activeTab, data, info } = props;
export function usePeaksTableColumns(activeTab: string) {
const dispatch = useDispatch();
const peaksPreferences = usePanelPreferences('peaks', activeTab);
const [peak, setEditedPeak] = useState<Peak1D | undefined>();
Expand Down Expand Up @@ -202,6 +190,22 @@ function PeaksTable(props: PeaksTableProps) {
return columns;
}, [COLUMNS, peaksPreferences]);

return { tableColumns, peak, setEditedPeak };
}

interface PeaksTableProps {
tableColumns: Array<ControlCustomColumn<PeakRecord>>;
data: PeakRecord[];
info: Info1D;
}

function handleActiveRow(row: Row<PeakRecord>) {
return row.original.isConstantlyHighlighted;
}

function PeaksTable(props: PeaksTableProps) {
const { tableColumns, data, info } = props;

if (info?.isFid) {
return <NoDataForFid />;
}
Expand All @@ -211,20 +215,14 @@ function PeaksTable(props: PeaksTableProps) {
}

return (
<>
<EditPeakShapeModal
peak={peak}
onCloseDialog={() => setEditedPeak(undefined)}
/>
<ReactTable
activeRow={handleActiveRow}
rowStyle={{ activated: { backgroundColor: '#f5f5dc' } }}
data={data}
columns={tableColumns}
approxItemHeight={24}
enableVirtualScroll
/>
</>
<ReactTable
activeRow={handleActiveRow}
rowStyle={{ activated: { backgroundColor: '#f5f5dc' } }}
data={data}
columns={tableColumns}
approxItemHeight={24}
enableVirtualScroll
/>
);
}

Expand Down
45 changes: 45 additions & 0 deletions src/component/panels/PeaksPanel/peaksToTSV.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import dlv from 'dlv';

import type { ControlCustomColumn } from '../../elements/ReactTable/utility/addCustomColumn.tsx';

import type { PeakRecord } from './PeaksPanel.tsx';

export function exportPeaksToTSV(
data: PeakRecord[],
tableColumns: Array<ControlCustomColumn<PeakRecord>>,
) {
const exportColumns = tableColumns.filter(
(col) => col.Header && typeof col.Header === 'string',
);

const headers: string[] = [];
for (const col of exportColumns) {
headers.push(col.Header as string);
}

const rows: string[] = [];
for (let i = 0; i < data.length; i++) {
const record = data[i];
const cells: string[] = [];
for (const col of exportColumns) {
const accessor = col.accessor;
if (typeof accessor === 'string') {
cells.push(String(dlv(record, accessor) ?? ''));
} else if (typeof accessor === 'function') {
cells.push(
String(
accessor(record, i, {
subRows: [],
depth: 0,
data: [],
}) ?? '',
),
);
} else {
cells.push('');
}
}
rows.push(cells.join('\t'));
}
return `${headers.join('\t')}\n${rows.join('\n')}`;
}
Loading