From ccfc0f16400c2a406f1fbe46a992f2a64095762f Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Tue, 9 May 2023 21:36:01 -0700 Subject: [PATCH 01/55] Refactor Table with resizable columns --- .../src/lib/component/core/table/Table.svelte | 328 ++++++++---------- 1 file changed, 149 insertions(+), 179 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 38787e04d..417c70d58 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -47,6 +47,9 @@ formatter: 'icon' }).then((newSchema) => { schema.set(newSchema); + if (columnWidths.length === 0) { + columnWidths = Array($schema.columns.length).fill(100); + } }); $: fetchChunk({ @@ -59,25 +62,23 @@ chunk.set(newChunk); }); - export let columnWidths = Array.apply(null, Array($schema.columns.length)).map((x, i) => 256); + let columnWidths: Array = []; let columnUnit: string = 'px'; let resizeProps = { colBeingResized: -1, x: 0, - wLeft: 0, - wRight: 0, + width: 0, dx: 0 }; let resizeMethods = { mousedown(colIndex: number) { return (e: MouseEvent) => { - // Update all the resize props + // Store the current state in the resize props resizeProps.colBeingResized = colIndex; resizeProps.x = e.clientX; - resizeProps.wLeft = columnWidths[colIndex]; - resizeProps.wRight = columnWidths[colIndex + 1]; + resizeProps.width = columnWidths[colIndex]; // Attach listeners for events window.addEventListener('mousemove', resizeMethods.mousemove); @@ -92,9 +93,9 @@ resizeProps.dx = e.clientX - resizeProps.x; // Update the width of column - if (resizeProps.wLeft + resizeProps.dx > 164 && resizeProps.wRight - resizeProps.dx > 164) { - columnWidths[resizeProps.colBeingResized] = resizeProps.wLeft + resizeProps.dx; - columnWidths[resizeProps.colBeingResized + 1] = resizeProps.wRight - resizeProps.dx; + const newWidth = resizeProps.width + resizeProps.dx; + if (newWidth > 5) { + columnWidths[resizeProps.colBeingResized] = newWidth; } }, @@ -104,193 +105,162 @@ } }; - let tableWidth: number; - onMount(async () => { - columnWidths = Array.apply(null, Array($schema.columns.length)).map( - (x, i) => tableWidth / $schema.columns.length - ); - columnUnit = 'px'; - }); + function onRowClick(e, keyidx) { + let dispatchSelect = true; + if (e.shiftKey) { + if (selected.length === 0) { + selected.push(keyidx); + dispatchSelect = false; + } else { + let lastIdx = selected[selected.length - 1]; + let lasti = $chunk.keyidxs.indexOf(lastIdx); + let i = $chunk.keyidxs.indexOf(keyidx); + if (i > lasti) { + for (let j = lasti; j <= i; j++) { + if (!selected.includes($chunk.keyidxs[j])) { + selected.push($chunk.keyidxs[j]); + } + } + } else { + for (let j = lasti; j >= i; j--) { + if (!selected.includes($chunk.keyidxs[j])) { + selected.push($chunk.keyidxs[j]); + } + } + } + } + } else if (e.altKey) { + selected = []; + selected.push(keyidx); + } else { + if (selected.includes(keyidx)) { + selected = without(selected, keyidx); + } else if (!selected.includes(keyidx)) { + if (singleSelect) { + selected.pop(); + } + selected.push(keyidx); + } + } + selected = selected; + if (dispatchSelect) { + console.log('dispatching onSelect', selected); + dispatch(onSelect.endpointId, { detail: { selected: selected } }); + } + } -
-
-
-
- -
- {#each $schema.columns as column, col_index} -
- -
- {#if column.name === $schema.primaryKey} - - - {:else} - - {/if} +
+ +
+ -
{column.name}
-
- -
+ +
+ + {#each $schema.columns as column, col_index} +
+ +
+
+ {#if column.name === $schema.primaryKey} + + + {:else} + + {/if}
- {/each} +
{column.name}
+
+ +
{ + columnWidths[col_index] = 100; + }} + > +
+
+
-
+ {/each} -
- {#each zip($chunk.keyidxs, $chunk.posidxs) as [keyidx, posidx], rowi} -
-
- +
+ + + {#each $chunk.columnInfos as col} +
+ { + console.log(keyidx); + dispatch(onEdit.endpointId, { + detail: { + column: col.name, + keyidx: keyidx, + posidx: posidx, + value: e.detail.value } - }} - > - {posidx} - -
- {#each $chunk.columnInfos as col} -
- { - console.log(keyidx); - dispatch(onEdit.endpointId, { - detail: { - column: col.name, - keyidx: keyidx, - posidx: posidx, - value: e.detail.value - } - }); - }} - /> -
- {/each} + }); + }} + />
{/each} -
+ {/each}
-
- -
-
- {#if selected.length > 0} - {#if selected.length === 1} - - {:else} - - {/if} -
{selected.length} Selected
- {/if} -
-
- -
- + +
+
+ {#if selected.length > 0} + {#if selected.length === 1} + + {:else} + + {/if} +
{selected.length} Selected
+ {/if}
- -
- -
+
From c416d962ffaf9e402abe03fd24eb48410a4d9238 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Wed, 10 May 2023 09:27:10 -0700 Subject: [PATCH 02/55] Make rows resizable --- .../src/lib/component/core/table/Table.svelte | 82 +++++++++++++------ 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 417c70d58..785a7e43a 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -23,6 +23,7 @@ export let onSelect: Endpoint; export let classes: string = 'h-fit'; + let wrap: string = 'clip'; // 'wrap' | 'clip' (add 'overflow' later) // Setup row modal const open_row_modal = (posidx: number) => { @@ -60,25 +61,32 @@ }).then((newChunk) => { console.log('here'); chunk.set(newChunk); + if (rowHeights.length === 0) { + rowHeights = Array($chunk.keyidxs.length).fill(20); // same as text-sm + } }); let columnWidths: Array = []; let columnUnit: string = 'px'; + let rowHeights: Array = []; + let rowUnit: string = 'px'; let resizeProps = { - colBeingResized: -1, - x: 0, - width: 0, - dx: 0 + direction: 'x', + idxBeingResized: -1, + mouseStart: 0, + sizeStart: 0, + offset: 0 }; let resizeMethods = { - mousedown(colIndex: number) { + mousedown(direction: string, idx: number) { return (e: MouseEvent) => { // Store the current state in the resize props - resizeProps.colBeingResized = colIndex; - resizeProps.x = e.clientX; - resizeProps.width = columnWidths[colIndex]; + resizeProps.direction = direction; + resizeProps.idxBeingResized = idx; + resizeProps.mouseStart = direction === 'x' ? e.clientX : e.clientY; + resizeProps.sizeStart = direction === 'x' ? columnWidths[idx] : rowHeights[idx]; // Attach listeners for events window.addEventListener('mousemove', resizeMethods.mousemove); @@ -87,15 +95,17 @@ }, mousemove(e: MouseEvent) { - if (resizeProps.colBeingResized === -1) return; + if (resizeProps.idxBeingResized === -1) return; // Determine how far the mouse has been moved - resizeProps.dx = e.clientX - resizeProps.x; + resizeProps.offset = + (resizeProps.direction === 'x' ? e.clientX : e.clientY) - resizeProps.mouseStart; - // Update the width of column - const newWidth = resizeProps.width + resizeProps.dx; - if (newWidth > 5) { - columnWidths[resizeProps.colBeingResized] = newWidth; + // Update the size + const newSize = resizeProps.sizeStart + resizeProps.offset; + if (newSize > 5) { + if (resizeProps.direction === 'x') columnWidths[resizeProps.idxBeingResized] = newSize; + else rowHeights[resizeProps.idxBeingResized] = newSize; } }, @@ -105,7 +115,7 @@ } }; - function onRowClick(e, keyidx) { + function onRowClick(e: MouseEvent, keyidx: string) { let dispatchSelect = true; if (e.shiftKey) { if (selected.length === 0) { @@ -154,8 +164,9 @@
@@ -163,10 +174,7 @@
{#each $schema.columns as column, col_index} -
+
@@ -184,17 +192,19 @@
{column.name}
+
{ columnWidths[col_index] = 100; }} > -
-
+
+
+
+
{/each} @@ -203,7 +213,7 @@ {#each zip($chunk.keyidxs, $chunk.posidxs) as [keyidx, posidx], rowi} -
+
+ + +
{ + rowHeights[posidx] = 20; + }} + > +
+
+
+
+
@@ -255,6 +279,10 @@ {/if}
+
+ #TODO Wrap: {wrap} +
+
From 1c488dc82a7d83bd0ddf7d78a7ac947379a3f70f Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Wed, 10 May 2023 11:08:52 -0700 Subject: [PATCH 03/55] Work on styling --- .../src/lib/component/core/table/Table.svelte | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 785a7e43a..5a4c0825f 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -62,7 +62,7 @@ console.log('here'); chunk.set(newChunk); if (rowHeights.length === 0) { - rowHeights = Array($chunk.keyidxs.length).fill(20); // same as text-sm + rowHeights = Array($chunk.keyidxs.length).fill(22); // same as text-sm + 2 } }); @@ -160,21 +160,25 @@ } - -
- + +
+
-
+
{#each $schema.columns as column, col_index} -
+
@@ -195,7 +199,7 @@
{ columnWidths[col_index] = 100; @@ -203,7 +207,6 @@ >
-
@@ -213,7 +216,7 @@ {#each zip($chunk.keyidxs, $chunk.posidxs) as [keyidx, posidx], rowi} -
+
+
{#each zip($chunk.keyidxs, $chunk.posidxs) as [keyidx, posidx], rowi} - -
+ +
@@ -256,10 +404,25 @@ {#each $chunk.columnInfos as col} -
+
{ + onClickCell(e, col.name, keyidx); + document.getSelection().removeAllRanges(); + }} + on:keydown={(e) => console.log('keydown', e)} + > { console.log(keyidx); dispatch(onEdit.endpointId, { @@ -279,7 +442,7 @@
@@ -288,26 +451,26 @@ class="flex justify-between h-8 z-10 bg-slate-100 px-5 rounded-b-sm border-t border-t-slate-300" >
- {#if selected.length > 0} - {#if selected.length === 1} + {#if selectedRows.length > 0} + {#if selectedRows.length === 1} {:else} {/if} -
{selected.length} Selected
+
{selectedRows.length} rows selected
{/if}
-
+
diff --git a/meerkat/interactive/app/src/lib/component/core/table/__init__.py b/meerkat/interactive/app/src/lib/component/core/table/__init__.py index eee650179..a2dc3f0d3 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/__init__.py +++ b/meerkat/interactive/app/src/lib/component/core/table/__init__.py @@ -36,7 +36,9 @@ class Table(Component): classes: str = "h-fit" on_edit: EndpointProperty[OnEditInterface] = None - on_select: EndpointProperty[OnSelectTable] = None + on_select_cells: EndpointProperty[OnSelectTable] = None + on_select_cols: EndpointProperty[OnSelectTable] = None + on_select_rows: EndpointProperty[OnSelectTable] = None def __init__( self, @@ -46,7 +48,9 @@ def __init__( single_select: bool = False, classes: str = "h-fit", on_edit: EndpointProperty = None, - on_select: EndpointProperty = None + on_select_cells: EndpointProperty = None, + on_select_cols: EndpointProperty = None, + on_select_rows: EndpointProperty = None ): """Table view of a DataFrame. @@ -64,7 +68,9 @@ def __init__( single_select=single_select, classes=classes, on_edit=on_edit, - on_select=on_select, + on_select_cells=on_select_cells, + on_select_cols=on_select_cols, + on_select_rows=on_select_rows, ) def _get_ipython_height(self): From c23352a985be1fb5f9a0bcd2caae3696c0102919 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Wed, 10 May 2023 23:24:07 -0700 Subject: [PATCH 06/55] Work on drag select --- .../src/lib/component/core/table/Table.svelte | 109 ++++++++++++++++-- 1 file changed, 98 insertions(+), 11 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index d971090c9..9524ba57c 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -86,7 +86,7 @@ offset: 0 }; - let resizeMethods = { + const resizeMethods = { mousedown(direction: string, idx: number) { return (e: MouseEvent) => { // Store the current state in the resize props @@ -105,8 +105,7 @@ if (resizeProps.idxBeingResized === -1) return; // Determine how far the mouse has been moved - resizeProps.offset = - (resizeProps.direction === 'x' ? e.x : e.y) - resizeProps.mouseStart; + resizeProps.offset = (resizeProps.direction === 'x' ? e.x : e.y) - resizeProps.mouseStart; // Update the size const newSize = resizeProps.sizeStart + resizeProps.offset; @@ -122,6 +121,94 @@ } }; + const selectCellMethods = { + mousedown(colName: string, keyidx: string) { + return (e: MouseEvent) => { + if (e.shiftKey) { + selectedCells = []; + + // loop through all cells between primarySelectedCell and this cell + const col1 = $schema.columns.findIndex((c) => c.name === primarySelectedCell[0]); + const keyidx1 = $chunk.keyidxs.indexOf(primarySelectedCell[1]); + + const col2 = $schema.columns.findIndex((c) => c.name === colName); + const keyidx2 = $chunk.keyidxs.indexOf(keyidx); + + const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; + const [keyidxStart, keyidxEnd] = + keyidx1 < keyidx2 ? [keyidx1, keyidx2] : [keyidx2, keyidx1]; + for (let i = colStart; i <= colEnd; i++) { + for (let j = keyidxStart; j <= keyidxEnd; j++) { + selectedCells.push([$schema.columns[i].name, $chunk.keyidxs[j]]); + } + } + } else if (e.metaKey) { + if (selectedCells.length === 0 && primarySelectedCell.length !== 0) { + selectedCells = [primarySelectedCell]; + } + primarySelectedCell = [colName, keyidx]; + + const i = selectedCells.findIndex((cell) => cell[0] === colName && cell[1] === keyidx); + if (i !== -1) selectedCells.splice(i, 1); + else selectedCells.push([colName, keyidx]); + } else { + primarySelectedCell = [colName, keyidx]; + selectedCells = []; // don't add to selectedCells + selectedCols = []; + selectedRows = []; + } + selectedCells = selectedCells.slice(); // trigger update + + // Attach listeners for events + window.addEventListener('mousemove', selectCellMethods.mousemove); + window.addEventListener('mouseup', selectCellMethods.mouseup); + }; + }, + + mousemove(e: MouseEvent) { + const elements = document.elementsFromPoint(e.x, e.y); + // loop through elements to find the first cell + for (let i = 0; i < elements.length; i++) { + if (elements[i].classList.contains('cell')) { + const colName = elements[i].getAttribute('colName'); + const keyidx = elements[i].getAttribute('keyidx') || ''; + console.log(colName, keyidx); + console.log($chunk.keyidxs) + + selectedCells = []; + + // loop through all cells between primarySelectedCell and this cell + const col1 = $schema.columns.findIndex((c) => c.name === primarySelectedCell[0]); + const keyidx1 = $chunk.keyidxs.indexOf(primarySelectedCell[1]); + + const col2 = $schema.columns.findIndex((c) => c.name === colName); + const keyidx2 = $chunk.keyidxs.indexOf(keyidx); + console.log(col2, keyidx2) + + const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; + const [keyidxStart, keyidxEnd] = + keyidx1 < keyidx2 ? [keyidx1, keyidx2] : [keyidx2, keyidx1]; + for (let i = colStart; i <= colEnd; i++) { + for (let j = keyidxStart; j <= keyidxEnd; j++) { + selectedCells.push([$schema.columns[i].name, $chunk.keyidxs[j]]); + } + } + break; + } + } + }, + + mouseup(e: MouseEvent) { + const s = selectedCells.length > 0 ? selectedCells : [primarySelectedCell]; + if (onSelectCells && onSelectCells.endpointId) { + dispatch(onSelectCells.endpointId, { detail: { selected: s } }); + } + + window.removeEventListener('mousemove', selectCellMethods.mousemove); + window.removeEventListener('mouseup', selectCellMethods.mouseup); + } + }; + function onClickCell(e: MouseEvent, colName: string, keyidx: string) { if (e.shiftKey) { selectedCells = []; @@ -149,8 +236,6 @@ selectedCells = []; // don't add to selectedCells selectedCols = []; selectedRows = []; - - } selectedCells = selectedCells.slice(); // trigger update @@ -404,8 +489,12 @@ {#each $chunk.columnInfos as col} +
{ - onClickCell(e, col.name, keyidx); - document.getSelection().removeAllRanges(); - }} - on:keydown={(e) => console.log('keydown', e)} + on:mousedown|preventDefault={selectCellMethods.mousedown(col.name, keyidx)} + colName={col.name} + keyidx={keyidx} > Date: Wed, 10 May 2023 23:28:42 -0700 Subject: [PATCH 07/55] Fix drag select --- .../app/src/lib/component/core/table/Table.svelte | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 9524ba57c..47c46ef88 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -172,8 +172,6 @@ if (elements[i].classList.contains('cell')) { const colName = elements[i].getAttribute('colName'); const keyidx = elements[i].getAttribute('keyidx') || ''; - console.log(colName, keyidx); - console.log($chunk.keyidxs) selectedCells = []; @@ -182,8 +180,7 @@ const keyidx1 = $chunk.keyidxs.indexOf(primarySelectedCell[1]); const col2 = $schema.columns.findIndex((c) => c.name === colName); - const keyidx2 = $chunk.keyidxs.indexOf(keyidx); - console.log(col2, keyidx2) + const keyidx2 = $chunk.keyidxs.findIndex((k) => k.toString() === keyidx); const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; const [keyidxStart, keyidxEnd] = @@ -505,7 +502,7 @@ )} on:mousedown|preventDefault={selectCellMethods.mousedown(col.name, keyidx)} colName={col.name} - keyidx={keyidx} + {keyidx} > Date: Wed, 10 May 2023 23:31:26 -0700 Subject: [PATCH 08/55] Remove old function --- .../src/lib/component/core/table/Table.svelte | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 47c46ef88..b007e7d92 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -206,41 +206,6 @@ } }; - function onClickCell(e: MouseEvent, colName: string, keyidx: string) { - if (e.shiftKey) { - selectedCells = []; - - // loop through all cells between primarySelectedCell and this cell - const col1 = $schema.columns.findIndex((c) => c.name === primarySelectedCell[0]); - const keyidx1 = $chunk.keyidxs.indexOf(primarySelectedCell[1]); - - const col2 = $schema.columns.findIndex((c) => c.name === colName); - const keyidx2 = $chunk.keyidxs.indexOf(keyidx); - - const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; - const [keyidxStart, keyidxEnd] = keyidx1 < keyidx2 ? [keyidx1, keyidx2] : [keyidx2, keyidx1]; - for (let i = colStart; i <= colEnd; i++) { - for (let j = keyidxStart; j <= keyidxEnd; j++) { - selectedCells.push([$schema.columns[i].name, $chunk.keyidxs[j]]); - } - } - } else if (e.metaKey) { - const i = selectedCells.findIndex((cell) => cell[0] === colName && cell[1] === keyidx); - if (i !== -1) selectedCells.splice(i, 1); - else selectedCells.push([colName, keyidx]); - } else { - primarySelectedCell = [colName, keyidx]; - selectedCells = []; // don't add to selectedCells - selectedCols = []; - selectedRows = []; - } - selectedCells = selectedCells.slice(); // trigger update - - if (onSelectCells && onSelectCells.endpointId) { - dispatch(onSelectCells.endpointId, { detail: { selected: selectedCells } }); - } - } - function onClickCol(e: MouseEvent, colName: string) { if (e.shiftKey) { if (selectedCols.length === 0) { From fcde831ae8cb9e2038bd05040646b28748c3b437 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Wed, 10 May 2023 23:39:02 -0700 Subject: [PATCH 09/55] Add better highlight colors --- .../app/src/lib/component/core/table/Table.svelte | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index b007e7d92..8b24d4807 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -293,13 +293,14 @@ selectedCols: Array, selectedRows: Array ) { + if (selectedCols.includes(colName)) + return 'bg-violet-700 text-white font-bold ' if ( primarySelectedCell[0] === colName || selectedCells.some((c) => c[0] === colName) || - selectedCols.includes(colName) || selectedRows.length > 0 ) - return 'bg-slate-200 text-violet-600 '; + return 'bg-violet-100 '; return ''; } @@ -310,13 +311,14 @@ selectedCols: Array, selectedRows: Array ) { + if (selectedRows.includes(keyidx)) + return 'bg-violet-700 text-white font-bold ' if ( primarySelectedCell[1] === keyidx || selectedCells.some((c) => c[1] === keyidx) || - selectedCols.length > 0 || - selectedRows.includes(keyidx) + selectedCols.length > 0 ) - return 'bg-slate-200 text-violet-600 '; + return 'bg-violet-100 '; return ''; } From 82bf5ddb60aa1ce1fc490c6adebc47c4e149e764 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Thu, 11 May 2023 23:25:10 -0700 Subject: [PATCH 10/55] Clean up Cell type, add arrow key support --- .../src/lib/component/core/table/Table.svelte | 393 +++++++++++++----- .../src/lib/component/core/table/__init__.py | 27 +- 2 files changed, 306 insertions(+), 114 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 8b24d4807..511d20046 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -3,7 +3,6 @@ import Pagination from '$lib/shared/pagination/Pagination.svelte'; import { fetchChunk, fetchSchema, dispatch } from '$lib/utils/api'; import { DataFrameChunk, type DataFrameRef, type DataFrameSchema } from '$lib/utils/dataframe'; - import { without } from 'underscore'; import { openModal } from 'svelte-modals'; import type { Endpoint } from '$lib/utils/types'; import { zip } from 'underscore'; @@ -11,6 +10,13 @@ import Cell from '$lib/shared/cell/Cell.svelte'; import { Check, CheckAll, KeyFill } from 'svelte-bootstrap-icons'; + type Cell = { + column: string; + keyidx: number; // TODO: decide if this should be number or string + posidx: number; + value: any; + }; + export let df: DataFrameRef; export let page: number = 0; @@ -18,10 +24,11 @@ export let onEdit: Endpoint; - export let primarySelectedCell: Array = []; - export let selectedCells: Array> = []; + export let primarySelectedCell: Cell = { column: '', keyidx: -1, posidx: -1, value: '' }; + export let secondarySelectedCell: Cell = { column: '', keyidx: -1, posidx: -1, value: '' }; + export let selectedCells: Array = []; export let selectedCols: Array = []; - export let selectedRows: Array = []; + export let selectedRows: Array = []; export let onSelectCells: Endpoint; export let onSelectCols: Endpoint; export let onSelectRows: Endpoint; @@ -58,6 +65,12 @@ if (columnWidths.length === 0) { columnWidths = Array(newSchema.columns.length).fill(100); } + if (primarySelectedCell.posidx === -1) { + primarySelectedCell = secondarySelectedCell = { + ...primarySelectedCell, + column: newSchema.columns[0].name + }; + } }); $: fetchChunk({ @@ -71,6 +84,14 @@ if (rowHeights.length === 0) { rowHeights = Array(newChunk.keyidxs.length).fill(22); // same as text-sm + 2 } + if (primarySelectedCell.posidx === -1) { + primarySelectedCell = secondarySelectedCell = { + ...primarySelectedCell, + keyidx: parseInt(newChunk.keyidxs[0]), + posidx: 0, + value: newChunk.getCell(0, $schema.columns[0].name).data + }; + } }); let columnWidths: Array = []; @@ -121,38 +142,90 @@ } }; + /** + * Helper function to convert a column name to the index of that column in + * the schema. + * @param col - The name of the column + */ + function col2idx(col: string) { + return $schema.columns.findIndex((c) => c.name === col); + } + + /** + * Helper function to convert a keyidx to the index of that keyidx in the + * chunk. This also corresponds to the posidx. + * @param keyidx - The keyidx of the row + */ + function keyidx2idx(keyidx: number) { + // TODO: figure out why we need to do k.toString(). It is a number when it should be a string + return $chunk.keyidxs.findIndex((k) => k.toString() === keyidx.toString()); + } + + /** + * Helper function to get the cell at a given column and posidx. + * @param col + * @param posidx + */ + function getCell(column: string, posidx: number) { + return { + column, + keyidx: parseInt($chunk.keyidxs[posidx]), + posidx, + value: $chunk.getCell(posidx, column).data + }; + } + + /** + * Loops through all cells between primarySelectedCell and + * secondarySelectedCell, adding them to the selectedCells array. + */ + function shiftSelect() { + // console.log('primarySelectedCell', primarySelectedCell); + // console.log('secondarySelectedCell', secondarySelectedCell); + selectedCells = []; + + const col1 = col2idx(primarySelectedCell.column); + const keyidx1 = keyidx2idx(primarySelectedCell.keyidx); + + const col2 = col2idx(secondarySelectedCell.column); + const keyidx2 = keyidx2idx(secondarySelectedCell.keyidx); + + const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; + const [keyidxStart, keyidxEnd] = keyidx1 < keyidx2 ? [keyidx1, keyidx2] : [keyidx2, keyidx1]; + + for (let i = colStart; i <= colEnd; i++) { + for (let j = keyidxStart; j <= keyidxEnd; j++) { + const column = $schema.columns[i].name; + const keyidx = parseInt($chunk.keyidxs[j]); + const posidx = j; + const value = $chunk.getCell(posidx, column).data; + selectedCells.push({ column, keyidx, posidx, value }); + } + } + + selectedCells = selectedCells.slice(); // trigger update + } + const selectCellMethods = { - mousedown(colName: string, keyidx: string) { + mousedown(cell: Cell) { return (e: MouseEvent) => { if (e.shiftKey) { - selectedCells = []; - - // loop through all cells between primarySelectedCell and this cell - const col1 = $schema.columns.findIndex((c) => c.name === primarySelectedCell[0]); - const keyidx1 = $chunk.keyidxs.indexOf(primarySelectedCell[1]); - - const col2 = $schema.columns.findIndex((c) => c.name === colName); - const keyidx2 = $chunk.keyidxs.indexOf(keyidx); - - const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; - const [keyidxStart, keyidxEnd] = - keyidx1 < keyidx2 ? [keyidx1, keyidx2] : [keyidx2, keyidx1]; - for (let i = colStart; i <= colEnd; i++) { - for (let j = keyidxStart; j <= keyidxEnd; j++) { - selectedCells.push([$schema.columns[i].name, $chunk.keyidxs[j]]); - } - } + secondarySelectedCell = cell; + shiftSelect(); } else if (e.metaKey) { - if (selectedCells.length === 0 && primarySelectedCell.length !== 0) { + if (selectedCells.length === 0 && primarySelectedCell.posidx !== -1) { selectedCells = [primarySelectedCell]; } - primarySelectedCell = [colName, keyidx]; + primarySelectedCell = secondarySelectedCell = cell; - const i = selectedCells.findIndex((cell) => cell[0] === colName && cell[1] === keyidx); + const i = selectedCells.findIndex( + (c) => c.column === cell.column && c.keyidx === cell.keyidx + ); if (i !== -1) selectedCells.splice(i, 1); - else selectedCells.push([colName, keyidx]); + // TODO: set new primarySelectedCell + else selectedCells.push(cell); } else { - primarySelectedCell = [colName, keyidx]; + primarySelectedCell = secondarySelectedCell = cell; selectedCells = []; // don't add to selectedCells selectedCols = []; selectedRows = []; @@ -166,32 +239,24 @@ }, mousemove(e: MouseEvent) { - const elements = document.elementsFromPoint(e.x, e.y); // loop through elements to find the first cell - for (let i = 0; i < elements.length; i++) { - if (elements[i].classList.contains('cell')) { - const colName = elements[i].getAttribute('colName'); - const keyidx = elements[i].getAttribute('keyidx') || ''; - - selectedCells = []; - - // loop through all cells between primarySelectedCell and this cell - const col1 = $schema.columns.findIndex((c) => c.name === primarySelectedCell[0]); - const keyidx1 = $chunk.keyidxs.indexOf(primarySelectedCell[1]); - - const col2 = $schema.columns.findIndex((c) => c.name === colName); - const keyidx2 = $chunk.keyidxs.findIndex((k) => k.toString() === keyidx); - - const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; - const [keyidxStart, keyidxEnd] = - keyidx1 < keyidx2 ? [keyidx1, keyidx2] : [keyidx2, keyidx1]; - for (let i = colStart; i <= colEnd; i++) { - for (let j = keyidxStart; j <= keyidxEnd; j++) { - selectedCells.push([$schema.columns[i].name, $chunk.keyidxs[j]]); - } - } - break; + for (const element of document.elementsFromPoint(e.x, e.y)) { + if (!element.classList.contains('cell')) { + continue; } + const column = element.getAttribute('column') || ''; + const keyidx = parseInt(element.getAttribute('keyidx') || '-1'); + + if (column === primarySelectedCell.column && keyidx === primarySelectedCell.keyidx) { + // same cell as primarySelectedCell + return; + } + + const posidx = keyidx2idx(keyidx); + secondarySelectedCell = getCell(column, posidx); + shiftSelect(); + + break; } }, @@ -206,80 +271,80 @@ } }; - function onClickCol(e: MouseEvent, colName: string) { + function onClickCol(e: MouseEvent, column: string) { if (e.shiftKey) { if (selectedCols.length === 0) { - selectedCols.push(colName); + selectedCols.push(column); } else { selectedCols = []; // loop through all cols between primarySelectedCol and this col - const col1 = $schema.columns.findIndex((c) => c.name === primarySelectedCell[0]); - const col2 = $schema.columns.findIndex((c) => c.name === colName); + const col1 = col2idx(primarySelectedCell.column); + const col2 = col2idx(column); const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; for (let i = colStart; i <= colEnd; i++) { selectedCols.push($schema.columns[i].name); } } } else if (e.metaKey) { - const i = selectedCols.indexOf(colName); + const i = selectedCols.indexOf(column); if (i !== -1) { // remove cells from selectedCells in this col - selectedCells = selectedCells.filter((cell) => cell[0] !== colName); + selectedCells = selectedCells.filter((c) => c.column !== column); selectedCols.splice(i, 1); + // TODO: set new primarySelectedCell } else { - primarySelectedCell = [colName, $chunk.keyidxs[0]]; - selectedCols.push(colName); + primarySelectedCell = secondarySelectedCell = getCell(column, 0); + selectedCols.push(column); } } else { - primarySelectedCell = [colName, $chunk.keyidxs[0]]; + primarySelectedCell = secondarySelectedCell = getCell(column, 0); selectedCells = []; - selectedCols = [colName]; + selectedCols = [column]; selectedRows = []; } - selectedCols = selectedCols.sort( - (a, b) => - $schema.columns.findIndex((c) => c.name === a) - - $schema.columns.findIndex((c) => c.name === b) - ); + selectedCols = selectedCols.sort((a, b) => col2idx(a) - col2idx(b)); if (onSelectCols && onSelectCols.endpointId) { dispatch(onSelectCols.endpointId, { detail: { selected: selectedCols } }); } } - function onClickRow(e: MouseEvent, keyidx: string) { + function onClickRow(e: MouseEvent, keyidx: number) { if (e.shiftKey) { if (selectedRows.length === 0) { selectedRows.push(keyidx); } else { selectedRows = []; // loop through all rows between primarySelectedCol and this row - const row1 = $chunk.keyidxs.indexOf(primarySelectedCell[1]); - const row2 = $chunk.keyidxs.indexOf(keyidx); + const row1 = keyidx2idx(primarySelectedCell.keyidx); + const row2 = keyidx2idx(keyidx); const [rowStart, rowEnd] = row1 < row2 ? [row1, row2] : [row2, row1]; for (let i = rowStart; i <= rowEnd; i++) { - selectedRows.push($chunk.keyidxs[i]); + selectedRows.push(parseInt($chunk.keyidxs[i])); } } } else if (e.metaKey) { const i = selectedRows.indexOf(keyidx); if (i !== -1) { // remove cells from selectedCells in this row - selectedCells = selectedCells.filter((cell) => cell[1] !== keyidx); + selectedCells = selectedCells.filter((c) => c.keyidx !== keyidx); selectedRows.splice(i, 1); + // TODO: set new primarySelectedCell } else { - primarySelectedCell = [$schema.columns[0].name, keyidx]; + const column = $schema.columns[0].name; + const posidx = keyidx2idx(keyidx); + primarySelectedCell = secondarySelectedCell = getCell(column, posidx); selectedRows.push(keyidx); } } else { - primarySelectedCell = [$schema.columns[0].name, keyidx]; + const column = $schema.columns[0].name; + const posidx = keyidx2idx(keyidx); + primarySelectedCell = secondarySelectedCell = getCell(column, posidx); selectedCells = []; selectedCols = []; selectedRows = [keyidx]; } - selectedRows = selectedRows.sort( - (a, b) => $chunk.keyidxs.indexOf(a) - $chunk.keyidxs.indexOf(b) - ); + selectedRows = selectedRows.sort((a, b) => keyidx2idx(a) - keyidx2idx(b)); if (onSelectRows && onSelectRows.endpointId) { dispatch(onSelectRows.endpointId, { detail: { selected: selectedCols } }); @@ -287,66 +352,172 @@ } function getColumnSelectClasses( - colName: string, - primarySelectedCell: Array, - selectedCells: Array>, + column: string, + primarySelectedCell: Cell, + selectedCells: Array, selectedCols: Array, - selectedRows: Array + selectedRows: Array ) { - if (selectedCols.includes(colName)) - return 'bg-violet-700 text-white font-bold ' + if (selectedCols.includes(column)) return 'bg-violet-700 text-white font-bold '; if ( - primarySelectedCell[0] === colName || - selectedCells.some((c) => c[0] === colName) || + primarySelectedCell.column === column || + selectedCells.some((c) => c.column === column) || selectedRows.length > 0 ) - return 'bg-violet-100 '; + return 'bg-violet-200 '; return ''; } function getRowSelectClasses( - keyidx: string, - primarySelectedCell: Array, - selectedCells: Array>, + keyidx: number, + primarySelectedCell: Cell, + selectedCells: Array, selectedCols: Array, - selectedRows: Array + selectedRows: Array ) { - if (selectedRows.includes(keyidx)) - return 'bg-violet-700 text-white font-bold ' + if (selectedRows.includes(keyidx)) return 'bg-violet-700 text-white font-bold '; if ( - primarySelectedCell[1] === keyidx || - selectedCells.some((c) => c[1] === keyidx) || + primarySelectedCell.keyidx === keyidx || + selectedCells.some((c) => c.keyidx === keyidx) || selectedCols.length > 0 ) - return 'bg-violet-100 '; + return 'bg-violet-200 '; return ''; } function getCellSelectClasses( - colName: string, - keyidx: string, - primarySelectedCell: Array, - selectedCells: Array>, + column: string, + keyidx: number, + primarySelectedCell: Cell, + selectedCells: Array, selectedCols: Array, - selectedRows: Array + selectedRows: Array ) { let classes = ''; + if ( - selectedCells.some((c) => c[0] === colName && c[1] === keyidx) || - selectedCols.includes(colName) || + selectedCells.some((c) => c.column === column && c.keyidx === keyidx) || + selectedCols.includes(column) || selectedRows.includes(keyidx) - ) + ) { classes += 'bg-violet-100 '; - if (primarySelectedCell[0] === colName && primarySelectedCell[1] === keyidx) + } + + if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { classes += 'border-2 border-violet-600 '; - else classes += 'border-t border-l border-slate-300 '; + } else { + classes += 'border-t border-l border-slate-300 '; + } + + if (secondarySelectedCell.column === column && secondarySelectedCell.keyidx === keyidx) { + classes += 'text-red-600 '; + } + return classes; } + + window.addEventListener('keydown', (e) => { + const colidx = col2idx(primarySelectedCell.column); + const posidx = primarySelectedCell.posidx; + + if (e.key === 'ArrowDown') { + if (e.shiftKey) { + const posidx2 = keyidx2idx(secondarySelectedCell.keyidx); + if (posidx2 + 1 < $chunk.keyidxs.length) { + secondarySelectedCell = getCell(secondarySelectedCell.column, posidx2 + 1); + shiftSelect(); + } else if (page * perPage + posidx2 < $schema.nrows - 1) { + // TODO: flesh out this case + page++; + secondarySelectedCell = getCell(secondarySelectedCell.column, 0); + } + } else { + if (posidx < $chunk.keyidxs.length - 1) { + primarySelectedCell = secondarySelectedCell = getCell( + primarySelectedCell.column, + posidx + 1 + ); + } else if (page * perPage + posidx < $schema.nrows - 1) { + page++; + primarySelectedCell = secondarySelectedCell = getCell(primarySelectedCell.column, 0); + } + selectedCells = []; + } + selectedCols = []; + selectedRows = []; + } else if (e.key === 'ArrowUp') { + if (e.shiftKey) { + const posidx2 = keyidx2idx(secondarySelectedCell.keyidx); + if (posidx2 > 0) { + secondarySelectedCell = getCell(secondarySelectedCell.column, posidx2 - 1); + shiftSelect(); + } else if (page > 0) { + // TODO: flesh out this case + page--; + secondarySelectedCell = getCell(secondarySelectedCell.column, $chunk.keyidxs.length - 1); + } + } else { + if (posidx > 0) { + primarySelectedCell = secondarySelectedCell = getCell( + primarySelectedCell.column, + posidx - 1 + ); + } else if (page > 0) { + page--; + primarySelectedCell = secondarySelectedCell = getCell( + primarySelectedCell.column, + $chunk.keyidxs.length - 1 + ); + } + selectedCells = []; + } + selectedCols = []; + selectedRows = []; + } else if (e.key === 'ArrowLeft') { + if (e.shiftKey) { + const colidx2 = col2idx(secondarySelectedCell.column); + const posidx2 = secondarySelectedCell.posidx; + if (colidx2 > 0) { + secondarySelectedCell = getCell($schema.columns[colidx2 - 1].name, posidx2); + shiftSelect(); + } + } else { + if (colidx > 0) { + primarySelectedCell = secondarySelectedCell = getCell( + $schema.columns[colidx - 1].name, + posidx + ); + } + selectedCells = []; + } + selectedCols = []; + selectedRows = []; + } else if (e.key === 'ArrowRight') { + if (e.shiftKey) { + const colidx2 = col2idx(secondarySelectedCell.column); + const posidx2 = secondarySelectedCell.posidx; + if (colidx2 < $schema.columns.length - 1) { + secondarySelectedCell = getCell($schema.columns[colidx2 + 1].name, posidx2); + shiftSelect(); + } + } else { + if (colidx < $schema.columns.length - 1) { + primarySelectedCell = secondarySelectedCell = getCell( + $schema.columns[colidx + 1].name, + posidx + ); + } + selectedCells = []; + } + selectedCols = []; + selectedRows = []; + } + });
@@ -458,7 +629,7 @@ document.getSelection().removeAllRanges(); }} -->
{ - console.log(keyidx); dispatch(onEdit.endpointId, { detail: { column: col.name, diff --git a/meerkat/interactive/app/src/lib/component/core/table/__init__.py b/meerkat/interactive/app/src/lib/component/core/table/__init__.py index a2dc3f0d3..854b445f4 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/__init__.py +++ b/meerkat/interactive/app/src/lib/component/core/table/__init__.py @@ -1,5 +1,7 @@ from typing import Any, List +from pydantic import BaseModel + from meerkat.dataframe import DataFrame from meerkat.interactive.app.src.lib.component.abstract import Component from meerkat.interactive.endpoint import EndpointProperty @@ -25,8 +27,23 @@ class OnEditInterface(EventInterface): value: Any -class OnSelectTable(EventInterface): - selected: List[Any] +class Cell(BaseModel): + column: str + keyidx: int + posidx: int + value: Any + + +class OnSelectTableCells(EventInterface): + selected: List[Cell] + + +class OnSelectTableCols(EventInterface): + selected: List[str] # list of column names + + +class OnSelectTableRows(EventInterface): + selected: List[int] # list of keyidx class Table(Component): @@ -36,9 +53,9 @@ class Table(Component): classes: str = "h-fit" on_edit: EndpointProperty[OnEditInterface] = None - on_select_cells: EndpointProperty[OnSelectTable] = None - on_select_cols: EndpointProperty[OnSelectTable] = None - on_select_rows: EndpointProperty[OnSelectTable] = None + on_select_cells: EndpointProperty[OnSelectTableCells] = None + on_select_cols: EndpointProperty[OnSelectTableCols] = None + on_select_rows: EndpointProperty[OnSelectTableRows] = None def __init__( self, From 4bb8461ef30b2b4f5b163b1201724f3c4df4644c Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Thu, 11 May 2023 23:50:32 -0700 Subject: [PATCH 11/55] Work on border box around selection --- .../src/lib/component/core/table/Table.svelte | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 511d20046..b512eae2e 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -388,12 +388,14 @@ function getCellSelectClasses( column: string, keyidx: number, + posidx: number, primarySelectedCell: Cell, selectedCells: Array, selectedCols: Array, selectedRows: Array ) { - let classes = ''; + let classes = 'border-t border-l border-slate-300 '; + // let classes = ''; if ( selectedCells.some((c) => c.column === column && c.keyidx === keyidx) || @@ -403,16 +405,50 @@ classes += 'bg-violet-100 '; } - if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { - classes += 'border-2 border-violet-600 '; - } else { - classes += 'border-t border-l border-slate-300 '; - } - if (secondarySelectedCell.column === column && secondarySelectedCell.keyidx === keyidx) { classes += 'text-red-600 '; } + // if the column is between the primary and secondary selected cells + if ( + (col2idx(primarySelectedCell.column) <= col2idx(column) && + col2idx(column) <= col2idx(secondarySelectedCell.column)) || + (col2idx(secondarySelectedCell.column) <= col2idx(column) && + col2idx(column) <= col2idx(primarySelectedCell.column)) + ) { + if (primarySelectedCell.posidx <= secondarySelectedCell.posidx) { + if (posidx === primarySelectedCell.posidx) classes += 'border-t-violet-600 '; + if (posidx === secondarySelectedCell.posidx) classes += 'border-b border-b-violet-600 '; + } + if (primarySelectedCell.posidx >= secondarySelectedCell.posidx) { + if (posidx === primarySelectedCell.posidx) classes += 'border-b border-b-violet-600 '; + if (posidx === secondarySelectedCell.posidx) classes += 'border-t-violet-600 '; + } + } + + // if the posidx is between the primary and secondary selected cells + if ( + (primarySelectedCell.posidx <= posidx && posidx <= secondarySelectedCell.posidx) || + (secondarySelectedCell.posidx <= posidx && posidx <= primarySelectedCell.posidx) + ) { + if (col2idx(primarySelectedCell.column) <= col2idx(secondarySelectedCell.column)) { + if (col2idx(column) === col2idx(primarySelectedCell.column)) + classes += 'border-l-violet-600 '; + if (col2idx(column) === col2idx(secondarySelectedCell.column)) + classes += 'border-r border-r-violet-600 '; + } + if (col2idx(primarySelectedCell.column) >= col2idx(secondarySelectedCell.column)) { + if (col2idx(column) === col2idx(primarySelectedCell.column)) + classes += 'border-r border-r-violet-600 '; + if (col2idx(column) === col2idx(secondarySelectedCell.column)) + classes += 'border-l-violet-600 '; + } + } + + if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { + classes += 'border-2 border-violet-600 '; + } + return classes; } @@ -633,6 +669,7 @@ getCellSelectClasses( col.name, keyidx, + posidx, primarySelectedCell, selectedCells, selectedCols, From c23ad7cf09bf0157b2979bbbbd8877d3d91a73ab Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Fri, 12 May 2023 12:25:20 -0700 Subject: [PATCH 12/55] Hide overflow --- .../interactive/app/src/lib/component/core/table/Table.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index b512eae2e..88ed951c2 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -665,7 +665,7 @@ document.getSelection().removeAllRanges(); }} -->
Date: Fri, 12 May 2023 14:46:12 -0700 Subject: [PATCH 13/55] Improve selected borders --- .../src/lib/component/core/table/Table.svelte | 107 ++++++++---------- 1 file changed, 50 insertions(+), 57 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 88ed951c2..ccc4846ed 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -180,18 +180,20 @@ * secondarySelectedCell, adding them to the selectedCells array. */ function shiftSelect() { - // console.log('primarySelectedCell', primarySelectedCell); - // console.log('secondarySelectedCell', secondarySelectedCell); selectedCells = []; const col1 = col2idx(primarySelectedCell.column); - const keyidx1 = keyidx2idx(primarySelectedCell.keyidx); + const row1 = primarySelectedCell.posidx; const col2 = col2idx(secondarySelectedCell.column); - const keyidx2 = keyidx2idx(secondarySelectedCell.keyidx); + const row2 = secondarySelectedCell.posidx; + + // If the user clicks on the same cell, none should be selected + if (col1 !== -1 && col1 === col2 && row1 !== -1 && row1 === row2) + return; const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; - const [keyidxStart, keyidxEnd] = keyidx1 < keyidx2 ? [keyidx1, keyidx2] : [keyidx2, keyidx1]; + const [keyidxStart, keyidxEnd] = row1 < row2 ? [row1, row2] : [row2, row1]; for (let i = colStart; i <= colEnd; i++) { for (let j = keyidxStart; j <= keyidxEnd; j++) { @@ -216,14 +218,27 @@ if (selectedCells.length === 0 && primarySelectedCell.posidx !== -1) { selectedCells = [primarySelectedCell]; } - primarySelectedCell = secondarySelectedCell = cell; const i = selectedCells.findIndex( (c) => c.column === cell.column && c.keyidx === cell.keyidx ); - if (i !== -1) selectedCells.splice(i, 1); - // TODO: set new primarySelectedCell - else selectedCells.push(cell); + if (i !== -1) { + if (selectedCells.length === 1) { + primarySelectedCell = secondarySelectedCell = selectedCells[0]; + selectedCells.splice(i, 1); + } + else if ( + selectedCells.length > 1 && + selectedCells[i].column === primarySelectedCell.column && + selectedCells[i].keyidx === primarySelectedCell.keyidx + ) { + selectedCells.splice(i, 1); + primarySelectedCell = secondarySelectedCell = selectedCells[selectedCells.length - 1]; + } + } else { + primarySelectedCell = secondarySelectedCell = cell; + selectedCells.push(cell); + } } else { primarySelectedCell = secondarySelectedCell = cell; selectedCells = []; // don't add to selectedCells @@ -239,7 +254,8 @@ }, mousemove(e: MouseEvent) { - // loop through elements to find the first cell + // Loop through elements beneath the mouse x and y to find the first + // element with class 'cell' for (const element of document.elementsFromPoint(e.x, e.y)) { if (!element.classList.contains('cell')) { continue; @@ -247,11 +263,6 @@ const column = element.getAttribute('column') || ''; const keyidx = parseInt(element.getAttribute('keyidx') || '-1'); - if (column === primarySelectedCell.column && keyidx === primarySelectedCell.keyidx) { - // same cell as primarySelectedCell - return; - } - const posidx = keyidx2idx(keyidx); secondarySelectedCell = getCell(column, posidx); shiftSelect(); @@ -310,14 +321,15 @@ } function onClickRow(e: MouseEvent, keyidx: number) { + const posidx = keyidx2idx(keyidx); if (e.shiftKey) { if (selectedRows.length === 0) { selectedRows.push(keyidx); } else { selectedRows = []; // loop through all rows between primarySelectedCol and this row - const row1 = keyidx2idx(primarySelectedCell.keyidx); - const row2 = keyidx2idx(keyidx); + const row1 = primarySelectedCell.posidx; + const row2 = posidx; const [rowStart, rowEnd] = row1 < row2 ? [row1, row2] : [row2, row1]; for (let i = rowStart; i <= rowEnd; i++) { selectedRows.push(parseInt($chunk.keyidxs[i])); @@ -332,13 +344,11 @@ // TODO: set new primarySelectedCell } else { const column = $schema.columns[0].name; - const posidx = keyidx2idx(keyidx); primarySelectedCell = secondarySelectedCell = getCell(column, posidx); selectedRows.push(keyidx); } } else { const column = $schema.columns[0].name; - const posidx = keyidx2idx(keyidx); primarySelectedCell = secondarySelectedCell = getCell(column, posidx); selectedCells = []; selectedCols = []; @@ -394,8 +404,7 @@ selectedCols: Array, selectedRows: Array ) { - let classes = 'border-t border-l border-slate-300 '; - // let classes = ''; + let classes = ''; if ( selectedCells.some((c) => c.column === column && c.keyidx === keyidx) || @@ -409,44 +418,28 @@ classes += 'text-red-600 '; } - // if the column is between the primary and secondary selected cells - if ( - (col2idx(primarySelectedCell.column) <= col2idx(column) && - col2idx(column) <= col2idx(secondarySelectedCell.column)) || - (col2idx(secondarySelectedCell.column) <= col2idx(column) && - col2idx(column) <= col2idx(primarySelectedCell.column)) - ) { - if (primarySelectedCell.posidx <= secondarySelectedCell.posidx) { - if (posidx === primarySelectedCell.posidx) classes += 'border-t-violet-600 '; - if (posidx === secondarySelectedCell.posidx) classes += 'border-b border-b-violet-600 '; - } - if (primarySelectedCell.posidx >= secondarySelectedCell.posidx) { - if (posidx === primarySelectedCell.posidx) classes += 'border-b border-b-violet-600 '; - if (posidx === secondarySelectedCell.posidx) classes += 'border-t-violet-600 '; - } + if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { + classes += 'border-2 border-violet-600 '; + return classes; } - // if the posidx is between the primary and secondary selected cells - if ( - (primarySelectedCell.posidx <= posidx && posidx <= secondarySelectedCell.posidx) || - (secondarySelectedCell.posidx <= posidx && posidx <= primarySelectedCell.posidx) - ) { - if (col2idx(primarySelectedCell.column) <= col2idx(secondarySelectedCell.column)) { - if (col2idx(column) === col2idx(primarySelectedCell.column)) - classes += 'border-l-violet-600 '; - if (col2idx(column) === col2idx(secondarySelectedCell.column)) - classes += 'border-r border-r-violet-600 '; - } - if (col2idx(primarySelectedCell.column) >= col2idx(secondarySelectedCell.column)) { - if (col2idx(column) === col2idx(primarySelectedCell.column)) - classes += 'border-r border-r-violet-600 '; - if (col2idx(column) === col2idx(secondarySelectedCell.column)) - classes += 'border-l-violet-600 '; - } + classes += 'border-t border-l border-slate-300 '; + + let col = col2idx(column), + col1 = col2idx(primarySelectedCell.column), + col2 = col2idx(secondarySelectedCell.column); + [col1, col2] = col1 < col2 ? [col1, col2] : [col2, col1]; + let row = posidx, + row1 = primarySelectedCell.posidx, + row2 = secondarySelectedCell.posidx; + [row1, row2] = row1 < row2 ? [row1, row2] : [row2, row1]; + + if (col1 <= col && col <= col2) { + if (row === row1 || row === row2 + 1) classes += 'border-t-violet-600 '; } - if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { - classes += 'border-2 border-violet-600 '; + if (row1 <= row && row <= row2) { + if (col === col1 || col === col2 + 1) classes += 'border-l-violet-600 '; } return classes; @@ -458,7 +451,7 @@ if (e.key === 'ArrowDown') { if (e.shiftKey) { - const posidx2 = keyidx2idx(secondarySelectedCell.keyidx); + const posidx2 = secondarySelectedCell.posidx; if (posidx2 + 1 < $chunk.keyidxs.length) { secondarySelectedCell = getCell(secondarySelectedCell.column, posidx2 + 1); shiftSelect(); @@ -483,7 +476,7 @@ selectedRows = []; } else if (e.key === 'ArrowUp') { if (e.shiftKey) { - const posidx2 = keyidx2idx(secondarySelectedCell.keyidx); + const posidx2 = secondarySelectedCell.posidx; if (posidx2 > 0) { secondarySelectedCell = getCell(secondarySelectedCell.column, posidx2 - 1); shiftSelect(); From 66e44a3183df0ee5a18b1f606817849ea6891960 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Fri, 12 May 2023 16:13:08 -0700 Subject: [PATCH 14/55] Simplify borders --- .../src/lib/component/core/table/Table.svelte | 96 +++++++++++++------ 1 file changed, 65 insertions(+), 31 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index ccc4846ed..dbd5408ed 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -189,8 +189,7 @@ const row2 = secondarySelectedCell.posidx; // If the user clicks on the same cell, none should be selected - if (col1 !== -1 && col1 === col2 && row1 !== -1 && row1 === row2) - return; + if (col1 !== -1 && col1 === col2 && row1 !== -1 && row1 === row2) return; const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; const [keyidxStart, keyidxEnd] = row1 < row2 ? [row1, row2] : [row2, row1]; @@ -226,8 +225,7 @@ if (selectedCells.length === 1) { primarySelectedCell = secondarySelectedCell = selectedCells[0]; selectedCells.splice(i, 1); - } - else if ( + } else if ( selectedCells.length > 1 && selectedCells[i].column === primarySelectedCell.column && selectedCells[i].keyidx === primarySelectedCell.keyidx @@ -395,6 +393,19 @@ return ''; } + /** + * Helper function that returns the number of times a cell is selected. + * @param column + * @param keyidx + */ + function countTimesSelected(column: string, keyidx: number) { + return ( + (selectedCells.some((c) => c.column === column && c.keyidx === keyidx) ? 1 : 0) + + (selectedCols.includes(column) ? 1 : 0) + + (selectedRows.includes(keyidx) ? 1 : 0) + ); + } + function getCellSelectClasses( column: string, keyidx: number, @@ -405,43 +416,66 @@ selectedRows: Array ) { let classes = ''; + const count = countTimesSelected(column, keyidx); - if ( - selectedCells.some((c) => c.column === column && c.keyidx === keyidx) || - selectedCols.includes(column) || - selectedRows.includes(keyidx) - ) { - classes += 'bg-violet-100 '; - } - - if (secondarySelectedCell.column === column && secondarySelectedCell.keyidx === keyidx) { - classes += 'text-red-600 '; - } + // Determine background color + if (count > 0) classes += `bg-violet-${count}00 `; + // Determine borders if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { classes += 'border-2 border-violet-600 '; - return classes; - } - - classes += 'border-t border-l border-slate-300 '; + } else { + classes += 'border-t border-l '; // border width of 1px + + if (posidx > 0) { + const countAbove = countTimesSelected(column, parseInt($chunk.keyidxs[posidx - 1])); + if ((count > 0 && countAbove === 0) || (count === 0 && countAbove > 0)) + classes += 'border-t-violet-600 '; + } else if (count > 0 && posidx === 0) { + classes += 'border-t-violet-600 '; + } else { + classes += 'border-t-slate-300 '; + } - let col = col2idx(column), - col1 = col2idx(primarySelectedCell.column), - col2 = col2idx(secondarySelectedCell.column); - [col1, col2] = col1 < col2 ? [col1, col2] : [col2, col1]; - let row = posidx, - row1 = primarySelectedCell.posidx, - row2 = secondarySelectedCell.posidx; - [row1, row2] = row1 < row2 ? [row1, row2] : [row2, row1]; + const colidx = col2idx(column); + if (colidx > 0) { + const countLeft = countTimesSelected($chunk.columns[colidx - 1], keyidx); + if ((count > 0 && countLeft === 0) || (count === 0 && countLeft > 0)) + classes += 'border-l-violet-600 '; + } else if (count > 0 && colidx === 0) { + classes += 'border-l-violet-600 '; + } else { + classes += 'border-l-slate-300 '; + } + } - if (col1 <= col && col <= col2) { - if (row === row1 || row === row2 + 1) classes += 'border-t-violet-600 '; + // Determine text color + if (secondarySelectedCell.column === column && secondarySelectedCell.keyidx === keyidx) { + classes += 'text-red-600 '; } - if (row1 <= row && row <= row2) { - if (col === col1 || col === col2 + 1) classes += 'border-l-violet-600 '; + if (getCell(column, posidx).value === 'KNDJX3AE8G') { + console.log('count', count); + console.log('classes', classes); } + // let col = col2idx(column), + // col1 = col2idx(primarySelectedCell.column), + // col2 = col2idx(secondarySelectedCell.column); + // [col1, col2] = col1 < col2 ? [col1, col2] : [col2, col1]; + // let row = posidx, + // row1 = primarySelectedCell.posidx, + // row2 = secondarySelectedCell.posidx; + // [row1, row2] = row1 < row2 ? [row1, row2] : [row2, row1]; + + // if (col1 <= col && col <= col2) { + // if (row === row1 || row === row2 + 1) classes += 'border-t-violet-600 '; + // } + + // if (row1 <= row && row <= row2) { + // if (col === col1 || col === col2 + 1) classes += 'border-l-violet-600 '; + // } + return classes; } From 442b693f5bf83b3bc4a99aed0dea743453ee0e4c Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Fri, 12 May 2023 16:34:59 -0700 Subject: [PATCH 15/55] Simplify border calculation again --- .../src/lib/component/core/table/Table.svelte | 77 +++++++++---------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index dbd5408ed..b9280f2bb 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -394,18 +394,40 @@ } /** - * Helper function that returns the number of times a cell is selected. + * Helper function that returns a number representing the ways a cell has + * been selected or not. + * + * - one's place: selectedCells (0 or 1) + * - ten's place: selectedCols (0 or 10) + * - hundred's place: selectedRows (0 or 100) + * * @param column * @param keyidx */ - function countTimesSelected(column: string, keyidx: number) { + function getSelectedBitmap(column: string, keyidx: number) { return ( (selectedCells.some((c) => c.column === column && c.keyidx === keyidx) ? 1 : 0) + - (selectedCols.includes(column) ? 1 : 0) + - (selectedRows.includes(keyidx) ? 1 : 0) + (selectedCols.includes(column) ? 10 : 0) + + (selectedRows.includes(keyidx) ? 100 : 0) ); } + /** + * Helper function that returns a string of classes for a cell based on + * how it has been selected. + * + * NOTE: The params selectedCells, selectedCols, and selectedRows are + * included so that this funciton is called reactively whenever any of those + * (or primarySelectedCell) changes. + * + * @param column + * @param keyidx + * @param posidx + * @param primarySelectedCell + * @param selectedCells + * @param selectedCols + * @param selectedRows + */ function getCellSelectClasses( column: string, keyidx: number, @@ -416,10 +438,13 @@ selectedRows: Array ) { let classes = ''; - const count = countTimesSelected(column, keyidx); + const bitmap = getSelectedBitmap(column, keyidx); // Determine background color - if (count > 0) classes += `bg-violet-${count}00 `; + if (bitmap > 0) { + const count = bitmap.toString().split('1').length - 1; + classes += `bg-violet-${count}00 `; + } // Determine borders if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { @@ -428,10 +453,9 @@ classes += 'border-t border-l '; // border width of 1px if (posidx > 0) { - const countAbove = countTimesSelected(column, parseInt($chunk.keyidxs[posidx - 1])); - if ((count > 0 && countAbove === 0) || (count === 0 && countAbove > 0)) - classes += 'border-t-violet-600 '; - } else if (count > 0 && posidx === 0) { + const bitmapAbove = getSelectedBitmap(column, parseInt($chunk.keyidxs[posidx - 1])); + if (bitmap !== bitmapAbove) classes += 'border-t-violet-600 '; + } else if (bitmap > 0 && posidx === 0) { classes += 'border-t-violet-600 '; } else { classes += 'border-t-slate-300 '; @@ -439,10 +463,9 @@ const colidx = col2idx(column); if (colidx > 0) { - const countLeft = countTimesSelected($chunk.columns[colidx - 1], keyidx); - if ((count > 0 && countLeft === 0) || (count === 0 && countLeft > 0)) - classes += 'border-l-violet-600 '; - } else if (count > 0 && colidx === 0) { + const bitmapLeft = getSelectedBitmap($chunk.columns[colidx - 1], keyidx); + if (bitmap !== bitmapLeft) classes += 'border-l-violet-600 '; + } else if (bitmap > 0 && colidx === 0) { classes += 'border-l-violet-600 '; } else { classes += 'border-l-slate-300 '; @@ -454,28 +477,6 @@ classes += 'text-red-600 '; } - if (getCell(column, posidx).value === 'KNDJX3AE8G') { - console.log('count', count); - console.log('classes', classes); - } - - // let col = col2idx(column), - // col1 = col2idx(primarySelectedCell.column), - // col2 = col2idx(secondarySelectedCell.column); - // [col1, col2] = col1 < col2 ? [col1, col2] : [col2, col1]; - // let row = posidx, - // row1 = primarySelectedCell.posidx, - // row2 = secondarySelectedCell.posidx; - // [row1, row2] = row1 < row2 ? [row1, row2] : [row2, row1]; - - // if (col1 <= col && col <= col2) { - // if (row === row1 || row === row2 + 1) classes += 'border-t-violet-600 '; - // } - - // if (row1 <= row && row <= row2) { - // if (col === col1 || col === col2 + 1) classes += 'border-l-violet-600 '; - // } - return classes; } @@ -687,10 +688,6 @@ {#each $chunk.columnInfos as col} -
Date: Fri, 12 May 2023 16:57:48 -0700 Subject: [PATCH 16/55] Work on edge cases --- .../src/lib/component/core/table/Table.svelte | 89 +++++++++++++------ 1 file changed, 60 insertions(+), 29 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index b9280f2bb..9388b04b0 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -222,16 +222,21 @@ (c) => c.column === cell.column && c.keyidx === cell.keyidx ); if (i !== -1) { - if (selectedCells.length === 1) { - primarySelectedCell = secondarySelectedCell = selectedCells[0]; + if (selectedCells.length === 2) { selectedCells.splice(i, 1); + primarySelectedCell = secondarySelectedCell = selectedCells[0]; + selectedCells = []; } else if ( - selectedCells.length > 1 && - selectedCells[i].column === primarySelectedCell.column && - selectedCells[i].keyidx === primarySelectedCell.keyidx + (selectedCells.length > 1 && + selectedCells[i].column === primarySelectedCell.column && + selectedCells[i].keyidx === primarySelectedCell.keyidx) || + (selectedCells[i].column === secondarySelectedCell.column && + selectedCells[i].keyidx === secondarySelectedCell.keyidx) ) { selectedCells.splice(i, 1); primarySelectedCell = secondarySelectedCell = selectedCells[selectedCells.length - 1]; + } else { + selectedCells.splice(i, 1); } } else { primarySelectedCell = secondarySelectedCell = cell; @@ -261,6 +266,14 @@ const column = element.getAttribute('column') || ''; const keyidx = parseInt(element.getAttribute('keyidx') || '-1'); + if ( + selectedCells.length > 0 && + column === primarySelectedCell.column && + keyidx === primarySelectedCell.keyidx + ) { + return; + } + const posidx = keyidx2idx(keyidx); secondarySelectedCell = getCell(column, posidx); shiftSelect(); @@ -282,17 +295,13 @@ function onClickCol(e: MouseEvent, column: string) { if (e.shiftKey) { - if (selectedCols.length === 0) { - selectedCols.push(column); - } else { - selectedCols = []; - // loop through all cols between primarySelectedCol and this col - const col1 = col2idx(primarySelectedCell.column); - const col2 = col2idx(column); - const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; - for (let i = colStart; i <= colEnd; i++) { - selectedCols.push($schema.columns[i].name); - } + selectedCols = []; + // loop through all cols between primarySelectedCol and this col + const col1 = col2idx(primarySelectedCell.column); + const col2 = col2idx(column); + const [colStart, colEnd] = col1 < col2 ? [col1, col2] : [col2, col1]; + for (let i = colStart; i <= colEnd; i++) { + selectedCols.push($schema.columns[i].name); } } else if (e.metaKey) { const i = selectedCols.indexOf(column); @@ -300,7 +309,18 @@ // remove cells from selectedCells in this col selectedCells = selectedCells.filter((c) => c.column !== column); selectedCols.splice(i, 1); - // TODO: set new primarySelectedCell + + // set new primarySelectedCell + if (selectedCells.length > 0) + primarySelectedCell = secondarySelectedCell = selectedCells[0]; + else if (selectedCols.length > 0) + primarySelectedCell = secondarySelectedCell = getCell(selectedCols[0], 0); + else if (selectedRows.length > 0) + primarySelectedCell = secondarySelectedCell = getCell( + $schema.columns[0].name, + selectedRows[0] + ); + else primarySelectedCell = secondarySelectedCell = getCell($schema.columns[0].name, 0); } else { primarySelectedCell = secondarySelectedCell = getCell(column, 0); selectedCols.push(column); @@ -321,17 +341,13 @@ function onClickRow(e: MouseEvent, keyidx: number) { const posidx = keyidx2idx(keyidx); if (e.shiftKey) { - if (selectedRows.length === 0) { - selectedRows.push(keyidx); - } else { - selectedRows = []; - // loop through all rows between primarySelectedCol and this row - const row1 = primarySelectedCell.posidx; - const row2 = posidx; - const [rowStart, rowEnd] = row1 < row2 ? [row1, row2] : [row2, row1]; - for (let i = rowStart; i <= rowEnd; i++) { - selectedRows.push(parseInt($chunk.keyidxs[i])); - } + selectedRows = []; + // loop through all rows between primarySelectedCol and this row + const row1 = primarySelectedCell.posidx; + const row2 = posidx; + const [rowStart, rowEnd] = row1 < row2 ? [row1, row2] : [row2, row1]; + for (let i = rowStart; i <= rowEnd; i++) { + selectedRows.push(parseInt($chunk.keyidxs[i])); } } else if (e.metaKey) { const i = selectedRows.indexOf(keyidx); @@ -339,7 +355,18 @@ // remove cells from selectedCells in this row selectedCells = selectedCells.filter((c) => c.keyidx !== keyidx); selectedRows.splice(i, 1); - // TODO: set new primarySelectedCell + + // set new primarySelectedCell + if (selectedCells.length > 0) + primarySelectedCell = secondarySelectedCell = selectedCells[0]; + else if (selectedCols.length > 0) + primarySelectedCell = secondarySelectedCell = getCell(selectedCols[0], 0); + else if (selectedRows.length > 0) + primarySelectedCell = secondarySelectedCell = getCell( + $schema.columns[0].name, + selectedRows[0] + ); + else primarySelectedCell = secondarySelectedCell = getCell($schema.columns[0].name, 0); } else { const column = $schema.columns[0].name; primarySelectedCell = secondarySelectedCell = getCell(column, posidx); @@ -485,6 +512,7 @@ const posidx = primarySelectedCell.posidx; if (e.key === 'ArrowDown') { + e.preventDefault(); if (e.shiftKey) { const posidx2 = secondarySelectedCell.posidx; if (posidx2 + 1 < $chunk.keyidxs.length) { @@ -510,6 +538,7 @@ selectedCols = []; selectedRows = []; } else if (e.key === 'ArrowUp') { + e.preventDefault(); if (e.shiftKey) { const posidx2 = secondarySelectedCell.posidx; if (posidx2 > 0) { @@ -538,6 +567,7 @@ selectedCols = []; selectedRows = []; } else if (e.key === 'ArrowLeft') { + e.preventDefault(); if (e.shiftKey) { const colidx2 = col2idx(secondarySelectedCell.column); const posidx2 = secondarySelectedCell.posidx; @@ -557,6 +587,7 @@ selectedCols = []; selectedRows = []; } else if (e.key === 'ArrowRight') { + e.preventDefault(); if (e.shiftKey) { const colidx2 = col2idx(secondarySelectedCell.column); const posidx2 = secondarySelectedCell.posidx; From df99dfa626f4f1a383172ba58a88da66d95d2629 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Fri, 12 May 2023 17:03:37 -0700 Subject: [PATCH 17/55] Add support for cmd+shift+arrow --- .../src/lib/component/core/table/Table.svelte | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 9388b04b0..131a756e5 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -513,7 +513,17 @@ if (e.key === 'ArrowDown') { e.preventDefault(); - if (e.shiftKey) { + if (e.metaKey) { + if (e.shiftKey) { + secondarySelectedCell = getCell(secondarySelectedCell.column, $chunk.keyidxs.length - 1); + shiftSelect(); + } else { + primarySelectedCell = secondarySelectedCell = getCell( + primarySelectedCell.column, + $chunk.keyidxs.length - 1 + ); + } + } else if (e.shiftKey) { const posidx2 = secondarySelectedCell.posidx; if (posidx2 + 1 < $chunk.keyidxs.length) { secondarySelectedCell = getCell(secondarySelectedCell.column, posidx2 + 1); @@ -539,7 +549,14 @@ selectedRows = []; } else if (e.key === 'ArrowUp') { e.preventDefault(); - if (e.shiftKey) { + if (e.metaKey) { + if (e.shiftKey) { + secondarySelectedCell = getCell(secondarySelectedCell.column, 0); + shiftSelect(); + } else { + primarySelectedCell = secondarySelectedCell = getCell(primarySelectedCell.column, 0); + } + } else if (e.shiftKey) { const posidx2 = secondarySelectedCell.posidx; if (posidx2 > 0) { secondarySelectedCell = getCell(secondarySelectedCell.column, posidx2 - 1); @@ -568,7 +585,14 @@ selectedRows = []; } else if (e.key === 'ArrowLeft') { e.preventDefault(); - if (e.shiftKey) { + if (e.metaKey) { + if (e.shiftKey) { + secondarySelectedCell = getCell($schema.columns[0].name, secondarySelectedCell.posidx); + shiftSelect(); + } else { + primarySelectedCell = secondarySelectedCell = getCell($schema.columns[0].name, posidx); + } + } else if (e.shiftKey) { const colidx2 = col2idx(secondarySelectedCell.column); const posidx2 = secondarySelectedCell.posidx; if (colidx2 > 0) { @@ -588,7 +612,20 @@ selectedRows = []; } else if (e.key === 'ArrowRight') { e.preventDefault(); - if (e.shiftKey) { + if (e.metaKey) { + if (e.shiftKey) { + secondarySelectedCell = getCell( + $schema.columns[$schema.columns.length - 1].name, + secondarySelectedCell.posidx + ); + shiftSelect(); + } else { + primarySelectedCell = secondarySelectedCell = getCell( + $schema.columns[$schema.columns.length - 1].name, + posidx + ); + } + } else if (e.shiftKey) { const colidx2 = col2idx(secondarySelectedCell.column); const posidx2 = secondarySelectedCell.posidx; if (colidx2 < $schema.columns.length - 1) { From 41ea6f2f6f8dde55fd5adba28bac1c53dd321748 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Fri, 12 May 2023 19:50:04 -0700 Subject: [PATCH 18/55] Add select all, deselect single cell from col/row --- .../src/lib/component/core/table/Table.svelte | 77 ++++++++++++------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 131a756e5..41a3a6904 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -176,17 +176,15 @@ } /** - * Loops through all cells between primarySelectedCell and - * secondarySelectedCell, adding them to the selectedCells array. + * Selects all cells between cell1 and cell2, inclusive. + * @param cell1 + * @param cell2 */ - function shiftSelect() { + function selectRange(cell1: Cell, cell2: Cell) { selectedCells = []; - const col1 = col2idx(primarySelectedCell.column); - const row1 = primarySelectedCell.posidx; - - const col2 = col2idx(secondarySelectedCell.column); - const row2 = secondarySelectedCell.posidx; + const [col1, row1] = [col2idx(cell1.column), cell1.posidx]; + const [col2, row2] = [col2idx(cell2.column), cell2.posidx]; // If the user clicks on the same cell, none should be selected if (col1 !== -1 && col1 === col2 && row1 !== -1 && row1 === row2) return; @@ -212,7 +210,7 @@ return (e: MouseEvent) => { if (e.shiftKey) { secondarySelectedCell = cell; - shiftSelect(); + selectRange(primarySelectedCell, secondarySelectedCell); } else if (e.metaKey) { if (selectedCells.length === 0 && primarySelectedCell.posidx !== -1) { selectedCells = [primarySelectedCell]; @@ -238,7 +236,24 @@ } else { selectedCells.splice(i, 1); } + } else if (selectedCols.includes(cell.column)) { + selectedCols.splice(selectedCols.indexOf(cell.column), 1); + // add all the cells in the column to selectedCells except the one we clicked on + for (let i = 0; i < $chunk.keyidxs.length; i++) { + if (i !== cell.posidx) { + selectedCells.push(getCell(cell.column, i)); + } + } + } else if (selectedRows.includes(cell.keyidx)) { + selectedRows.splice(selectedRows.indexOf(cell.keyidx), 1); + // add all the cells in the row to selectedCells except the one we clicked on + for (let i = 0; i < $schema.columns.length; i++) { + if ($schema.columns[i].name !== cell.column) { + selectedCells.push(getCell($schema.columns[i].name, cell.posidx)); + } + } } else { + // not selected yet primarySelectedCell = secondarySelectedCell = cell; selectedCells.push(cell); } @@ -276,7 +291,7 @@ const posidx = keyidx2idx(keyidx); secondarySelectedCell = getCell(column, posidx); - shiftSelect(); + selectRange(primarySelectedCell, secondarySelectedCell); break; } @@ -477,46 +492,54 @@ if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { classes += 'border-2 border-violet-600 '; } else { - classes += 'border-t border-l '; // border width of 1px + // border width of 1px, default color slate + classes += 'border-t border-l border-slate-300 '; if (posidx > 0) { const bitmapAbove = getSelectedBitmap(column, parseInt($chunk.keyidxs[posidx - 1])); if (bitmap !== bitmapAbove) classes += 'border-t-violet-600 '; } else if (bitmap > 0 && posidx === 0) { classes += 'border-t-violet-600 '; - } else { - classes += 'border-t-slate-300 '; } + if (bitmap > 0 && posidx === $chunk.keyidxs.length - 1) classes += 'border-b border-b-violet-600 '; + const colidx = col2idx(column); if (colidx > 0) { const bitmapLeft = getSelectedBitmap($chunk.columns[colidx - 1], keyidx); if (bitmap !== bitmapLeft) classes += 'border-l-violet-600 '; } else if (bitmap > 0 && colidx === 0) { classes += 'border-l-violet-600 '; - } else { - classes += 'border-l-slate-300 '; } + + if (bitmap > 0 && colidx === $schema.columns.length - 1) classes += 'border-r border-r-violet-600 '; } // Determine text color - if (secondarySelectedCell.column === column && secondarySelectedCell.keyidx === keyidx) { - classes += 'text-red-600 '; - } + // if (secondarySelectedCell.column === column && secondarySelectedCell.keyidx === keyidx) { + // classes += 'text-red-600 '; + // } return classes; } + // Define keyboard shortcuts window.addEventListener('keydown', (e) => { const colidx = col2idx(primarySelectedCell.column); const posidx = primarySelectedCell.posidx; - if (e.key === 'ArrowDown') { + if (e.key === 'a' && e.metaKey) { + e.preventDefault(); + selectRange( + getCell($schema.columns[0].name, 0), + getCell($schema.columns[$schema.columns.length - 1].name, $chunk.keyidxs.length - 1) + ); + } else if (e.key === 'ArrowDown') { e.preventDefault(); if (e.metaKey) { if (e.shiftKey) { secondarySelectedCell = getCell(secondarySelectedCell.column, $chunk.keyidxs.length - 1); - shiftSelect(); + selectRange(primarySelectedCell, secondarySelectedCell); } else { primarySelectedCell = secondarySelectedCell = getCell( primarySelectedCell.column, @@ -527,7 +550,7 @@ const posidx2 = secondarySelectedCell.posidx; if (posidx2 + 1 < $chunk.keyidxs.length) { secondarySelectedCell = getCell(secondarySelectedCell.column, posidx2 + 1); - shiftSelect(); + selectRange(primarySelectedCell, secondarySelectedCell); } else if (page * perPage + posidx2 < $schema.nrows - 1) { // TODO: flesh out this case page++; @@ -552,7 +575,7 @@ if (e.metaKey) { if (e.shiftKey) { secondarySelectedCell = getCell(secondarySelectedCell.column, 0); - shiftSelect(); + selectRange(primarySelectedCell, secondarySelectedCell); } else { primarySelectedCell = secondarySelectedCell = getCell(primarySelectedCell.column, 0); } @@ -560,7 +583,7 @@ const posidx2 = secondarySelectedCell.posidx; if (posidx2 > 0) { secondarySelectedCell = getCell(secondarySelectedCell.column, posidx2 - 1); - shiftSelect(); + selectRange(primarySelectedCell, secondarySelectedCell); } else if (page > 0) { // TODO: flesh out this case page--; @@ -588,7 +611,7 @@ if (e.metaKey) { if (e.shiftKey) { secondarySelectedCell = getCell($schema.columns[0].name, secondarySelectedCell.posidx); - shiftSelect(); + selectRange(primarySelectedCell, secondarySelectedCell); } else { primarySelectedCell = secondarySelectedCell = getCell($schema.columns[0].name, posidx); } @@ -597,7 +620,7 @@ const posidx2 = secondarySelectedCell.posidx; if (colidx2 > 0) { secondarySelectedCell = getCell($schema.columns[colidx2 - 1].name, posidx2); - shiftSelect(); + selectRange(primarySelectedCell, secondarySelectedCell); } } else { if (colidx > 0) { @@ -618,7 +641,7 @@ $schema.columns[$schema.columns.length - 1].name, secondarySelectedCell.posidx ); - shiftSelect(); + selectRange(primarySelectedCell, secondarySelectedCell); } else { primarySelectedCell = secondarySelectedCell = getCell( $schema.columns[$schema.columns.length - 1].name, @@ -630,7 +653,7 @@ const posidx2 = secondarySelectedCell.posidx; if (colidx2 < $schema.columns.length - 1) { secondarySelectedCell = getCell($schema.columns[colidx2 + 1].name, posidx2); - shiftSelect(); + selectRange(primarySelectedCell, secondarySelectedCell); } } else { if (colidx < $schema.columns.length - 1) { From 4250600bd66aeac94f65c2eebad607c7887ae386 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Sat, 13 May 2023 00:26:19 -0700 Subject: [PATCH 19/55] Add todo --- .../interactive/app/src/lib/component/core/table/Table.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 41a3a6904..b771344fb 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -710,6 +710,7 @@ on:click={(e) => onClickCol(e, column.name)} >
+ {#if column.name === $schema.primaryKey} From 9526a47a07e36fa398cbf4792b3bfbfd63715e6d Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Sat, 13 May 2023 13:21:35 -0700 Subject: [PATCH 20/55] Add in activeCells array --- .../src/lib/component/core/table/Table.svelte | 173 +++++++++++------- 1 file changed, 107 insertions(+), 66 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index b771344fb..cd433e1e6 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -26,6 +26,7 @@ export let primarySelectedCell: Cell = { column: '', keyidx: -1, posidx: -1, value: '' }; export let secondarySelectedCell: Cell = { column: '', keyidx: -1, posidx: -1, value: '' }; + export let activeCells: Array = []; // the cells currently being interacted with export let selectedCells: Array = []; export let selectedCols: Array = []; export let selectedRows: Array = []; @@ -175,13 +176,24 @@ }; } + /** + * Helper function to determine if two cells are equal. + * @param cell1 + * @param cell2 + */ + function areEqual(cell1: Cell, cell2: Cell) { + return cell1.column === cell2.column && cell1.posidx === cell2.posidx; + } + /** * Selects all cells between cell1 and cell2, inclusive. * @param cell1 * @param cell2 + * @param select - If true, will select the cells. If false, will mark as active */ - function selectRange(cell1: Cell, cell2: Cell) { - selectedCells = []; + function selectRange(cell1: Cell, cell2: Cell, select = true) { + if (select) selectedCells = []; + else activeCells = []; const [col1, row1] = [col2idx(cell1.column), cell1.posidx]; const [col2, row2] = [col2idx(cell2.column), cell2.posidx]; @@ -198,11 +210,14 @@ const keyidx = parseInt($chunk.keyidxs[j]); const posidx = j; const value = $chunk.getCell(posidx, column).data; - selectedCells.push({ column, keyidx, posidx, value }); + if (select) selectedCells.push({ column, keyidx, posidx, value }); + else activeCells.push({ column, keyidx, posidx, value }); } } - selectedCells = selectedCells.slice(); // trigger update + // trigger update + if (select) selectedCells = selectedCells.slice(); + else activeCells = activeCells.slice(); } const selectCellMethods = { @@ -212,51 +227,8 @@ secondarySelectedCell = cell; selectRange(primarySelectedCell, secondarySelectedCell); } else if (e.metaKey) { - if (selectedCells.length === 0 && primarySelectedCell.posidx !== -1) { - selectedCells = [primarySelectedCell]; - } - - const i = selectedCells.findIndex( - (c) => c.column === cell.column && c.keyidx === cell.keyidx - ); - if (i !== -1) { - if (selectedCells.length === 2) { - selectedCells.splice(i, 1); - primarySelectedCell = secondarySelectedCell = selectedCells[0]; - selectedCells = []; - } else if ( - (selectedCells.length > 1 && - selectedCells[i].column === primarySelectedCell.column && - selectedCells[i].keyidx === primarySelectedCell.keyidx) || - (selectedCells[i].column === secondarySelectedCell.column && - selectedCells[i].keyidx === secondarySelectedCell.keyidx) - ) { - selectedCells.splice(i, 1); - primarySelectedCell = secondarySelectedCell = selectedCells[selectedCells.length - 1]; - } else { - selectedCells.splice(i, 1); - } - } else if (selectedCols.includes(cell.column)) { - selectedCols.splice(selectedCols.indexOf(cell.column), 1); - // add all the cells in the column to selectedCells except the one we clicked on - for (let i = 0; i < $chunk.keyidxs.length; i++) { - if (i !== cell.posidx) { - selectedCells.push(getCell(cell.column, i)); - } - } - } else if (selectedRows.includes(cell.keyidx)) { - selectedRows.splice(selectedRows.indexOf(cell.keyidx), 1); - // add all the cells in the row to selectedCells except the one we clicked on - for (let i = 0; i < $schema.columns.length; i++) { - if ($schema.columns[i].name !== cell.column) { - selectedCells.push(getCell($schema.columns[i].name, cell.posidx)); - } - } - } else { - // not selected yet - primarySelectedCell = secondarySelectedCell = cell; - selectedCells.push(cell); - } + primarySelectedCell = secondarySelectedCell = cell; + activeCells.push(cell); } else { primarySelectedCell = secondarySelectedCell = cell; selectedCells = []; // don't add to selectedCells @@ -291,13 +263,66 @@ const posidx = keyidx2idx(keyidx); secondarySelectedCell = getCell(column, posidx); - selectRange(primarySelectedCell, secondarySelectedCell); + selectRange(primarySelectedCell, secondarySelectedCell, false); break; } }, mouseup(e: MouseEvent) { + // select all activeCells if at least one of them is not already selected + let foundUnselected = false; + for (const cell of activeCells) { + if (getSelectedBitmap(cell.column, cell.keyidx, false) === 0) { + foundUnselected = true; + break; + } + } + + if (foundUnselected) { + // select all cells + selectedCells = selectedCells.concat(activeCells); + } else { + // unselect all cells + for (const cell of activeCells) { + const i = selectedCells.findIndex((c) => areEqual(c, cell)); + if (i !== -1) { + if (selectedCells.length === 2) { + selectedCells.splice(i, 1); + primarySelectedCell = secondarySelectedCell = selectedCells[0]; + selectedCells = []; + } else if ( + selectedCells.length > 1 && + (areEqual(selectedCells[i], primarySelectedCell) || + areEqual(selectedCells[i], secondarySelectedCell)) + ) { + selectedCells.splice(i, 1); + primarySelectedCell = secondarySelectedCell = selectedCells[selectedCells.length - 1]; + } else { + selectedCells.splice(i, 1); + } + } else if (selectedCols.includes(cell.column)) { + selectedCols.splice(selectedCols.indexOf(cell.column), 1); + // add all the cells in the column to selectedCells except the one we clicked on + for (let i = 0; i < $chunk.keyidxs.length; i++) { + if (i !== cell.posidx) { + selectedCells.push(getCell(cell.column, i)); + } + } + } else if (selectedRows.includes(cell.keyidx)) { + selectedRows.splice(selectedRows.indexOf(cell.keyidx), 1); + // add all the cells in the row to selectedCells except the one we clicked on + for (let i = 0; i < $schema.columns.length; i++) { + if ($schema.columns[i].name !== cell.column) { + selectedCells.push(getCell($schema.columns[i].name, cell.posidx)); + } + } + } + } + } + + activeCells = []; + const s = selectedCells.length > 0 ? selectedCells : [primarySelectedCell]; if (onSelectCells && onSelectCells.endpointId) { dispatch(onSelectCells.endpointId, { detail: { selected: s } }); @@ -414,7 +439,7 @@ selectedCells.some((c) => c.column === column) || selectedRows.length > 0 ) - return 'bg-violet-200 '; + return 'bg-violet-200 font-bold '; return ''; } @@ -431,7 +456,7 @@ selectedCells.some((c) => c.keyidx === keyidx) || selectedCols.length > 0 ) - return 'bg-violet-200 '; + return 'bg-violet-200 font-bold '; return ''; } @@ -439,18 +464,23 @@ * Helper function that returns a number representing the ways a cell has * been selected or not. * - * - one's place: selectedCells (0 or 1) + * - one's place: number of occurences in selectedCells * - ten's place: selectedCols (0 or 10) * - hundred's place: selectedRows (0 or 100) + * - thousand's place: activeCells (0 or 1000) * * @param column * @param keyidx + * @param countActive */ - function getSelectedBitmap(column: string, keyidx: number) { + function getSelectedBitmap(column: string, keyidx: number, countActive = true) { return ( - (selectedCells.some((c) => c.column === column && c.keyidx === keyidx) ? 1 : 0) + + selectedCells.filter((c) => c.column === column && c.keyidx === keyidx).length + (selectedCols.includes(column) ? 10 : 0) + - (selectedRows.includes(keyidx) ? 100 : 0) + (selectedRows.includes(keyidx) ? 100 : 0) + + (countActive && activeCells.some((c) => c.column === column && c.keyidx === keyidx) + ? 1000 + : 0) ); } @@ -458,14 +488,15 @@ * Helper function that returns a string of classes for a cell based on * how it has been selected. * - * NOTE: The params selectedCells, selectedCols, and selectedRows are - * included so that this funciton is called reactively whenever any of those - * (or primarySelectedCell) changes. + * NOTE: The params activeCells, selectedCells, selectedCols, and + * selectedRows are included so that this funciton is called reactively + * whenever any of those (or primarySelectedCell) changes. * * @param column * @param keyidx * @param posidx * @param primarySelectedCell + * @param activeCells * @param selectedCells * @param selectedCols * @param selectedRows @@ -475,6 +506,7 @@ keyidx: number, posidx: number, primarySelectedCell: Cell, + activeCells: Array, selectedCells: Array, selectedCols: Array, selectedRows: Array @@ -484,7 +516,14 @@ // Determine background color if (bitmap > 0) { - const count = bitmap.toString().split('1').length - 1; + // sum the digits in the bitmap + const count = bitmap + .toString() + .split('') + .map(Number) + .reduce(function (a, b) { + return a + b; + }, 0); classes += `bg-violet-${count}00 `; } @@ -502,7 +541,8 @@ classes += 'border-t-violet-600 '; } - if (bitmap > 0 && posidx === $chunk.keyidxs.length - 1) classes += 'border-b border-b-violet-600 '; + if (bitmap > 0 && posidx === $chunk.keyidxs.length - 1) + classes += 'border-b border-b-violet-600 '; const colidx = col2idx(column); if (colidx > 0) { @@ -512,7 +552,8 @@ classes += 'border-l-violet-600 '; } - if (bitmap > 0 && colidx === $schema.columns.length - 1) classes += 'border-r border-r-violet-600 '; + if (bitmap > 0 && colidx === $schema.columns.length - 1) + classes += 'border-r border-r-violet-600 '; } // Determine text color @@ -530,10 +571,9 @@ if (e.key === 'a' && e.metaKey) { e.preventDefault(); - selectRange( - getCell($schema.columns[0].name, 0), - getCell($schema.columns[$schema.columns.length - 1].name, $chunk.keyidxs.length - 1) - ); + selectedCells = []; + selectedCols = $schema.columns.map((c) => c.name); + selectedRows = $chunk.keyidxs.map((k) => parseInt(k)); } else if (e.key === 'ArrowDown') { e.preventDefault(); if (e.metaKey) { @@ -787,6 +827,7 @@ keyidx, posidx, primarySelectedCell, + activeCells, selectedCells, selectedCols, selectedRows From 584eb7d614b76ff6c1ce47815870019716366e67 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Sat, 13 May 2023 13:50:33 -0700 Subject: [PATCH 21/55] Fix edge case with metaKey --- .../app/src/lib/component/core/table/Table.svelte | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index cd433e1e6..ca83ccb09 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -227,6 +227,8 @@ secondarySelectedCell = cell; selectRange(primarySelectedCell, secondarySelectedCell); } else if (e.metaKey) { + if (getSelectedBitmap(cell.column, cell.keyidx) === 0) + selectedCells.push(primarySelectedCell); primarySelectedCell = secondarySelectedCell = cell; activeCells.push(cell); } else { @@ -297,7 +299,7 @@ areEqual(selectedCells[i], secondarySelectedCell)) ) { selectedCells.splice(i, 1); - primarySelectedCell = secondarySelectedCell = selectedCells[selectedCells.length - 1]; + primarySelectedCell = secondarySelectedCell = selectedCells[0]; } else { selectedCells.splice(i, 1); } @@ -309,6 +311,9 @@ selectedCells.push(getCell(cell.column, i)); } } + if (selectedCells.length > 0) + primarySelectedCell = secondarySelectedCell = selectedCells[0]; + else primarySelectedCell = secondarySelectedCell = getCell(cell.column, cell.posidx); } else if (selectedRows.includes(cell.keyidx)) { selectedRows.splice(selectedRows.indexOf(cell.keyidx), 1); // add all the cells in the row to selectedCells except the one we clicked on @@ -317,6 +322,9 @@ selectedCells.push(getCell($schema.columns[i].name, cell.posidx)); } } + if (selectedCells.length > 0) + primarySelectedCell = secondarySelectedCell = selectedCells[0]; + else primarySelectedCell = secondarySelectedCell = getCell(cell.column, cell.posidx); } } } From 7b078ab4bc4b5dfd65fc66370a236c7099867672 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Sun, 14 May 2023 14:34:24 -0700 Subject: [PATCH 22/55] Filter selected cells to be unique --- .../app/src/lib/component/core/table/Table.svelte | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index ca83ccb09..55491b351 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -331,7 +331,12 @@ activeCells = []; - const s = selectedCells.length > 0 ? selectedCells : [primarySelectedCell]; + let s = [primarySelectedCell]; + if (selectedCells.length > 0) { + // filter out duplicate cells + s = selectedCells.filter((cell, i, arr) => arr.findIndex((c) => areEqual(c, cell)) === i); + } + console.log('s', s); if (onSelectCells && onSelectCells.endpointId) { dispatch(onSelectCells.endpointId, { detail: { selected: s } }); } @@ -758,11 +763,13 @@ on:click={(e) => onClickCol(e, column.name)} >
- {#if column.name === $schema.primaryKey} - + {:else} + Date: Sun, 14 May 2023 14:56:47 -0700 Subject: [PATCH 23/55] Work on selection edge cases --- .../src/lib/component/core/table/Table.svelte | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 55491b351..d53250747 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -227,7 +227,7 @@ secondarySelectedCell = cell; selectRange(primarySelectedCell, secondarySelectedCell); } else if (e.metaKey) { - if (getSelectedBitmap(cell.column, cell.keyidx) === 0) + if (getSelectedBitmap(primarySelectedCell.column, primarySelectedCell.keyidx) === 0) selectedCells.push(primarySelectedCell); primarySelectedCell = secondarySelectedCell = cell; activeCells.push(cell); @@ -336,7 +336,6 @@ // filter out duplicate cells s = selectedCells.filter((cell, i, arr) => arr.findIndex((c) => areEqual(c, cell)) === i); } - console.log('s', s); if (onSelectCells && onSelectCells.endpointId) { dispatch(onSelectCells.endpointId, { detail: { selected: s } }); } @@ -477,10 +476,10 @@ * Helper function that returns a number representing the ways a cell has * been selected or not. * - * - one's place: number of occurences in selectedCells - * - ten's place: selectedCols (0 or 10) - * - hundred's place: selectedRows (0 or 100) - * - thousand's place: activeCells (0 or 1000) + * - ones's place: selectedCols (0 or 1) + * - ten's place: selectedRows (0 or 10) + * - hundred's place: activeCells (0 or 100) + * - thousand's place: number of occurences in selectedCells * * @param column * @param keyidx @@ -488,12 +487,32 @@ */ function getSelectedBitmap(column: string, keyidx: number, countActive = true) { return ( - selectedCells.filter((c) => c.column === column && c.keyidx === keyidx).length + - (selectedCols.includes(column) ? 10 : 0) + - (selectedRows.includes(keyidx) ? 100 : 0) + + (selectedCols.includes(column) ? 1 : 0) + + (selectedRows.includes(keyidx) ? 10 : 0) + (countActive && activeCells.some((c) => c.column === column && c.keyidx === keyidx) - ? 1000 - : 0) + ? 100 + : 0) + + selectedCells.filter((c) => c.column === column && c.keyidx === keyidx).length * 1000 + ); + } + + /** + * Helper function that returns the number of times a cell is selected. + * @param column + * @param keyidx + * @param posidx + * @param primarySelectedCell + * @param activeCells + * @param selectedCells + * @param selectedCols + * @param selectedRows + */ + function getSelectedCount(column: string, keyidx: number) { + return ( + (selectedCols.includes(column) ? 1 : 0) + + (selectedRows.includes(keyidx) ? 1 : 0) + + (activeCells.some((c) => c.column === column && c.keyidx === keyidx) ? 1 : 0) + + selectedCells.filter((c) => c.column === column && c.keyidx === keyidx).length ); } @@ -529,15 +548,8 @@ // Determine background color if (bitmap > 0) { - // sum the digits in the bitmap - const count = bitmap - .toString() - .split('') - .map(Number) - .reduce(function (a, b) { - return a + b; - }, 0); - classes += `bg-violet-${count}00 `; + // the max tailwind color is 900, so we cap the count at 9 + classes += `bg-violet-${Math.min(getSelectedCount(column, keyidx), 9)}00 `; } // Determine borders From 255424755b069b56da5ee37b07de1f0fcb23f1d2 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Sun, 14 May 2023 15:02:33 -0700 Subject: [PATCH 24/55] Add Tab keyboard shortcut --- .../src/lib/component/core/table/Table.svelte | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index d53250747..153c187d1 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -731,6 +731,26 @@ } selectedCols = []; selectedRows = []; + } else if (e.key === 'Tab') { + e.preventDefault(); + if (e.shiftKey) { + if (colidx > 0) { + primarySelectedCell = secondarySelectedCell = getCell( + $schema.columns[colidx - 1].name, + posidx + ); + } + } else { + if (colidx < $schema.columns.length - 1) { + primarySelectedCell = secondarySelectedCell = getCell( + $schema.columns[colidx + 1].name, + posidx + ); + } + } + selectedCells = []; + selectedCols = []; + selectedRows = []; } }); From af897fe1cf56a8a4abd2548c240611dd00a84877 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Sun, 14 May 2023 15:04:58 -0700 Subject: [PATCH 25/55] Add todo --- .../interactive/app/src/lib/component/core/table/Table.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 153c187d1..50bfcf237 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -733,6 +733,7 @@ selectedRows = []; } else if (e.key === 'Tab') { e.preventDefault(); + // TODO: if there are selected cells, loop through them if (e.shiftKey) { if (colidx > 0) { primarySelectedCell = secondarySelectedCell = getCell( From 886ca3a314583c25b9a5a8761f09d81472800fdd Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Tue, 23 May 2023 16:05:33 -0700 Subject: [PATCH 26/55] Make text editable with some bugs --- .../src/lib/component/core/table/Table.svelte | 69 ++++++++++++++----- .../src/lib/component/core/text/Text.svelte | 53 ++++++++++---- 2 files changed, 92 insertions(+), 30 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 50bfcf237..66bddf15c 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -22,6 +22,8 @@ export let page: number = 0; export let perPage: number = 50; + let editMode: boolean = false; + let editValue: string = ''; export let onEdit: Endpoint; export let primarySelectedCell: Cell = { column: '', keyidx: -1, posidx: -1, value: '' }; @@ -222,6 +224,20 @@ const selectCellMethods = { mousedown(cell: Cell) { + if (editMode) { + if (onEdit && onEdit.endpointId) { + const { column, keyidx, posidx } = primarySelectedCell; + dispatch(onEdit.endpointId, { + detail: { + column, + keyidx, + posidx, + value: editValue + } + }); + } + editMode = false; + } return (e: MouseEvent) => { if (e.shiftKey) { secondarySelectedCell = cell; @@ -555,13 +571,18 @@ // Determine borders if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { classes += 'border-2 border-violet-600 '; + // TODO: add a shadow if the cell is being edited + // if (editMode) classes += 'shadow-md shadow-violet-500 '; } else { // border width of 1px, default color slate classes += 'border-t border-l border-slate-300 '; if (posidx > 0) { + // TODO: Don't add border if the cell above is primarySelectedCell + // if (!areEqual(getCell(column, parseInt($chunk.keyidxs[posidx - 1])), primarySelectedCell)) { const bitmapAbove = getSelectedBitmap(column, parseInt($chunk.keyidxs[posidx - 1])); if (bitmap !== bitmapAbove) classes += 'border-t-violet-600 '; + // } } else if (bitmap > 0 && posidx === 0) { classes += 'border-t-violet-600 '; } @@ -571,8 +592,11 @@ const colidx = col2idx(column); if (colidx > 0) { + // TODO: Don't add border if the cell on the left is primarySelectedCell + // if (!areEqual(getCell($chunk.columns[colidx - 1], keyidx), primarySelectedCell)) { const bitmapLeft = getSelectedBitmap($chunk.columns[colidx - 1], keyidx); if (bitmap !== bitmapLeft) classes += 'border-l-violet-600 '; + // } } else if (bitmap > 0 && colidx === 0) { classes += 'border-l-violet-600 '; } @@ -672,6 +696,7 @@ selectedCols = []; selectedRows = []; } else if (e.key === 'ArrowLeft') { + if (editMode) return; e.preventDefault(); if (e.metaKey) { if (e.shiftKey) { @@ -699,6 +724,7 @@ selectedCols = []; selectedRows = []; } else if (e.key === 'ArrowRight') { + if (editMode) return; e.preventDefault(); if (e.metaKey) { if (e.shiftKey) { @@ -752,6 +778,24 @@ selectedCells = []; selectedCols = []; selectedRows = []; + } else if (e.key === 'Enter') { + e.preventDefault(); + if (editMode) { + if (onEdit && onEdit.endpointId) { + const { column, keyidx, posidx } = primarySelectedCell; + dispatch(onEdit.endpointId, { + detail: { + column, + keyidx, + posidx, + value: editValue + } + }); + } + editMode = false; + } else { + editMode = true; + } } }); @@ -886,22 +930,19 @@ posidx: posidx, value: $chunk.getCell(rowi, col.name).data })} + on:dblclick={() => { + console.log('double click'); + editMode = true; + }} column={col.name} {keyidx} > { - dispatch(onEdit.endpointId, { - detail: { - column: col.name, - keyidx: keyidx, - posidx: posidx, - value: e.detail.value - } - }); - }} + editable={editMode && + col.name === primarySelectedCell.column && + keyidx === primarySelectedCell.keyidx} + on:edit={(e) => (editValue = e.detail.value)} />
{/each} @@ -936,9 +977,3 @@
- - diff --git a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte index 87f1d3e8a..be7899686 100644 --- a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte +++ b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte @@ -3,21 +3,48 @@ export let data: any; export let editable: boolean = false; + let editablePrev = editable; export let classes: string = ''; const cellEdit: CallableFunction = getContext('cellEdit'); + + let editableCell: HTMLDivElement; + + $: setFocus(editable); + + function setFocus(focus: boolean) { + if (editablePrev === focus) return; + editablePrev = focus; + + console.log('Trying to setFocus:', data, focus, editableCell); + if (editableCell) { + if (!focus) { + editableCell.blur(); + return; + } + + editableCell.focus(); + + // Set the cursor to the end of the div. From + // https://stackoverflow.com/a/3866442. Supported on Firefox, + // Chrome, Opera, Safari, IE 9+ + let range = document.createRange(); + range.selectNodeContents(editableCell); + range.collapse(false); + + let selection = window.getSelection(); + if (selection) { + selection.removeAllRanges(); + selection.addRange(range); + } + } + } -{#if editable} - { - cellEdit(data); - }} - bind:value={data} - /> -{:else} -
- {data} -
-{/if} +
cellEdit(data)} +/> From 3e48d77b36c9ba3e19a4fa02561091f3a10604c6 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Tue, 23 May 2023 16:17:09 -0700 Subject: [PATCH 27/55] Make text purple when editing --- .../app/src/lib/component/core/table/Table.svelte | 11 +++++++---- .../app/src/lib/component/core/text/Text.svelte | 3 +-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 66bddf15c..ac0bac6ef 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -557,7 +557,8 @@ activeCells: Array, selectedCells: Array, selectedCols: Array, - selectedRows: Array + selectedRows: Array, + editMode: boolean ) { let classes = ''; const bitmap = getSelectedBitmap(column, keyidx); @@ -572,7 +573,7 @@ if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { classes += 'border-2 border-violet-600 '; // TODO: add a shadow if the cell is being edited - // if (editMode) classes += 'shadow-md shadow-violet-500 '; + if (editMode) classes += 'text-violet-500 '; } else { // border width of 1px, default color slate classes += 'border-t border-l border-slate-300 '; @@ -795,6 +796,7 @@ editMode = false; } else { editMode = true; + editValue = primarySelectedCell.value; } } }); @@ -922,7 +924,8 @@ activeCells, selectedCells, selectedCols, - selectedRows + selectedRows, + editMode )} on:mousedown|preventDefault={selectCellMethods.mousedown({ column: col.name, @@ -931,8 +934,8 @@ value: $chunk.getCell(rowi, col.name).data })} on:dblclick={() => { - console.log('double click'); editMode = true; + editValue = primarySelectedCell.value; }} column={col.name} {keyidx} diff --git a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte index be7899686..7b86c5bbb 100644 --- a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte +++ b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte @@ -16,7 +16,6 @@ if (editablePrev === focus) return; editablePrev = focus; - console.log('Trying to setFocus:', data, focus, editableCell); if (editableCell) { if (!focus) { editableCell.blur(); @@ -42,7 +41,7 @@
Date: Wed, 24 May 2023 18:23:28 -0700 Subject: [PATCH 28/55] Work on adding minWidth and minHeight --- .../src/lib/component/core/table/Table.svelte | 102 ++++++++++-------- .../src/lib/component/core/text/Text.svelte | 46 ++++++-- .../src/lib/shared/DynamicComponent.svelte | 8 +- .../app/src/lib/shared/cell/Cell.svelte | 22 +++- .../app/src/lib/utils/dataframe.ts | 1 + 5 files changed, 117 insertions(+), 62 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index ac0bac6ef..b8e8a7949 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -26,12 +26,12 @@ let editValue: string = ''; export let onEdit: Endpoint; - export let primarySelectedCell: Cell = { column: '', keyidx: -1, posidx: -1, value: '' }; - export let secondarySelectedCell: Cell = { column: '', keyidx: -1, posidx: -1, value: '' }; - export let activeCells: Array = []; // the cells currently being interacted with - export let selectedCells: Array = []; - export let selectedCols: Array = []; - export let selectedRows: Array = []; + let primarySelectedCell: Cell = { column: '', keyidx: -1, posidx: -1, value: '' }; + let secondarySelectedCell: Cell = { column: '', keyidx: -1, posidx: -1, value: '' }; + let activeCells: Array = []; // the cells currently being interacted with + let selectedCells: Array = []; + let selectedCols: Array = []; + let selectedRows: Array = []; export let onSelectCells: Endpoint; export let onSelectCols: Endpoint; export let onSelectRows: Endpoint; @@ -64,15 +64,11 @@ df: df, formatter: 'icon' }).then((newSchema) => { + console.log('Fetching schema'); schema.set(newSchema); if (columnWidths.length === 0) { columnWidths = Array(newSchema.columns.length).fill(100); - } - if (primarySelectedCell.posidx === -1) { - primarySelectedCell = secondarySelectedCell = { - ...primarySelectedCell, - column: newSchema.columns[0].name - }; + columnWidths[0] = 200; } }); @@ -85,15 +81,8 @@ console.log('Fetching chunk'); chunk.set(newChunk); if (rowHeights.length === 0) { - rowHeights = Array(newChunk.keyidxs.length).fill(22); // same as text-sm + 2 - } - if (primarySelectedCell.posidx === -1) { - primarySelectedCell = secondarySelectedCell = { - ...primarySelectedCell, - keyidx: parseInt(newChunk.keyidxs[0]), - posidx: 0, - value: newChunk.getCell(0, $schema.columns[0].name).data - }; + rowHeights = Array(newChunk.keyidxs.length).fill(24); // same as text-sm + 4 + rowHeights[0] = 32; } }); @@ -571,9 +560,8 @@ // Determine borders if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { - classes += 'border-2 border-violet-600 '; - // TODO: add a shadow if the cell is being edited - if (editMode) classes += 'text-violet-500 '; + if (editMode) classes += 'text-violet-500 overflow-visible '; + else classes += 'border-2 border-violet-600 '; } else { // border width of 1px, default color slate classes += 'border-t border-l border-slate-300 '; @@ -614,6 +602,35 @@ return classes; } + /** + * Start editing the current cell. + */ + function startEdit() { + editMode = true; + editValue = primarySelectedCell.value; + } + + /** + * Finish editing. If callOnEdit is true, save the edit by calling the + * onEdit endpoint. + * @param callOnEdit Whether or not to call the onEdit endpoint. + */ + function endEdit(callOnEdit: boolean = true) { + if (callOnEdit && onEdit && onEdit.endpointId) { + const { column, keyidx, posidx } = primarySelectedCell; + dispatch(onEdit.endpointId, { + detail: { + column, + keyidx, + posidx, + value: editValue + } + }); + } + editMode = false; + if (!callOnEdit) console.log('primarySelectedCell:', primarySelectedCell); + } + // Define keyboard shortcuts window.addEventListener('keydown', (e) => { const colidx = col2idx(primarySelectedCell.column); @@ -626,6 +643,7 @@ selectedRows = $chunk.keyidxs.map((k) => parseInt(k)); } else if (e.key === 'ArrowDown') { e.preventDefault(); + if (editMode) endEdit(); if (e.metaKey) { if (e.shiftKey) { secondarySelectedCell = getCell(secondarySelectedCell.column, $chunk.keyidxs.length - 1); @@ -662,6 +680,7 @@ selectedRows = []; } else if (e.key === 'ArrowUp') { e.preventDefault(); + if (editMode) endEdit(); if (e.metaKey) { if (e.shiftKey) { secondarySelectedCell = getCell(secondarySelectedCell.column, 0); @@ -760,6 +779,7 @@ selectedRows = []; } else if (e.key === 'Tab') { e.preventDefault(); + if (editMode) endEdit(); // TODO: if there are selected cells, loop through them if (e.shiftKey) { if (colidx > 0) { @@ -781,23 +801,12 @@ selectedRows = []; } else if (e.key === 'Enter') { e.preventDefault(); - if (editMode) { - if (onEdit && onEdit.endpointId) { - const { column, keyidx, posidx } = primarySelectedCell; - dispatch(onEdit.endpointId, { - detail: { - column, - keyidx, - posidx, - value: editValue - } - }); - } - editMode = false; - } else { - editMode = true; - editValue = primarySelectedCell.value; - } + if (editMode) endEdit(); + else startEdit(); + } else if (e.key === 'Escape') { + // TODO: make the cell revert immediately instead of on refresh + e.preventDefault(); + if (editMode) endEdit(false); } }); @@ -913,9 +922,9 @@
- {#each $chunk.columnInfos as col} + {#each $chunk.columnInfos as col, col_index}
{ - editMode = true; - editValue = primarySelectedCell.value; - }} + on:dblclick={startEdit} column={col.name} {keyidx} > @@ -945,6 +951,8 @@ editable={editMode && col.name === primarySelectedCell.column && keyidx === primarySelectedCell.keyidx} + minWidth={columnWidths[col_index]} + minHeight={rowHeights[posidx]} on:edit={(e) => (editValue = e.detail.value)} />
diff --git a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte index 7b86c5bbb..0e73b1d43 100644 --- a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte +++ b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte @@ -1,5 +1,7 @@ -
cellEdit(data)} -/> +
+
cellEdit(data)} + /> +
diff --git a/meerkat/interactive/app/src/lib/shared/DynamicComponent.svelte b/meerkat/interactive/app/src/lib/shared/DynamicComponent.svelte index 892abef68..a9c127eb8 100644 --- a/meerkat/interactive/app/src/lib/shared/DynamicComponent.svelte +++ b/meerkat/interactive/app/src/lib/shared/DynamicComponent.svelte @@ -7,7 +7,7 @@ export let props: any; export let slots: any = []; - console.log("props", props) + console.log('props', props); let component: ComponentType; onMount(async () => { @@ -18,18 +18,16 @@ return; } }); - $:{ + $: { component = components[name]; } - - {#if component} {#each slots as slot} - + {/each} {/if} diff --git a/meerkat/interactive/app/src/lib/shared/cell/Cell.svelte b/meerkat/interactive/app/src/lib/shared/cell/Cell.svelte index 6cd3fee85..f801748dc 100644 --- a/meerkat/interactive/app/src/lib/shared/cell/Cell.svelte +++ b/meerkat/interactive/app/src/lib/shared/cell/Cell.svelte @@ -11,12 +11,25 @@ export let cellProps: object = {}; export let cellDataProp: string = 'data'; export let editable: boolean = false; + export let minWidth: number = 0; + export let minHeight: number = 0; const dispatch = createEventDispatcher(); setContext('cellEdit', (data: any) => { dispatch('edit', { value: data }); }); + let cellInfoObj = { + ...cellInfo, + style: { minWidth: writable(minWidth), minHeight: writable(minHeight) } + }; + $: { + cellInfoObj.style.minWidth.set(minWidth); + cellInfoObj.style.minHeight.set(minHeight); + console.log('minWidth:', minWidth, 'minHeight:', minHeight); + } + setContext('cellInfo', cellInfoObj); + // need to actually create a new object, since we don't want to modify the // cell_props that were passed in $: { @@ -35,7 +48,12 @@ } } } - - + diff --git a/meerkat/interactive/app/src/lib/utils/dataframe.ts b/meerkat/interactive/app/src/lib/utils/dataframe.ts index aad61c1fc..c449c3451 100644 --- a/meerkat/interactive/app/src/lib/utils/dataframe.ts +++ b/meerkat/interactive/app/src/lib/utils/dataframe.ts @@ -31,6 +31,7 @@ export interface CellInfo { dfRefId: string; columnName: string; row: number; + style: any; } /** From 22a7b3065d43b7263c82c6c0f644c4f597923b9a Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Wed, 24 May 2023 18:25:54 -0700 Subject: [PATCH 29/55] Fix minWidth minHeight bug --- .../interactive/app/src/lib/component/core/text/Text.svelte | 5 ++--- meerkat/interactive/app/src/lib/shared/cell/Cell.svelte | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte index 0e73b1d43..efe9827d6 100644 --- a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte +++ b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte @@ -10,14 +10,13 @@ const cellEdit: CallableFunction = getContext('cellEdit'); - // Build up style if it exists in the context const cellInfo: CellInfo = getContext('cellInfo'); + // Build up style if it exists in the context let style: string = ''; - if (editable && cellInfo) { + if (cellInfo) { if (cellInfo.style) { if (cellInfo.style.minWidth) { const unsubscribe = cellInfo.style.minWidth.subscribe((value: number) => { - console.log('minWidth:', value); style = style.replace(/min-width: \d+px;/, ''); style += `min-width: ${value}px;`; }); diff --git a/meerkat/interactive/app/src/lib/shared/cell/Cell.svelte b/meerkat/interactive/app/src/lib/shared/cell/Cell.svelte index f801748dc..bcb6f80be 100644 --- a/meerkat/interactive/app/src/lib/shared/cell/Cell.svelte +++ b/meerkat/interactive/app/src/lib/shared/cell/Cell.svelte @@ -26,7 +26,6 @@ $: { cellInfoObj.style.minWidth.set(minWidth); cellInfoObj.style.minHeight.set(minHeight); - console.log('minWidth:', minWidth, 'minHeight:', minHeight); } setContext('cellInfo', cellInfoObj); From 116430582dd378894728ef0b84dbc841c8117f45 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Wed, 24 May 2023 18:36:07 -0700 Subject: [PATCH 30/55] Add second border (outline) when editing --- .../interactive/app/src/lib/component/core/text/Text.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte index efe9827d6..ff96c4f53 100644 --- a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte +++ b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte @@ -68,7 +68,9 @@
Date: Wed, 24 May 2023 18:49:03 -0700 Subject: [PATCH 31/55] Work on box margin style, will need to fix --- .../app/src/lib/component/core/table/Table.svelte | 5 +++-- .../interactive/app/src/lib/component/core/text/Text.svelte | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index b8e8a7949..f22cad845 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -560,8 +560,9 @@ // Determine borders if (primarySelectedCell.column === column && primarySelectedCell.keyidx === keyidx) { - if (editMode) classes += 'text-violet-500 overflow-visible '; - else classes += 'border-2 border-violet-600 '; + // TODO: styles are awkwardly split between here and Text.svelte + if (editMode) classes += 'overflow-visible -ml-px -mt-px '; + else classes += 'border-2 border-violet-600 -ml-px -mt-px '; } else { // border width of 1px, default color slate classes += 'border-t border-l border-slate-300 '; diff --git a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte index ff96c4f53..13ac402a7 100644 --- a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte +++ b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte @@ -69,7 +69,7 @@ class={classes + ' px-1 outline-none whitespace-nowrap ' + (editable - ? 'bg-white border-double border-2 border-red-500 outline-2 outline-offset-0 outline-red-300 w-fit z-50' + ? 'bg-white border-double border-2 border-violet-600 outline-2 outline-offset-0 outline-violet-300 w-fit z-50' : '')} {style} contenteditable="true" From fb70c7a986180b67f129ce105cefb77806f0d8e6 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Wed, 24 May 2023 19:10:38 -0700 Subject: [PATCH 32/55] Fix cell overlap when editing --- .../interactive/app/src/lib/component/core/text/Text.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte index 13ac402a7..81b1cd7f6 100644 --- a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte +++ b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte @@ -64,12 +64,12 @@ } -
+
Date: Wed, 24 May 2023 19:23:19 -0700 Subject: [PATCH 33/55] Try to work on newlines when editing a cell --- .../src/lib/component/core/table/Table.svelte | 18 +++++++++++++----- .../src/lib/component/core/text/Text.svelte | 3 ++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index f22cad845..ab965f637 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -643,8 +643,8 @@ selectedCols = $schema.columns.map((c) => c.name); selectedRows = $chunk.keyidxs.map((k) => parseInt(k)); } else if (e.key === 'ArrowDown') { + if (editMode) return; e.preventDefault(); - if (editMode) endEdit(); if (e.metaKey) { if (e.shiftKey) { secondarySelectedCell = getCell(secondarySelectedCell.column, $chunk.keyidxs.length - 1); @@ -680,8 +680,8 @@ selectedCols = []; selectedRows = []; } else if (e.key === 'ArrowUp') { + if (editMode) return; e.preventDefault(); - if (editMode) endEdit(); if (e.metaKey) { if (e.shiftKey) { secondarySelectedCell = getCell(secondarySelectedCell.column, 0); @@ -801,9 +801,17 @@ selectedCols = []; selectedRows = []; } else if (e.key === 'Enter') { - e.preventDefault(); - if (editMode) endEdit(); - else startEdit(); + if (editMode) { + if (e.ctrlKey || e.altKey || e.metaKey) { + return; + } else { + e.preventDefault(); + endEdit(); + } + } else { + e.preventDefault(); + startEdit(); + } } else if (e.key === 'Escape') { // TODO: make the cell revert immediately instead of on refresh e.preventDefault(); diff --git a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte index 81b1cd7f6..3e12312dd 100644 --- a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte +++ b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte @@ -64,12 +64,13 @@ } +
Date: Thu, 1 Jun 2023 18:49:36 -0700 Subject: [PATCH 34/55] Work on reducing number of fetches --- .../src/lib/component/core/table/Table.svelte | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index ab965f637..4cfa8203e 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -66,10 +66,10 @@ }).then((newSchema) => { console.log('Fetching schema'); schema.set(newSchema); - if (columnWidths.length === 0) { - columnWidths = Array(newSchema.columns.length).fill(100); - columnWidths[0] = 200; - } + // if (columnWidths.length === 0) { + // columnWidths = Array(newSchema.columns.length).fill(100); + // columnWidths[0] = 200; + // } }); $: fetchChunk({ @@ -80,15 +80,15 @@ }).then((newChunk) => { console.log('Fetching chunk'); chunk.set(newChunk); - if (rowHeights.length === 0) { - rowHeights = Array(newChunk.keyidxs.length).fill(24); // same as text-sm + 4 - rowHeights[0] = 32; - } + // if (rowHeights.length === 0) { + // rowHeights = Array(newChunk.keyidxs.length).fill(24); // same as text-sm + 4 + // rowHeights[0] = 32; + // } }); - let columnWidths: Array = []; + let columnWidths: Array = Array(17).fill(100); //[]; let columnUnit: string = 'px'; - let rowHeights: Array = []; + let rowHeights: Array = Array(50).fill(24); //[]; let rowUnit: string = 'px'; let resizeProps = { @@ -216,6 +216,7 @@ if (editMode) { if (onEdit && onEdit.endpointId) { const { column, keyidx, posidx } = primarySelectedCell; + primarySelectedCell.value = editValue; dispatch(onEdit.endpointId, { detail: { column, @@ -609,6 +610,8 @@ function startEdit() { editMode = true; editValue = primarySelectedCell.value; + console.log('startEdit, primarySelectedCell:', primarySelectedCell); + console.log('startEdit, editValue:', editValue); } /** @@ -617,8 +620,10 @@ * @param callOnEdit Whether or not to call the onEdit endpoint. */ function endEdit(callOnEdit: boolean = true) { + console.log('endEdit, editValue:', editValue); if (callOnEdit && onEdit && onEdit.endpointId) { const { column, keyidx, posidx } = primarySelectedCell; + primarySelectedCell.value = editValue; dispatch(onEdit.endpointId, { detail: { column, @@ -627,6 +632,8 @@ value: editValue } }); + // TODO: after this runs, we need to update primarySelectedCell with the edited value. Otherwise it holds state from the previous edit + console.log('endEdit, primarySelectedCell:', primarySelectedCell); } editMode = false; if (!callOnEdit) console.log('primarySelectedCell:', primarySelectedCell); @@ -962,7 +969,10 @@ keyidx === primarySelectedCell.keyidx} minWidth={columnWidths[col_index]} minHeight={rowHeights[posidx]} - on:edit={(e) => (editValue = e.detail.value)} + on:edit={(e) => { + console.log('e.detail.value:', e.detail.value); + editValue = e.detail.value; + }} />
{/each} From 14cbbaf51975c0f701d3020d4232fe4c033b33ad Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Sun, 4 Jun 2023 23:17:07 -0700 Subject: [PATCH 35/55] Work on min-content for wrappign --- .../src/lib/component/core/table/Table.svelte | 69 +++++++++++++------ .../src/lib/component/core/text/Text.svelte | 26 ++++--- 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 4cfa8203e..116238236 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -99,6 +99,18 @@ offset: 0 }; + /** + * Helper function to find the first ancestor of an element with a given + * class. + * + * @param el - The element to start searching from + * @param cls - The class to search for + */ + const findAncestor = (el: HTMLElement | null, cls: string) => { + while (el && (el = el.parentElement) && !el.classList.contains(cls)); + return el; + }; + const resizeMethods = { mousedown(direction: string, idx: number) { return (e: MouseEvent) => { @@ -106,6 +118,12 @@ resizeProps.direction = direction; resizeProps.idxBeingResized = idx; resizeProps.mouseStart = direction === 'x' ? e.x : e.y; + + const el = findAncestor(e.target as HTMLElement, 'header-cell'); + if (el) { + if (direction === 'x') columnWidths[idx] = el.offsetWidth; + else rowHeights[idx] = el.offsetHeight; + } resizeProps.sizeStart = direction === 'x' ? columnWidths[idx] : rowHeights[idx]; // Attach listeners for events @@ -835,8 +853,12 @@
(h === -1 ? 'min-content' : `${h}${rowUnit}`)) + .join(' ')};` + + `grid-template-columns: min-content ${columnWidths + .map((w) => (w === -1 ? 'min-content' : `${w}${columnUnit}`)) + .join(' ')};` + 'max-height:calc(100vh - 32px)'} > @@ -889,9 +911,7 @@
{ - columnWidths[col_index] = 100; - }} + on:dblclick|preventDefault={() => (columnWidths[col_index] = -1)} >
@@ -927,9 +947,7 @@
{ - rowHeights[posidx] = 20; - }} + on:dblclick|preventDefault={() => (rowHeights[posidx] = -1)} >
@@ -962,18 +980,27 @@ column={col.name} {keyidx} > - { - console.log('e.detail.value:', e.detail.value); - editValue = e.detail.value; - }} - /> + keyidx === primarySelectedCell.keyidx + ? ' z-10' + : '')} + > + { + console.log('e.detail.value:', e.detail.value); + editValue = e.detail.value; + }} + /> +
{/each} {/each} @@ -1000,9 +1027,9 @@ {/if}
- +
diff --git a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte index 3e12312dd..4d22b7c0f 100644 --- a/meerkat/interactive/app/src/lib/component/core/text/Text.svelte +++ b/meerkat/interactive/app/src/lib/component/core/text/Text.svelte @@ -65,17 +65,15 @@ -
-
cellEdit(data)} - /> -
+
cellEdit(data)} +/> From 03f353d6d793b6014ba857827564c27a5082b283 Mon Sep 17 00:00:00 2001 From: Dean Stratakos Date: Mon, 5 Jun 2023 09:47:00 -0700 Subject: [PATCH 36/55] Make buttons full width/height and centered --- .../interactive/app/src/lib/component/core/table/Table.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte index 116238236..8ab5299c0 100644 --- a/meerkat/interactive/app/src/lib/component/core/table/Table.svelte +++ b/meerkat/interactive/app/src/lib/component/core/table/Table.svelte @@ -885,7 +885,7 @@ >