mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-17 16:50:53 +00:00
Enhancement: implement Shift+click and Ctrl+click row selection in tables for improved user interaction
Some checks are pending
CI Pipeline / prepare (push) Waiting to run
CI Pipeline / docker (amd64, ubuntu-24.04) (push) Blocked by required conditions
CI Pipeline / docker (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
CI Pipeline / create-manifest (push) Blocked by required conditions
Build and Push Multi-Arch Docker Image / build-and-push (push) Waiting to run
Frontend Tests / test (push) Waiting to run
Some checks are pending
CI Pipeline / prepare (push) Waiting to run
CI Pipeline / docker (amd64, ubuntu-24.04) (push) Blocked by required conditions
CI Pipeline / docker (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
CI Pipeline / create-manifest (push) Blocked by required conditions
Build and Push Multi-Arch Docker Image / build-and-push (push) Waiting to run
Frontend Tests / test (push) Waiting to run
This commit is contained in:
parent
821dccc380
commit
e0bb2d925e
4 changed files with 36 additions and 1 deletions
|
|
@ -41,6 +41,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- **`POST /api/channels/series-rules/preview/`** returns up to 25 (configurable, max 100) upcoming programs that a candidate rule would match within the standard 7-day evaluation horizon, without persisting anything. Used by the new rule editor to give live feedback as the user types.
|
||||
- **`GET /api/epg/programs/search/?tvg_id=`** filter parameter for exact `epg.tvg_id` matches.
|
||||
- **Series rule editor modal** with form (title + match mode, description + match mode, episodes mode, pinned channel) and a debounced (500ms) preview pane backed by an `AbortController` so per-keystroke calls don't pile up. A "Customize rule..." link in the program record-choice modal opens the editor pre-filled with the program's `tvg_id` and title; the series rules modal gains an "Add rule" button and an "Edit" button per existing rule.
|
||||
- **Shift+click and Ctrl+click row selection in tables.** Clicking anywhere on a non-interactive area of a row now participates in selection:
|
||||
- **Shift+click**: extends selection from the last-clicked row to the current row (range select), identical to shift+clicking the checkbox.
|
||||
- **Ctrl+click** (Cmd+click on Mac): toggles the clicked row in or out of the current selection without disturbing other selected rows.
|
||||
- Plain clicks on action buttons, checkboxes, inputs, links, and menus are unaffected. The expand chevron uses `stopPropagation` so expanding a row does not also trigger selection.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ const CustomTable = ({ table }) => {
|
|||
tableCellProps={table.tableCellProps}
|
||||
enableDragDrop={table.enableDragDrop}
|
||||
selectedTableIdsSet={table.selectedTableIdsSet}
|
||||
handleRowClickRef={table.handleRowClickRef}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const MemoizedTableRow = React.memo(
|
|||
isSelected,
|
||||
renderBodyCellRef,
|
||||
expandedRowRendererRef,
|
||||
handleRowClickRef,
|
||||
getRowStyles,
|
||||
tableCellProps,
|
||||
enableDragDrop,
|
||||
|
|
@ -38,6 +39,7 @@ const MemoizedTableRow = React.memo(
|
|||
onMouseDown={(e) => {
|
||||
if (e.shiftKey) e.preventDefault();
|
||||
}}
|
||||
onClick={(e) => handleRowClickRef?.current?.(row.original.id, e)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
|
|
@ -101,6 +103,7 @@ const CustomTableBody = ({
|
|||
tableCellProps,
|
||||
enableDragDrop = false,
|
||||
selectedTableIdsSet,
|
||||
handleRowClickRef,
|
||||
}) => {
|
||||
// Store callbacks in refs so memoized rows always access the latest versions
|
||||
// without the function references themselves triggering re-renders.
|
||||
|
|
@ -127,6 +130,7 @@ const CustomTableBody = ({
|
|||
}
|
||||
renderBodyCellRef={renderBodyCellRef}
|
||||
expandedRowRendererRef={expandedRowRendererRef}
|
||||
handleRowClickRef={handleRowClickRef}
|
||||
getRowStyles={getRowStyles}
|
||||
tableCellProps={tableCellProps}
|
||||
enableDragDrop={enableDragDrop}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const useTable = ({
|
|||
lastClickedIdRef.current = lastClickedId;
|
||||
const allRowIdsRef = useRef(allRowIds);
|
||||
allRowIdsRef.current = allRowIds;
|
||||
const handleRowClickRef = useRef(null);
|
||||
|
||||
// Use shared table preferences hook
|
||||
const { headerPinned, setHeaderPinned, tableSize, setTableSize } =
|
||||
|
|
@ -171,6 +172,29 @@ const useTable = ({
|
|||
return true; // Return true to indicate we've handled it
|
||||
};
|
||||
|
||||
const handleRowClick = (rowId, e) => {
|
||||
if (
|
||||
e.target.closest(
|
||||
'button, a, input, select, textarea, [role="menuitem"], [role="option"], [role="button"]'
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (e.shiftKey) {
|
||||
handleShiftSelect(rowId, true);
|
||||
} else if (e.ctrlKey || e.metaKey) {
|
||||
const newSet = new Set(selectedTableIdsRef.current);
|
||||
if (newSet.has(rowId)) {
|
||||
newSet.delete(rowId);
|
||||
} else {
|
||||
newSet.add(rowId);
|
||||
setLastClickedId(rowId);
|
||||
}
|
||||
updateSelectedTableIds([...newSet]);
|
||||
}
|
||||
};
|
||||
handleRowClickRef.current = handleRowClick;
|
||||
|
||||
const renderBodyCell = ({ row, cell }) => {
|
||||
if (bodyCellRenderFns[cell.column.id]) {
|
||||
return bodyCellRenderFns[cell.column.id]({ row, cell });
|
||||
|
|
@ -209,7 +233,8 @@ const useTable = ({
|
|||
return (
|
||||
<Center
|
||||
style={{ width: '100%', cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRowExpansion(row);
|
||||
}}
|
||||
>
|
||||
|
|
@ -239,6 +264,7 @@ const useTable = ({
|
|||
expandedRowIds,
|
||||
expandedRowRenderer,
|
||||
setSelectedTableIds,
|
||||
handleRowClickRef,
|
||||
headerPinned,
|
||||
setHeaderPinned,
|
||||
tableSize,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue