Skip to content

Commit 3e42c49

Browse files
committed
handle more advanced shifting of columns
1 parent 1f30c6d commit 3e42c49

File tree

5 files changed

+130
-14
lines changed

5 files changed

+130
-14
lines changed

compose/neurosynth-frontend/src/pages/Annotations/components/EditAnnotationsHotTable.tsx

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { useGetWindowHeight, useUpdateAnnotationById } from 'hooks';
1313
import useUserCanEdit from 'hooks/useUserCanEdit';
1414
import { useSnackbar } from 'notistack';
1515
import { useProjectUser } from 'pages/Project/store/ProjectStore';
16-
import React, { useEffect, useRef } from 'react';
16+
import React, { useEffect, useMemo, useRef } from 'react';
1717
import { createColumns, hotDataToAnnotationNotes, hotSettings } from './EditAnnotationsHotTable.helpers';
1818

1919
registerAllModules();
@@ -39,6 +39,11 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro
3939
rowHeights,
4040
} = useEditAnnotationsHotTable(props.annotationId, !canEdit);
4141

42+
const manualColumnMoveOrder = useMemo(() => {
43+
const totalCols = 2 + (noteKeys?.length || 0);
44+
return canEdit ? Array.from({ length: totalCols }, (_v, idx) => idx) : false;
45+
}, [noteKeys?.length, canEdit]);
46+
4247
useEffect(() => {
4348
const timeout: any = setTimeout(() => {
4449
if (!hotTableRef.current?.hotInstance) return;
@@ -151,18 +156,49 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro
151156
return updated;
152157
};
153158

154-
const handleColumnMove = (movedColumns: number[], finalIndex: number) => {
159+
const handleColumnMove = (
160+
movedColumns: number[],
161+
finalIndex: number,
162+
_dropIndex?: number,
163+
movePossible?: boolean,
164+
orderOfColumns?: number[]
165+
) => {
155166
if (!canEdit) return;
156167
if (!movedColumns.length) return;
168+
if (movePossible === false) return;
157169
const fromVisualIndex = movedColumns[0];
158170
const toVisualIndex = finalIndex;
159171

160172
if (fromVisualIndex < 2 || toVisualIndex < 2) return; // lock study/analysis columns
161173

162-
const from = fromVisualIndex - 2;
163-
let to = toVisualIndex - 2;
164-
165174
setAnnotationsHotState((prev) => {
175+
if (!prev.noteKeys?.length) return prev;
176+
177+
// If Handsontable provides the full order, use it to derive shifted positions
178+
if (Array.isArray(orderOfColumns) && orderOfColumns.length >= 2) {
179+
const noteOrder = orderOfColumns.filter((colIdx) => colIdx >= 2).map((colIdx) => colIdx - 2);
180+
const updatedNoteKeys = noteOrder.map((nkIdx, idx) => ({
181+
...prev.noteKeys[nkIdx],
182+
order: idx,
183+
}));
184+
185+
const updatedHotData = prev.hotData.map((row) => {
186+
const meta = row.slice(0, 2);
187+
const noteVals = orderOfColumns.filter((colIdx) => colIdx >= 2).map((colIdx) => row[colIdx]);
188+
return [...meta, ...noteVals];
189+
});
190+
191+
return {
192+
...prev,
193+
isEdited: true,
194+
noteKeys: updatedNoteKeys,
195+
hotColumns: createColumns(updatedNoteKeys, !canEdit),
196+
hotData: updatedHotData,
197+
};
198+
}
199+
200+
const from = fromVisualIndex - 2;
201+
let to = toVisualIndex - 2;
166202
if (from >= prev.noteKeys.length) return prev;
167203
if (to >= prev.noteKeys.length) to = prev.noteKeys.length - 1;
168204

@@ -185,8 +221,13 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro
185221
hotData: updatedHotData,
186222
};
187223
});
224+
};
188225

189-
// Avoid leaving drag artifacts; Handsontable plugin manages its own state
226+
const handleBeforeColumnMove = (movedColumns: number[], finalIndex: number) => {
227+
if (!canEdit) return true;
228+
if (!movedColumns.length) return true;
229+
if (movedColumns[0] < 2 || finalIndex < 2) return false; // prevent mixing with fixed cols
230+
return true;
190231
};
191232

192233
/**
@@ -207,6 +248,15 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro
207248
TD: HTMLTableCellElement,
208249
controller: SelectionController
209250
): void => {
251+
if (coords.row < 0) {
252+
const target = event.target as HTMLElement;
253+
const isDragHandle = target?.closest('[data-drag-handle="true"]');
254+
if (isDragHandle) {
255+
event.stopImmediatePropagation();
256+
event.preventDefault();
257+
return;
258+
}
259+
}
210260
const isRowHeader = coords.col === -1 || coords.col === 0;
211261
if (isRowHeader) {
212262
event.stopImmediatePropagation();
@@ -296,7 +346,8 @@ const AnnotationsHotTable: React.FC<{ annotationId?: string }> = React.memo((pro
296346
mergeCells={mergeCells}
297347
disableVisualSelection={!canEdit}
298348
colHeaders={hotColumnHeaders}
299-
manualColumnMove={canEdit}
349+
manualColumnMove={manualColumnMoveOrder}
350+
beforeColumnMove={handleBeforeColumnMove}
300351
colWidths={colWidths}
301352
rowHeights={rowHeights}
302353
columns={hotColumns}

compose/neurosynth-frontend/src/pages/Study/components/EditStudyAnnotationsHotTable.tsx

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
useCreateAnnotationColumn,
1818
useRemoveAnnotationColumn,
1919
useReorderAnnotationColumns,
20+
useReorderAnnotationColumnsByOrder,
2021
useUpdateAnnotationNotes,
2122
} from 'stores/AnnotationStore.actions';
2223

@@ -29,6 +30,7 @@ const EditStudyAnnotationsHotTable: React.FC<{ readonly?: boolean }> = ({ readon
2930
const createAnnotationColumn = useCreateAnnotationColumn();
3031
const removeAnnotationColumn = useRemoveAnnotationColumn();
3132
const reorderAnnotationColumns = useReorderAnnotationColumns();
33+
const reorderAnnotationColumnsByOrder = useReorderAnnotationColumnsByOrder();
3234
const { colWidths, colHeaders, columns, hiddenRows, data } = useEditStudyAnnotationsHotTable(readonly);
3335
const projectUser = useProjectUser();
3436
const canEdit = useUserCanEdit(projectUser || undefined);
@@ -105,29 +107,63 @@ const EditStudyAnnotationsHotTable: React.FC<{ readonly?: boolean }> = ({ readon
105107
}
106108
};
107109

108-
const handleColumnMove = (movedColumns: number[], finalIndex: number) => {
110+
const handleColumnMove = (
111+
movedColumns: number[],
112+
finalIndex: number,
113+
_dropIndex?: number,
114+
movePossible?: boolean,
115+
orderOfColumns?: number[]
116+
) => {
109117
if (readonly || !canEdit) return;
110118
if (!movedColumns.length) return;
119+
if (movePossible === false) return;
111120

112121
const fromVisualIndex = movedColumns[0];
113122
const toVisualIndex = finalIndex;
114123

115124
// first two columns are static (analysis name/description)
116125
if (fromVisualIndex < 2 || toVisualIndex < 2) return;
117126

118-
const from = fromVisualIndex - 2;
119-
let to = toVisualIndex - 2;
120-
121127
if (!noteKeys) return;
122-
if (to >= noteKeys.length) to = noteKeys.length - 1;
128+
if (Array.isArray(orderOfColumns) && orderOfColumns.length >= 2) {
129+
const noteOrder = orderOfColumns.filter((colIdx) => colIdx >= 2).map((colIdx) => colIdx - 2);
130+
if (!noteOrder.length) return;
131+
reorderAnnotationColumnsByOrder(noteOrder);
132+
} else {
133+
const from = fromVisualIndex - 2;
134+
let to = toVisualIndex - 2;
135+
if (to >= noteKeys.length) to = noteKeys.length - 1;
136+
reorderAnnotationColumns(from, to);
137+
}
138+
};
123139

124-
reorderAnnotationColumns(from, to);
140+
const handleBeforeColumnMove = (movedColumns: number[], finalIndex: number) => {
141+
if (readonly || !canEdit) return true;
142+
if (!movedColumns.length) return true;
143+
if (movedColumns[0] < 2 || finalIndex < 2) return false;
144+
return true;
125145
};
126146

127147
const memoizedData = useMemo(() => {
128148
return JSON.parse(JSON.stringify(data || []));
129149
}, [data]);
130150

151+
const handleBeforeOnCellMouseDown = (
152+
event: MouseEvent,
153+
coords: CellCoords,
154+
TD: HTMLTableCellElement
155+
) => {
156+
if (coords.row < 0) {
157+
const target = event.target as HTMLElement;
158+
const isDragHandle = target?.closest('[data-drag-handle="true"]');
159+
if (isDragHandle) {
160+
event.stopImmediatePropagation();
161+
event.preventDefault();
162+
return;
163+
}
164+
}
165+
};
166+
131167
return (
132168
<Box>
133169
{canEdit && !readonly && (
@@ -173,8 +209,10 @@ const EditStudyAnnotationsHotTable: React.FC<{ readonly?: boolean }> = ({ readon
173209
rows: hiddenRows,
174210
indicators: false,
175211
}}
212+
beforeOnCellMouseDown={handleBeforeOnCellMouseDown}
176213
afterOnCellMouseUp={handleCellMouseUp}
177-
manualColumnMove={!readonly && canEdit}
214+
manualColumnMove={!readonly && canEdit ? Array.from({ length: 2 + (noteKeys?.length || 0) }, (_v, idx) => idx) : false}
215+
beforeColumnMove={handleBeforeColumnMove}
178216
afterColumnMove={handleColumnMove}
179217
colWidths={colWidths}
180218
columns={columns}

compose/neurosynth-frontend/src/stores/AnnotationStore.actions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ export const useCreateAnnotationNote = () => useAnnotationStore((state) => state
2121
export const useDeleteAnnotationNote = () => useAnnotationStore((state) => state.deleteAnnotationNote);
2222
export const useUpdateAnnotationNoteName = () => useAnnotationStore((state) => state.updateAnnotationNoteName);
2323
export const useReorderAnnotationColumns = () => useAnnotationStore((state) => state.reorderAnnotationColumns);
24+
export const useReorderAnnotationColumnsByOrder = () =>
25+
useAnnotationStore((state) => state.reorderAnnotationColumnsByOrder);

compose/neurosynth-frontend/src/stores/AnnotationStore.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,30 @@ export const useAnnotationStore = create<
240240
};
241241
});
242242
},
243+
reorderAnnotationColumnsByOrder: (order) => {
244+
setUnloadHandler('annotation');
245+
set((state) => {
246+
if (!state.annotation.note_keys || !order.length) return state;
247+
const ordered = order
248+
.map((idx) => state.annotation.note_keys?.[idx])
249+
.filter((x): x is NoteKeyType => !!x)
250+
.map((noteKey, idx) => ({ ...noteKey, order: idx }));
251+
252+
if (!ordered.length) return state;
253+
254+
return {
255+
...state,
256+
annotation: {
257+
...state.annotation,
258+
note_keys: ordered,
259+
},
260+
storeMetadata: {
261+
...state.storeMetadata,
262+
annotationIsEdited: true,
263+
},
264+
};
265+
});
266+
},
243267
createAnnotationNote: (analysisId, studyId, analysisName) => {
244268
setUnloadHandler('annotation');
245269
set((state) => {

compose/neurosynth-frontend/src/stores/AnnotationStore.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export type AnnotationStoreActions = {
2929
updateAnnotationNoteName: (analysis: Partial<IStoreNoteCollectionReturn>) => void;
3030
removeAnnotationColumn: (noteKey: string) => void;
3131
reorderAnnotationColumns: (fromIndex: number, toIndex: number) => void;
32+
reorderAnnotationColumnsByOrder: (order: number[]) => void;
3233
};
3334

3435
export type AnnotationNoteType = {

0 commit comments

Comments
 (0)