From c664c3aafec611582edb25ee4334f36f6009a40d Mon Sep 17 00:00:00 2001 From: eireland Date: Tue, 3 Oct 2023 15:48:03 -0700 Subject: [PATCH 01/18] Keeps table headers in view as user scrolls up the table --- src/components/app.scss | 2 -- src/components/menu.scss | 4 +++- src/components/portrait-view.tsx | 8 ++++---- src/components/tables.scss | 35 +++++++++++++++++++++++++++++--- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/components/app.scss b/src/components/app.scss index 9444571..334ef50 100644 --- a/src/components/app.scss +++ b/src/components/app.scss @@ -1,7 +1,5 @@ .selectView { padding: 10px; - height: 100vh; - .buttons { display: flex; gap: 5px; diff --git a/src/components/menu.scss b/src/components/menu.scss index be18b42..f9416ed 100644 --- a/src/components/menu.scss +++ b/src/components/menu.scss @@ -4,6 +4,8 @@ gap: 25px; margin-bottom: 10px; margin-left: 10px; + position: sticky; + top: 0; .option { display: flex; @@ -14,4 +16,4 @@ select, input { margin-left: 5px; } -} \ No newline at end of file +} diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx index 1451ad6..e033475 100644 --- a/src/components/portrait-view.tsx +++ b/src/components/portrait-view.tsx @@ -1,11 +1,11 @@ -import React from "react"; +import React, { useEffect } from "react"; import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; import css from "./tables.scss"; export const PortraitView = (props: ITableProps) => { const {paddingStyle, mapCellsFromValues, mapHeadersFromValues, showHeaders, collectionClasses, - getClassName, selectedDataSet, collections, getValueLength} = props; + getClassName, selectedDataSet, collections, getValueLength} = props; const renderNestedTable = (parentColl: ICollection) => { const firstRowValues = parentColl.cases.map(caseObj => caseObj.values); @@ -14,7 +14,7 @@ export const PortraitView = (props: ITableProps) => { return ( <> - {selectedDataSet.name} + {selectedDataSet.name} {parentColl.name} @@ -80,7 +80,7 @@ export const PortraitView = (props: ITableProps) => { }; return ( -
+
{collections.length && collectionClasses.length && renderTable()}
); diff --git a/src/components/tables.scss b/src/components/tables.scss index 1f74ab5..05be27f 100644 --- a/src/components/tables.scss +++ b/src/components/tables.scss @@ -3,15 +3,16 @@ table { font-size: 8pt; margin: 0 auto; - border-collapse: collapse; + border-spacing: 0; th, td { - border-collapse: collapse; + border-spacing: 0; } tr { /*this is ignored but it's so a td element can take up the entire height of a row*/ height: 1px; + border-spacing: 0; } th { @@ -34,11 +35,12 @@ table { .mainTable { width: 90%; - margin-bottom: 10px; + margin-bottom: 20px; .mainHeader { th { background-color: #EFEFEF; + border: 1px solid #3c78d8; } } @@ -111,6 +113,13 @@ table { } &.portraitTable { + overflow: scroll; + .datasetNameHeader { + position: sticky; + top: 0; + z-index: 4; + } + &.collection0, .collection0, &.collection0>tbody>tr>td { border: 1px solid #3c78d8; } @@ -118,10 +127,16 @@ table { &.collection0>th, .collection0>th, th.collection0 { background-color: #CEE9FB; border: 1px solid #3c78d8; + position: sticky; + top: 16px; + z-index: 3; } .collection1>th, th.collection1 { background-color: #F7EABA; + position: sticky; + top: 32px; + z-index: 2; } .subTable.collection1, .collection1>tbody>tr>th, .collection1>tbody>tr>td { @@ -130,6 +145,9 @@ table { .collection2>th, th.collection2 { background-color: #CCBEE0; + position: sticky; + top: 48px; + z-index: 1; } .subTable.collection2, .collection2>tbody>tr>th, .collection2>tbody>tr>td { @@ -138,6 +156,9 @@ table { .collection3>th, th.collection3 { background-color: #BEE0CA; + position: sticky; + top: 64px; + z-index: 1; } .subTable.collection3, .collection3>tbody>tr>th, .collection3>tbody>tr>td { @@ -146,6 +167,9 @@ table { .collection4>th, th.collection4 { background-color: #E7C3A4; + position: sticky; + top: 80px; + z-index: 1; } .subTable.collection4, .collection4>tbody>tr>th, .collection4>tbody>tr>td { @@ -158,3 +182,8 @@ table { width: 100%; height: 100%; } + +.portraitTableWrapper { + overflow: auto; + height: 90vh; +} From 38ba8047817f7ab6b7454af8b1f963a656d4a83b Mon Sep 17 00:00:00 2001 From: eireland Date: Wed, 4 Oct 2023 06:57:23 -0700 Subject: [PATCH 02/18] change z-indexes for subtables --- src/components/tables.scss | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/tables.scss b/src/components/tables.scss index 05be27f..4c5433b 100644 --- a/src/components/tables.scss +++ b/src/components/tables.scss @@ -4,6 +4,7 @@ table { margin: 0 auto; border-spacing: 0; + border-collapse: collapse; th, td { border-spacing: 0; @@ -117,7 +118,7 @@ table { .datasetNameHeader { position: sticky; top: 0; - z-index: 4; + z-index: 6; } &.collection0, .collection0, &.collection0>tbody>tr>td { @@ -129,14 +130,14 @@ table { border: 1px solid #3c78d8; position: sticky; top: 16px; - z-index: 3; + z-index: 5; } .collection1>th, th.collection1 { background-color: #F7EABA; position: sticky; top: 32px; - z-index: 2; + z-index: 4; } .subTable.collection1, .collection1>tbody>tr>th, .collection1>tbody>tr>td { @@ -147,7 +148,7 @@ table { background-color: #CCBEE0; position: sticky; top: 48px; - z-index: 1; + z-index: 3; } .subTable.collection2, .collection2>tbody>tr>th, .collection2>tbody>tr>td { @@ -158,7 +159,7 @@ table { background-color: #BEE0CA; position: sticky; top: 64px; - z-index: 1; + z-index: 2; } .subTable.collection3, .collection3>tbody>tr>th, .collection3>tbody>tr>td { From 7e50606da5a1689e1b0657dd4e7fc7dd022bd32a Mon Sep 17 00:00:00 2001 From: eireland Date: Mon, 9 Oct 2023 14:45:46 -0230 Subject: [PATCH 03/18] border is shown when scrolled --- src/components/tables.scss | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/tables.scss b/src/components/tables.scss index 4c5433b..fd87a38 100644 --- a/src/components/tables.scss +++ b/src/components/tables.scss @@ -4,7 +4,7 @@ table { margin: 0 auto; border-spacing: 0; - border-collapse: collapse; + // border-collapse: collapse; th, td { border-spacing: 0; @@ -118,7 +118,7 @@ table { .datasetNameHeader { position: sticky; top: 0; - z-index: 6; + z-index: 10; } &.collection0, .collection0, &.collection0>tbody>tr>td { @@ -130,14 +130,14 @@ table { border: 1px solid #3c78d8; position: sticky; top: 16px; - z-index: 5; + z-index: 8; } .collection1>th, th.collection1 { background-color: #F7EABA; position: sticky; top: 32px; - z-index: 4; + z-index: 6; } .subTable.collection1, .collection1>tbody>tr>th, .collection1>tbody>tr>td { @@ -148,7 +148,7 @@ table { background-color: #CCBEE0; position: sticky; top: 48px; - z-index: 3; + z-index: 5; } .subTable.collection2, .collection2>tbody>tr>th, .collection2>tbody>tr>td { @@ -159,7 +159,7 @@ table { background-color: #BEE0CA; position: sticky; top: 64px; - z-index: 2; + z-index: 4; } .subTable.collection3, .collection3>tbody>tr>th, .collection3>tbody>tr>td { @@ -170,7 +170,7 @@ table { background-color: #E7C3A4; position: sticky; top: 80px; - z-index: 1; + z-index: 3; } .subTable.collection4, .collection4>tbody>tr>th, .collection4>tbody>tr>td { From 492f024200ad570f76739f8136d465cb49cc540e Mon Sep 17 00:00:00 2001 From: eireland Date: Tue, 10 Oct 2023 09:07:19 -0400 Subject: [PATCH 04/18] Fixes z-indices and disappearing headers when not showing all case headers --- src/components/nested-table.scss | 1 + src/components/portrait-view.tsx | 4 ++-- src/components/tables.scss | 17 +++++++++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/nested-table.scss b/src/components/nested-table.scss index f3bece4..f1bd342 100644 --- a/src/components/nested-table.scss +++ b/src/components/nested-table.scss @@ -1,3 +1,4 @@ .nestedTableWrapper { height: 100vh; + overflow: hidden; } diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx index 93c1e0a..1524e9f 100644 --- a/src/components/portrait-view.tsx +++ b/src/components/portrait-view.tsx @@ -38,9 +38,9 @@ export const PortraitView = (props: ITableProps) => { {index === 0 && {mapHeadersFromValues(collectionId, `first-row-${index}`, values)} - {showHeaders && ( + {showHeaders ? ( {children[0].collection.name} - )} + ) : } } diff --git a/src/components/tables.scss b/src/components/tables.scss index fd87a38..cf32776 100644 --- a/src/components/tables.scss +++ b/src/components/tables.scss @@ -129,14 +129,23 @@ table { background-color: #CEE9FB; border: 1px solid #3c78d8; position: sticky; + + } + + tr.collection0>th { top: 16px; + z-index: 9; + } + + tr.collection0:nth-child(3)>th { + top: 32px; z-index: 8; } .collection1>th, th.collection1 { background-color: #F7EABA; position: sticky; - top: 32px; + top: 48px; z-index: 6; } @@ -147,7 +156,7 @@ table { .collection2>th, th.collection2 { background-color: #CCBEE0; position: sticky; - top: 48px; + top: 64px; z-index: 5; } @@ -158,7 +167,7 @@ table { .collection3>th, th.collection3 { background-color: #BEE0CA; position: sticky; - top: 64px; + top: 80px; z-index: 4; } @@ -169,7 +178,7 @@ table { .collection4>th, th.collection4 { background-color: #E7C3A4; position: sticky; - top: 80px; + top: 96px; z-index: 3; } From b00f1c708bfb2f5b3517c5038ef1357181a749f7 Mon Sep 17 00:00:00 2001 From: eireland Date: Mon, 16 Oct 2023 10:10:55 -0700 Subject: [PATCH 05/18] Removes extra border caused by border-collapse: separate. --- src/components/tables.scss | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/components/tables.scss b/src/components/tables.scss index cf32776..2a765f4 100644 --- a/src/components/tables.scss +++ b/src/components/tables.scss @@ -115,6 +115,9 @@ table { &.portraitTable { overflow: scroll; + border-collapse: separate; + border-spacing: 0; + .datasetNameHeader { position: sticky; top: 0; @@ -129,14 +132,13 @@ table { background-color: #CEE9FB; border: 1px solid #3c78d8; position: sticky; - } tr.collection0>th { top: 16px; z-index: 9; } - + tr.collection0:nth-child(3)>th { top: 32px; z-index: 8; @@ -148,10 +150,12 @@ table { top: 48px; z-index: 6; } - - .subTable.collection1, .collection1>tbody>tr>th, .collection1>tbody>tr>td { + .collection1>tbody>tr>th, .collection1>tbody>tr>td { border: 1px solid #f1c232; } + .subTable.collection1 { + border: none; + } .collection2>th, th.collection2 { background-color: #CCBEE0; @@ -160,9 +164,12 @@ table { z-index: 5; } - .subTable.collection2, .collection2>tbody>tr>th, .collection2>tbody>tr>td { + .collection2>tbody>tr>th, .collection2>tbody>tr>td { border: 1px solid #8e7cc3; } + .subTable.collection2 { + border: none; + } .collection3>th, th.collection3 { background-color: #BEE0CA; @@ -171,9 +178,12 @@ table { z-index: 4; } - .subTable.collection3, .collection3>tbody>tr>th, .collection3>tbody>tr>td { + .collection3>tbody>tr>th, .collection3>tbody>tr>td { border: 1px solid #93c47d; } + .subTable.collection3 { + border: none; + } .collection4>th, th.collection4 { background-color: #E7C3A4; @@ -182,15 +192,20 @@ table { z-index: 3; } - .subTable.collection4, .collection4>tbody>tr>th, .collection4>tbody>tr>td { + .collection4>tbody>tr>th, .collection4>tbody>tr>td { border: 1px solid #f6b26b; } + .subTable.collection4 { + border: none; + } } } .subTable { width: 100%; height: 100%; + border-collapse: separate; + border-spacing: 0; } .portraitTableWrapper { From 1f6b3fae23098f5b12149bef5f4c456df5554e3b Mon Sep 17 00:00:00 2001 From: eireland Date: Mon, 16 Oct 2023 13:22:13 -0700 Subject: [PATCH 06/18] Fixes bugs created after merging main --- src/components/menu.scss | 2 -- src/components/nested-table.scss | 2 -- src/components/portrait-view.tsx | 12 ++++++------ src/components/tables.scss | 22 +++++++--------------- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/components/menu.scss b/src/components/menu.scss index f9416ed..fcdab5f 100644 --- a/src/components/menu.scss +++ b/src/components/menu.scss @@ -4,8 +4,6 @@ gap: 25px; margin-bottom: 10px; margin-left: 10px; - position: sticky; - top: 0; .option { display: flex; diff --git a/src/components/nested-table.scss b/src/components/nested-table.scss index 3d74bfa..a7e5204 100644 --- a/src/components/nested-table.scss +++ b/src/components/nested-table.scss @@ -1,6 +1,4 @@ .nestedTableWrapper { - height: 100vh; - overflow: hidden; width: 95vw; margin: 0 auto; } diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx index d4a77c7..6c45340 100644 --- a/src/components/portrait-view.tsx +++ b/src/components/portrait-view.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; import { DraggableTableContainer, DroppableTableData, DroppableTableHeader } from "./draggable-table-tags"; @@ -23,9 +23,9 @@ export const PortraitViewRow = (props: PortraitViewRowProps) => { {index === 0 && {mapHeadersFromValues(collectionId, `first-row-${index}`, values)} - {showHeaders && ( - {children[0].collection.name} - )} + {showHeaders ? ( + {children[0].collection.name} + ) : } } @@ -78,7 +78,7 @@ export const PortraitView = (props: ITableProps) => { - + @@ -99,7 +99,7 @@ export const PortraitView = (props: ITableProps) => { }; return ( -
+
{collections.length && collectionClasses.length && renderTable()}
); diff --git a/src/components/tables.scss b/src/components/tables.scss index 522025f..f089d14 100644 --- a/src/components/tables.scss +++ b/src/components/tables.scss @@ -1,18 +1,14 @@ table { font-family: 'Montserrat', sans-serif; font-size: 8pt; - margin: 0 auto; border-spacing: 0; - // border-collapse: collapse; th, td { border-spacing: 0; } tr { - /*this is ignored but it's so a td element can take up the entire height of a row*/ - height: 1px; border-spacing: 0; } @@ -22,7 +18,6 @@ table { td { padding: 1px 4px 2px 4px; - height: 1px; } } @@ -119,7 +114,6 @@ table { } &.portraitTable { - overflow: scroll; border-collapse: separate; border-spacing: 0; @@ -208,22 +202,15 @@ table { .subTable { width: 100%; - height: 100%; border-collapse: separate; border-spacing: 0; } -} - table.draggableTableContainer { width: 100%; border-spacing: 0px; - overflow: auto; - height: 90vh; - - > tr { - height: 1px; - } + overflow-x: auto; + overflow-y: hidden; td.draggableTableContainerDropTarget { @@ -260,3 +247,8 @@ table.draggableTableContainer { padding: 0; } } + +.portraitTableContainer { + overflow: auto; + height: 90vh; +} From 132ded4403ff70a2c918028c9e1cd9776309ef28 Mon Sep 17 00:00:00 2001 From: eireland Date: Wed, 18 Oct 2023 07:53:25 -0700 Subject: [PATCH 07/18] Fixes padding between tables when padding is turned on --- src/components/nested-table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/nested-table.tsx b/src/components/nested-table.tsx index a401adb..1cf0d1f 100644 --- a/src/components/nested-table.tsx +++ b/src/components/nested-table.tsx @@ -64,7 +64,7 @@ export const NestedTable = (props: IProps) => { }, [interactiveState.dataSetName, updateInteractiveState]); useEffect(() => { - const style = interactiveState.padding ? {padding: "7px"} : {padding: "0px"}; + const style = interactiveState.padding ? {padding: "3px"} : {padding: "0px"}; setPaddingStyle(style); }, [interactiveState.padding]); From be5ecc7bcb3c0c4a1d1156e8e3417251925c9e3a Mon Sep 17 00:00:00 2001 From: eireland Date: Wed, 18 Oct 2023 07:54:29 -0700 Subject: [PATCH 08/18] Adds div around data cell value to position it better --- src/components/draggable-table-tags.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/draggable-table-tags.tsx b/src/components/draggable-table-tags.tsx index d10976b..2375be5 100644 --- a/src/components/draggable-table-tags.tsx +++ b/src/components/draggable-table-tags.tsx @@ -90,7 +90,10 @@ export const DraggagleTableData: React.FC return (
); }; From 5073256f3b06b6090b748829b32d9898769ca463 Mon Sep 17 00:00:00 2001 From: eireland Date: Wed, 18 Oct 2023 14:20:45 -0700 Subject: [PATCH 09/18] Scrolling works for Mammals, but not Roller Coaster because of very tall cells. --- src/components/portrait-view.tsx | 70 +++++++++++++++++++------------- src/components/tables.scss | 3 -- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx index cb93b9e..a26422d 100644 --- a/src/components/portrait-view.tsx +++ b/src/components/portrait-view.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo } from "react"; import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; import { DraggableTableContainer, DroppableTableData, DroppableTableHeader } from "./draggable-table-tags"; @@ -75,19 +75,6 @@ export const PortraitView = (props: ITableProps) => { return t; },[]); - const [scrolling, setScrolling] = useState(false); - const [scrollTop, setScrollTop] = useState(0); - - useEffect(() => { - const onScroll = (e: any) => { - setScrollTop(e.target.documentElement.scrollTop); - setScrolling(e.target.documentElement.scrollTop > scrollTop); - }; - window.addEventListener("scroll", onScroll); - - return () => window.removeEventListener("scroll", onScroll); - }, [scrollTop]); - useEffect(() => { const handleIntersection = (entries: IntersectionObserverEntry[]) => { entries.forEach((entry) => { @@ -96,23 +83,51 @@ export const PortraitView = (props: ITableProps) => { const entryHeight = entryRect.height; const intersectionRect = entry.intersectionRect; const visibleHeight = intersectionRect.height; + const visibleTop = intersectionRect.top; const intersectionHeightRatio = visibleHeight/entryHeight; const cells = Array.from(target.querySelectorAll(".parent-data")); - if (cells) { cells.forEach(cell => { - cell.style.position = "relative"; - if (entry.isIntersecting && intersectionHeightRatio < 0.85) { - if (intersectionRect.top === 0) { //we're in the bottom part of the visible rect - cell.style.verticalAlign = "top"; - cell.style.top = `${(visibleHeight/2) - entryRect.top - 16}px`; - } else { //we're in the top part of the visible rect - cell.style.verticalAlign = "top"; - cell.style.top = `${visibleHeight/2}px`; + const cellTop = cell.getBoundingClientRect().top; + const dataCellHeight = cell.clientHeight; + const dataTextValue = cell.querySelector(".data-text-value"); + const textHeight = dataTextValue?.getBoundingClientRect().height || 16; + const visiblePortion = Math.min(dataCellHeight, window.innerHeight - cell.getBoundingClientRect().top); + // console.log(target.textContent, "target entryRect top", entryRect.top); + // console.log(cell.textContent, "visibleHeight", visibleHeight); + console.log(cell.textContent, "visibleTop", visibleTop); + console.log(cell.textContent, "visiblePortion", visiblePortion); + // console.log(cell.textContent, "intersectionRect.top", intersectionRect.top, "cellTop", + // cellTop); + console.log(cell.textContent, "cellTop", cellTop); + console.log(cell.textContent, "dataCellHeight", dataCellHeight); + let textTopPosition = 0; + + if (dataTextValue) { + dataTextValue.style.position = "relative"; + // console.log(cell.textContent, "isIntersecting", entry.isIntersecting, + // "intersectionHeightRatio", intersectionHeightRatio); + if (dataCellHeight <= visibleHeight) { + // console.log(cell.textContent, "WHOLE CELL IS VISIBLE"); + textTopPosition = 0; + } else + if (entry.isIntersecting && intersectionHeightRatio < 0.95) { + if (cellTop < intersectionRect.top/2) { //we're in the bottom part of the visible rect + console.log(cell.textContent, "BOTTOM PART"); + // textTopPosition = (visibleHeight/2) - entryRect.top - 16; + // textTopPosition = (dataCellHeight / 2) + cellTop - textHeight; + textTopPosition = Math.min((dataCellHeight/2 - textHeight), visibleTop - (cellTop) + textHeight); + // textTopPosition = ((dataCellHeight - visiblePortion) / 2) - textHeight; + } else { //we're in the top part of the visible rect + console.log(cell.textContent, "TOP PART"); + // dataTextValue.style.top = `${visibleHeight/2}px`; + textTopPosition = Math.max((-dataCellHeight/2) + textHeight, + (visiblePortion - dataCellHeight) / 2 + textHeight); + } } - } else { - cell.style.top = "0"; - cell.style.verticalAlign = "middle"; + // console.log(cell.textContent, "textTopPositon", textTopPosition); + console.log(cell.textContent, "*****************************************************"); + dataTextValue.style.top = `${textTopPosition}px`; } }); } @@ -123,12 +138,11 @@ export const PortraitView = (props: ITableProps) => { observer.observe(cell); }); return () => { - // Clean up the observer when the component unmounts document.querySelectorAll(".parent-row").forEach((cell) => { observer.unobserve(cell); }); }; - }, [scrollTop, scrolling, thresh]); + }, []); const renderTable = () => { const parentColl = collections.filter((coll: ICollection) => !coll.parent)[0]; diff --git a/src/components/tables.scss b/src/components/tables.scss index f089d14..dc1d90b 100644 --- a/src/components/tables.scss +++ b/src/components/tables.scss @@ -209,8 +209,6 @@ table { table.draggableTableContainer { width: 100%; border-spacing: 0px; - overflow-x: auto; - overflow-y: hidden; td.draggableTableContainerDropTarget { @@ -249,6 +247,5 @@ table.draggableTableContainer { } .portraitTableContainer { - overflow: auto; height: 90vh; } From f10a8c5d2523f826e0ab26212ff28e46cc31ecbb Mon Sep 17 00:00:00 2001 From: eireland Date: Thu, 19 Oct 2023 11:03:02 -0700 Subject: [PATCH 10/18] Does not work with roller coaster because of tall cells. observer stops observer when ratio does not change. --- src/components/portrait-view.tsx | 63 ++++++++++++++++++++------------ src/components/tables.scss | 15 +++++--- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx index a26422d..618606c 100644 --- a/src/components/portrait-view.tsx +++ b/src/components/portrait-view.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; import { DraggableTableContainer, DroppableTableData, DroppableTableHeader } from "./draggable-table-tags"; @@ -33,7 +33,7 @@ export const PortraitViewRow = (props: PortraitViewRowProps) => {
{selectedDataSet.name}{selectedDataSet.name}
{parentColl.name} - {children} + {isParent + ?
{children}
+ : children + }
- + {caseObj.children.map((child, i) => { const nextProps: PortraitViewRowProps = { ...props, @@ -70,13 +70,27 @@ export const PortraitView = (props: ITableProps) => { const thresh = useMemo(() => { const t: number[] = []; for (let i = 0; i <= 100; i++) { - t.push(i / 100); + t.push(i/100); } return t; },[]); + const [scrolling, setScrolling] = useState(false); + const [scrollTop, setScrollTop] = useState(0); + console.log("scrolling", window.scrollY); + + useEffect(() => { + const onScroll = (e: any) => { + setScrollTop(window.scrollY); + setScrolling(window.scrollY > scrollTop); + }; + window.addEventListener("scroll", onScroll); + + return () => window.removeEventListener("scroll", onScroll); + }, [scrollTop]); + useEffect(() => { - const handleIntersection = (entries: IntersectionObserverEntry[]) => { + const handleIntersection = (entries: IntersectionObserverEntry[], o: any) => { entries.forEach((entry) => { const target = entry.target; const entryRect = target.getBoundingClientRect(); @@ -88,19 +102,24 @@ export const PortraitView = (props: ITableProps) => { const cells = Array.from(target.querySelectorAll(".parent-data")); if (cells) { cells.forEach(cell => { - const cellTop = cell.getBoundingClientRect().top; + // cell.textContent==="Wooden" && console.log("in handleIntersection Wooden",entry); + // console.log(cell.textContent, "intersectionRatio", entry.intersectionRatio); + const cellRect = cell.getBoundingClientRect(); + const cellTop = cellRect.top; const dataCellHeight = cell.clientHeight; const dataTextValue = cell.querySelector(".data-text-value"); const textHeight = dataTextValue?.getBoundingClientRect().height || 16; const visiblePortion = Math.min(dataCellHeight, window.innerHeight - cell.getBoundingClientRect().top); - // console.log(target.textContent, "target entryRect top", entryRect.top); + // console.log(cell.textContent, "target entryRect top", entryRect.top); // console.log(cell.textContent, "visibleHeight", visibleHeight); - console.log(cell.textContent, "visibleTop", visibleTop); - console.log(cell.textContent, "visiblePortion", visiblePortion); + // console.log(cell.textContent, "intersectionRect bounds", intersectionRect); + // console.log(cell.textContent, "visibleTop", visibleTop); + // console.log(cell.textContent, "visiblePortion", visiblePortion); // console.log(cell.textContent, "intersectionRect.top", intersectionRect.top, "cellTop", // cellTop); - console.log(cell.textContent, "cellTop", cellTop); - console.log(cell.textContent, "dataCellHeight", dataCellHeight); + // console.log(cell.textContent, "cellTop", cellTop); + // console.log(cell.textContent, "dataCellHeight", dataCellHeight); + // console.log(cell.textContent, "window.innerHeight", window.innerHeight); let textTopPosition = 0; if (dataTextValue) { @@ -113,36 +132,32 @@ export const PortraitView = (props: ITableProps) => { } else if (entry.isIntersecting && intersectionHeightRatio < 0.95) { if (cellTop < intersectionRect.top/2) { //we're in the bottom part of the visible rect - console.log(cell.textContent, "BOTTOM PART"); - // textTopPosition = (visibleHeight/2) - entryRect.top - 16; - // textTopPosition = (dataCellHeight / 2) + cellTop - textHeight; + // console.log(cell.textContent, "BOTTOM PART"); textTopPosition = Math.min((dataCellHeight/2 - textHeight), visibleTop - (cellTop) + textHeight); - // textTopPosition = ((dataCellHeight - visiblePortion) / 2) - textHeight; } else { //we're in the top part of the visible rect - console.log(cell.textContent, "TOP PART"); - // dataTextValue.style.top = `${visibleHeight/2}px`; + // console.log(cell.textContent, "TOP PART"); textTopPosition = Math.max((-dataCellHeight/2) + textHeight, (visiblePortion - dataCellHeight) / 2 + textHeight); } } // console.log(cell.textContent, "textTopPositon", textTopPosition); - console.log(cell.textContent, "*****************************************************"); + // console.log(cell.textContent, "*****************************************************"); dataTextValue.style.top = `${textTopPosition}px`; } }); } }); }; - const observer = new IntersectionObserver(handleIntersection, { threshold: thresh }); - document.querySelectorAll(".parent-row").forEach((cell) => { - observer.observe(cell); + const observer = new IntersectionObserver(handleIntersection, {threshold: thresh}); + document.querySelectorAll(".parent-row").forEach((row) => { + observer.observe(row); }); return () => { - document.querySelectorAll(".parent-row").forEach((cell) => { - observer.unobserve(cell); + document.querySelectorAll(".parent-row").forEach((row) => { + observer.unobserve(row); }); }; - }, []); + }, [scrolling, thresh]); const renderTable = () => { const parentColl = collections.filter((coll: ICollection) => !coll.parent)[0]; @@ -153,7 +168,7 @@ export const PortraitView = (props: ITableProps) => { return (
- + diff --git a/src/components/tables.scss b/src/components/tables.scss index dc1d90b..bbd1d25 100644 --- a/src/components/tables.scss +++ b/src/components/tables.scss @@ -127,7 +127,8 @@ table { border: 1px solid #3c78d8; } - &.collection0>th, .collection0>th, th.collection0 { + // &.collection0>th, .collection0>th, th.collection0 { + &.collection0>th, .collection0>th { background-color: #CEE9FB; border: 1px solid #3c78d8; position: sticky; @@ -143,7 +144,8 @@ table { z-index: 8; } - .collection1>th, th.collection1 { + // .collection1>th, th.collection1 { + .collection1>th { background-color: #F7EABA; position: sticky; top: 48px; @@ -156,7 +158,8 @@ table { border: none; } - .collection2>th, th.collection2 { + // .collection2>th, th.collection2 { + .collection2>th { background-color: #CCBEE0; position: sticky; top: 64px; @@ -170,7 +173,8 @@ table { border: none; } - .collection3>th, th.collection3 { + // .collection3>th, th.collection3 { + .collection3>th { background-color: #BEE0CA; position: sticky; top: 80px; @@ -184,7 +188,8 @@ table { border: none; } - .collection4>th, th.collection4 { + // .collection4>th, th.collection4 { + .collection4>th { background-color: #E7C3A4; position: sticky; top: 96px; From 7405e0eb9b41fa86bf99b4bfa7fd3a4fbb980bd6 Mon Sep 17 00:00:00 2001 From: eireland Date: Fri, 20 Oct 2023 14:34:12 -0700 Subject: [PATCH 11/18] Scratch work on figuring out the visible portion of the cell Moves the intersectionObserver into the data call part of the code from the row part of the code. Adds position relative to the parent data cell, so we can position the text div absolute. --- src/components/draggable-table-tags.tsx | 152 +++++++++++++++++++++++- src/components/portrait-view.tsx | 81 +------------ src/components/tables.scss | 4 + 3 files changed, 156 insertions(+), 81 deletions(-) diff --git a/src/components/draggable-table-tags.tsx b/src/components/draggable-table-tags.tsx index 2375be5..479bc4a 100644 --- a/src/components/draggable-table-tags.tsx +++ b/src/components/draggable-table-tags.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback, useEffect, useMemo } from "react"; import { useDraggableTableContext, Side } from "../hooks/useDraggableTable"; import AddIcon from "../assets/plus-level-1.svg"; @@ -87,9 +87,157 @@ export const DraggagleTableData: React.FC = ({collectionId, attrTitle, children, isParent}) => { const {dragOverId, dragSide} = useDraggableTableContext(); const {style} = getIdAndStyle(collectionId, attrTitle, dragOverId, dragSide); + const thresh = useMemo(() => { + const t: number[] = []; + for (let i = 0; i <= 100; i++) { + t.push(i/100); + } + return t; + },[]); + + const positionDataCellValue = useCallback(()=>{ + const cell = document.querySelector(css.parentData); + function calculateVisibilityForDiv(div$) { + var windowHeight = $(window).height(), + docScroll = $(document).scrollTop(), + divPosition = div$.offset().top, + divHeight = div$.height(), + hiddenBefore = docScroll - divPosition, + hiddenAfter = (divPosition + divHeight) - (docScroll + windowHeight); + + if ((docScroll > divPosition + divHeight) || (divPosition > docScroll + windowHeight)) { + return 0; + } else { + var result = 100; + + if (hiddenBefore > 0) { + result -= (hiddenBefore * 100) / divHeight; + } + + if (hiddenAfter > 0) { + result -= (hiddenAfter * 100) / divHeight; + } + + return result; + } + } + // if (cells) { + // cells.forEach(cell => { + // // cell.textContent==="Wooden" && console.log("in handleIntersection Wooden",entry); + // // console.log(cell.textContent, "intersectionRatio", entry.intersectionRatio); + // const cellRect = cell.getBoundingClientRect(); + // const cellTop = cellRect.top; + // const dataCellHeight = cellRect.height; + // const dataTextValue = cell.querySelector(".data-text-value"); + // const textHeight = dataTextValue?.getBoundingClientRect().height || 16; + // const visiblePortion = Math.min(dataCellHeight, window.innerHeight - cell.getBoundingClientRect().top); + // // console.log(cell.textContent, "target entryRect top", entryRect.top); + // // console.log(cell.textContent, "visibleHeight", visibleHeight); + // // console.log(cell.textContent, "intersectionRect bounds", intersectionRect); + // // console.log(cell.textContent, "visibleTop", visibleTop); + // // console.log(cell.textContent, "visiblePortion", visiblePortion); + // // console.log(cell.textContent, "intersectionRect.top", intersectionRect.top, "cellTop", + // // cellTop); + // // console.log(cell.textContent, "cellTop", cellTop); + // // console.log(cell.textContent, "dataCellHeight", dataCellHeight); + // // console.log(cell.textContent, "window.innerHeight", window.innerHeight); + // let textTopPosition = 0; + + // if (dataTextValue) { + // dataTextValue.style.position = "relative"; + // if (dataCellHeight <= visiblePortion) { // Center value if whole cell is visible + // textTopPosition = 0; + // } + // // else + // // if (entry.isIntersecting && intersectionHeightRatio < 0.95) { + // // if (cellTop < intersectionRect.top/2) { //we're in the bottom part of the visible rect + // // // console.log(cell.textContent, "BOTTOM PART"); + // // textTopPosition = Math.min((dataCellHeight/2 - textHeight), visibleTop - (cellTop) + textHeight); + // // } else { //we're in the top part of the visible rect + // // // console.log(cell.textContent, "TOP PART"); + // // textTopPosition = Math.max((-dataCellHeight/2) + textHeight, + // // (visiblePortion - dataCellHeight) / 2 + textHeight); + // // } + // // } + // // console.log(cell.textContent, "textTopPositon", textTopPosition); + // // console.log(cell.textContent, "*****************************************************"); + // dataTextValue.style.top = `${textTopPosition}px`; + // } + // }); + // } + },[]); + + useEffect(() => { + const handleIntersection = (entries: IntersectionObserverEntry[], o: any) => { + entries.forEach((entry) => { + const target = entry.target; + const entryRect = target.getBoundingClientRect(); + const entryHeight = entryRect.height; + const intersectionRect = entry.intersectionRect; + const visibleHeight = intersectionRect.height; + const visibleTop = intersectionRect.top; + const intersectionHeightRatio = visibleHeight/entryHeight; + const cells = Array.from(target.querySelectorAll(css.parentData)); + if (cells) { + cells.forEach(cell => { + console.log(cell.textContent, "intersectionRatio", entry.intersectionRatio); + const cellRect = cell.getBoundingClientRect(); + const cellTop = cellRect.top; + const dataCellHeight = cell.clientHeight; + const dataTextValue = cell.querySelector(".data-text-value"); + const textHeight = dataTextValue?.getBoundingClientRect().height || 16; + const visiblePortion = Math.min(dataCellHeight, window.innerHeight - cell.getBoundingClientRect().top); + console.log(cell.textContent, "target entryRect top", entryRect.top); + console.log(cell.textContent, "visibleHeight", visibleHeight); + console.log(cell.textContent, "intersectionRect bounds", intersectionRect); + console.log(cell.textContent, "visibleTop", visibleTop); + console.log(cell.textContent, "visiblePortion", visiblePortion); + console.log(cell.textContent, "intersectionRect.top", intersectionRect.top, "cellTop", + cellTop); + console.log(cell.textContent, "cellTop", cellTop); + console.log(cell.textContent, "dataCellHeight", dataCellHeight); + console.log(cell.textContent, "window.innerHeight", window.innerHeight); + let textTopPosition = 0; + + if (dataTextValue) { + dataTextValue.style.position = "absolute"; + // console.log(cell.textContent, "isIntersecting", entry.isIntersecting, + // "intersectionHeightRatio", intersectionHeightRatio); + if (dataCellHeight <= visibleHeight) { + // console.log(cell.textContent, "WHOLE CELL IS VISIBLE"); + textTopPosition = 0; + } else + if (entry.isIntersecting && intersectionHeightRatio < 0.95) { + if (cellTop < intersectionRect.top/2) { //we're in the bottom part of the visible rect + // console.log(cell.textContent, "BOTTOM PART"); + textTopPosition = Math.min((dataCellHeight/2 - textHeight), visibleTop - (cellTop) + textHeight); + } else { //we're in the top part of the visible rect + // console.log(cell.textContent, "TOP PART"); + textTopPosition = Math.max((-dataCellHeight/2) + textHeight, + (visiblePortion - dataCellHeight) / 2 + textHeight); + } + } + // console.log(cell.textContent, "textTopPositon", textTopPosition); + // console.log(cell.textContent, "*****************************************************"); + dataTextValue.style.top = `${textTopPosition}px`; + } + }); + } + }); + }; + const observer = new IntersectionObserver(handleIntersection, {threshold: thresh}); + document.querySelectorAll(".parent-row").forEach((row) => { + observer.observe(row); + }); + return () => { + document.querySelectorAll(".parent-row").forEach((row) => { + observer.unobserve(row); + }); + }; + }, [thresh]); return ( - diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx index b640577..15396c4 100644 --- a/src/components/portrait-view.tsx +++ b/src/components/portrait-view.tsx @@ -1,8 +1,9 @@ -import React from "react"; +import React, { useRef } from "react"; import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; import { DraggableTableContainer, DroppableTableData, DroppableTableHeader } from "./draggable-table-tags"; import css from "./tables.scss"; +import { TableScrollTopContext, useTableScrollTop } from "../hooks/useTableScrollTop"; export type PortraitViewRowProps = {collectionId: number, caseObj: IProcessedCaseObj, index?: null|number, isParent: boolean} & ITableProps; @@ -67,6 +68,8 @@ export const PortraitViewRow = (props: PortraitViewRowProps) => { export const PortraitView = (props: ITableProps) => { const {collectionClasses, selectedDataSet, collections, getValueLength} = props; + const tableRef = useRef(null); + const tableScrollTop = useTableScrollTop(tableRef); const renderTable = () => { const parentColl = collections.filter((coll: ICollection) => !coll.parent)[0]; @@ -76,7 +79,7 @@ export const PortraitView = (props: ITableProps) => { return ( -
{selectedDataSet.name}
+ {isParent ?
{children}
: children diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx index 618606c..c14ca66 100644 --- a/src/components/portrait-view.tsx +++ b/src/components/portrait-view.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; import { DraggableTableContainer, DroppableTableData, DroppableTableHeader } from "./draggable-table-tags"; @@ -67,17 +67,10 @@ export const PortraitViewRow = (props: PortraitViewRowProps) => { export const PortraitView = (props: ITableProps) => { const {collectionClasses, selectedDataSet, collections, getValueLength} = props; - const thresh = useMemo(() => { - const t: number[] = []; - for (let i = 0; i <= 100; i++) { - t.push(i/100); - } - return t; - },[]); + const [scrolling, setScrolling] = useState(false); const [scrollTop, setScrollTop] = useState(0); - console.log("scrolling", window.scrollY); useEffect(() => { const onScroll = (e: any) => { @@ -89,76 +82,6 @@ export const PortraitView = (props: ITableProps) => { return () => window.removeEventListener("scroll", onScroll); }, [scrollTop]); - useEffect(() => { - const handleIntersection = (entries: IntersectionObserverEntry[], o: any) => { - entries.forEach((entry) => { - const target = entry.target; - const entryRect = target.getBoundingClientRect(); - const entryHeight = entryRect.height; - const intersectionRect = entry.intersectionRect; - const visibleHeight = intersectionRect.height; - const visibleTop = intersectionRect.top; - const intersectionHeightRatio = visibleHeight/entryHeight; - const cells = Array.from(target.querySelectorAll(".parent-data")); - if (cells) { - cells.forEach(cell => { - // cell.textContent==="Wooden" && console.log("in handleIntersection Wooden",entry); - // console.log(cell.textContent, "intersectionRatio", entry.intersectionRatio); - const cellRect = cell.getBoundingClientRect(); - const cellTop = cellRect.top; - const dataCellHeight = cell.clientHeight; - const dataTextValue = cell.querySelector(".data-text-value"); - const textHeight = dataTextValue?.getBoundingClientRect().height || 16; - const visiblePortion = Math.min(dataCellHeight, window.innerHeight - cell.getBoundingClientRect().top); - // console.log(cell.textContent, "target entryRect top", entryRect.top); - // console.log(cell.textContent, "visibleHeight", visibleHeight); - // console.log(cell.textContent, "intersectionRect bounds", intersectionRect); - // console.log(cell.textContent, "visibleTop", visibleTop); - // console.log(cell.textContent, "visiblePortion", visiblePortion); - // console.log(cell.textContent, "intersectionRect.top", intersectionRect.top, "cellTop", - // cellTop); - // console.log(cell.textContent, "cellTop", cellTop); - // console.log(cell.textContent, "dataCellHeight", dataCellHeight); - // console.log(cell.textContent, "window.innerHeight", window.innerHeight); - let textTopPosition = 0; - - if (dataTextValue) { - dataTextValue.style.position = "relative"; - // console.log(cell.textContent, "isIntersecting", entry.isIntersecting, - // "intersectionHeightRatio", intersectionHeightRatio); - if (dataCellHeight <= visibleHeight) { - // console.log(cell.textContent, "WHOLE CELL IS VISIBLE"); - textTopPosition = 0; - } else - if (entry.isIntersecting && intersectionHeightRatio < 0.95) { - if (cellTop < intersectionRect.top/2) { //we're in the bottom part of the visible rect - // console.log(cell.textContent, "BOTTOM PART"); - textTopPosition = Math.min((dataCellHeight/2 - textHeight), visibleTop - (cellTop) + textHeight); - } else { //we're in the top part of the visible rect - // console.log(cell.textContent, "TOP PART"); - textTopPosition = Math.max((-dataCellHeight/2) + textHeight, - (visiblePortion - dataCellHeight) / 2 + textHeight); - } - } - // console.log(cell.textContent, "textTopPositon", textTopPosition); - // console.log(cell.textContent, "*****************************************************"); - dataTextValue.style.top = `${textTopPosition}px`; - } - }); - } - }); - }; - const observer = new IntersectionObserver(handleIntersection, {threshold: thresh}); - document.querySelectorAll(".parent-row").forEach((row) => { - observer.observe(row); - }); - return () => { - document.querySelectorAll(".parent-row").forEach((row) => { - observer.unobserve(row); - }); - }; - }, [scrolling, thresh]); - const renderTable = () => { const parentColl = collections.filter((coll: ICollection) => !coll.parent)[0]; const {className} = collectionClasses[0]; diff --git a/src/components/tables.scss b/src/components/tables.scss index bbd1d25..a4c6fcd 100644 --- a/src/components/tables.scss +++ b/src/components/tables.scss @@ -254,3 +254,7 @@ table.draggableTableContainer { .portraitTableContainer { height: 90vh; } + +.parentData { + position: relative; +} From 8bfc00b316067d7b4a2e04ab7b751f0ec02335df Mon Sep 17 00:00:00 2001 From: eireland Date: Fri, 20 Oct 2023 14:39:17 -0700 Subject: [PATCH 12/18] Moves scroll listener to data cell render. Adds counter state to trigger rerender on scroll or intersectObserver cahnges. --- src/components/draggable-table-tags.tsx | 126 +++++++++++++----------- src/components/portrait-view.tsx | 16 +-- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/src/components/draggable-table-tags.tsx b/src/components/draggable-table-tags.tsx index 479bc4a..432cc90 100644 --- a/src/components/draggable-table-tags.tsx +++ b/src/components/draggable-table-tags.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo } from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; import { useDraggableTableContext, Side } from "../hooks/useDraggableTable"; import AddIcon from "../assets/plus-level-1.svg"; @@ -95,6 +95,19 @@ export const DraggagleTableData: React.FC return t; },[]); + const [counter, setCounter] = useState(0); + const [scrollTop, setScrollTop] = useState(0); + + useEffect(() => { + const onScroll = (e: any) => { + setScrollTop(window.scrollY); + setCounter((prevCounter: number) => prevCounter++); + }; + window.addEventListener("scroll", onScroll); + + return () => window.removeEventListener("scroll", onScroll); + }, [scrollTop]); + const positionDataCellValue = useCallback(()=>{ const cell = document.querySelector(css.parentData); function calculateVisibilityForDiv(div$) { @@ -169,61 +182,62 @@ export const DraggagleTableData: React.FC useEffect(() => { const handleIntersection = (entries: IntersectionObserverEntry[], o: any) => { - entries.forEach((entry) => { - const target = entry.target; - const entryRect = target.getBoundingClientRect(); - const entryHeight = entryRect.height; - const intersectionRect = entry.intersectionRect; - const visibleHeight = intersectionRect.height; - const visibleTop = intersectionRect.top; - const intersectionHeightRatio = visibleHeight/entryHeight; - const cells = Array.from(target.querySelectorAll(css.parentData)); - if (cells) { - cells.forEach(cell => { - console.log(cell.textContent, "intersectionRatio", entry.intersectionRatio); - const cellRect = cell.getBoundingClientRect(); - const cellTop = cellRect.top; - const dataCellHeight = cell.clientHeight; - const dataTextValue = cell.querySelector(".data-text-value"); - const textHeight = dataTextValue?.getBoundingClientRect().height || 16; - const visiblePortion = Math.min(dataCellHeight, window.innerHeight - cell.getBoundingClientRect().top); - console.log(cell.textContent, "target entryRect top", entryRect.top); - console.log(cell.textContent, "visibleHeight", visibleHeight); - console.log(cell.textContent, "intersectionRect bounds", intersectionRect); - console.log(cell.textContent, "visibleTop", visibleTop); - console.log(cell.textContent, "visiblePortion", visiblePortion); - console.log(cell.textContent, "intersectionRect.top", intersectionRect.top, "cellTop", - cellTop); - console.log(cell.textContent, "cellTop", cellTop); - console.log(cell.textContent, "dataCellHeight", dataCellHeight); - console.log(cell.textContent, "window.innerHeight", window.innerHeight); - let textTopPosition = 0; - - if (dataTextValue) { - dataTextValue.style.position = "absolute"; - // console.log(cell.textContent, "isIntersecting", entry.isIntersecting, - // "intersectionHeightRatio", intersectionHeightRatio); - if (dataCellHeight <= visibleHeight) { - // console.log(cell.textContent, "WHOLE CELL IS VISIBLE"); - textTopPosition = 0; - } else - if (entry.isIntersecting && intersectionHeightRatio < 0.95) { - if (cellTop < intersectionRect.top/2) { //we're in the bottom part of the visible rect - // console.log(cell.textContent, "BOTTOM PART"); - textTopPosition = Math.min((dataCellHeight/2 - textHeight), visibleTop - (cellTop) + textHeight); - } else { //we're in the top part of the visible rect - // console.log(cell.textContent, "TOP PART"); - textTopPosition = Math.max((-dataCellHeight/2) + textHeight, - (visiblePortion - dataCellHeight) / 2 + textHeight); - } - } - // console.log(cell.textContent, "textTopPositon", textTopPosition); - // console.log(cell.textContent, "*****************************************************"); - dataTextValue.style.top = `${textTopPosition}px`; - } - }); - } - }); + setCounter((prevCounter: number) => prevCounter++); + // entries.forEach((entry) => { + // const target = entry.target; + // const entryRect = target.getBoundingClientRect(); + // const entryHeight = entryRect.height; + // const intersectionRect = entry.intersectionRect; + // const visibleHeight = intersectionRect.height; + // const visibleTop = intersectionRect.top; + // const intersectionHeightRatio = visibleHeight/entryHeight; + // const cells = Array.from(target.querySelectorAll(css.parentData)); + // if (cells) { + // cells.forEach(cell => { + // console.log(cell.textContent, "intersectionRatio", entry.intersectionRatio); + // const cellRect = cell.getBoundingClientRect(); + // const cellTop = cellRect.top; + // const dataCellHeight = cell.clientHeight; + // const dataTextValue = cell.querySelector(".data-text-value"); + // const textHeight = dataTextValue?.getBoundingClientRect().height || 16; + // const visiblePortion = Math.min(dataCellHeight, window.innerHeight - cell.getBoundingClientRect().top); + // console.log(cell.textContent, "target entryRect top", entryRect.top); + // console.log(cell.textContent, "visibleHeight", visibleHeight); + // console.log(cell.textContent, "intersectionRect bounds", intersectionRect); + // console.log(cell.textContent, "visibleTop", visibleTop); + // console.log(cell.textContent, "visiblePortion", visiblePortion); + // console.log(cell.textContent, "intersectionRect.top", intersectionRect.top, "cellTop", + // cellTop); + // console.log(cell.textContent, "cellTop", cellTop); + // console.log(cell.textContent, "dataCellHeight", dataCellHeight); + // console.log(cell.textContent, "window.innerHeight", window.innerHeight); + // let textTopPosition = 0; + + // if (dataTextValue) { + // dataTextValue.style.position = "absolute"; + // // console.log(cell.textContent, "isIntersecting", entry.isIntersecting, + // // "intersectionHeightRatio", intersectionHeightRatio); + // if (dataCellHeight <= visibleHeight) { + // // console.log(cell.textContent, "WHOLE CELL IS VISIBLE"); + // textTopPosition = 0; + // } else + // if (entry.isIntersecting && intersectionHeightRatio < 0.95) { + // if (cellTop < intersectionRect.top/2) { //we're in the bottom part of the visible rect + // // console.log(cell.textContent, "BOTTOM PART"); + // textTopPosition = Math.min((dataCellHeight/2 - textHeight), visibleTop - (cellTop) + textHeight); + // } else { //we're in the top part of the visible rect + // // console.log(cell.textContent, "TOP PART"); + // textTopPosition = Math.max((-dataCellHeight/2) + textHeight, + // (visiblePortion - dataCellHeight) / 2 + textHeight); + // } + // } + // // console.log(cell.textContent, "textTopPositon", textTopPosition); + // // console.log(cell.textContent, "*****************************************************"); + // dataTextValue.style.top = `${textTopPosition}px`; + // } + // }); + // } + // }); }; const observer = new IntersectionObserver(handleIntersection, {threshold: thresh}); document.querySelectorAll(".parent-row").forEach((row) => { diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx index c14ca66..b640577 100644 --- a/src/components/portrait-view.tsx +++ b/src/components/portrait-view.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React from "react"; import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; import { DraggableTableContainer, DroppableTableData, DroppableTableHeader } from "./draggable-table-tags"; @@ -68,20 +68,6 @@ export const PortraitViewRow = (props: PortraitViewRowProps) => { export const PortraitView = (props: ITableProps) => { const {collectionClasses, selectedDataSet, collections, getValueLength} = props; - - const [scrolling, setScrolling] = useState(false); - const [scrollTop, setScrollTop] = useState(0); - - useEffect(() => { - const onScroll = (e: any) => { - setScrollTop(window.scrollY); - setScrolling(window.scrollY > scrollTop); - }; - window.addEventListener("scroll", onScroll); - - return () => window.removeEventListener("scroll", onScroll); - }, [scrollTop]); - const renderTable = () => { const parentColl = collections.filter((coll: ICollection) => !coll.parent)[0]; const {className} = collectionClasses[0]; From 47cc3358e7780352d00598f7c5b6edf5d2abea22 Mon Sep 17 00:00:00 2001 From: eireland Date: Fri, 20 Oct 2023 16:06:23 -0700 Subject: [PATCH 13/18] Aligns text to center of flex div --- src/components/tables.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/tables.scss b/src/components/tables.scss index a4c6fcd..b0cf987 100644 --- a/src/components/tables.scss +++ b/src/components/tables.scss @@ -257,4 +257,12 @@ table.draggableTableContainer { .parentData { position: relative; + .cellTextValue{ + position: absolute; + width: fit-content; + display: flex; + flex-wrap: wrap; + align-content: center; + } } + From fa308810ab51063e38a7751a1fce37460177522d Mon Sep 17 00:00:00 2001 From: eireland Date: Mon, 23 Oct 2023 13:01:12 -0700 Subject: [PATCH 14/18] reworked logic for placing data value Co-authored-by: Doug Martin --- src/components/draggable-table-tags.tsx | 220 ++++++------------------ src/components/portrait-view.tsx | 15 +- src/components/tables.scss | 3 +- src/hooks/useTableScrollTop.ts | 34 ++++ 4 files changed, 103 insertions(+), 169 deletions(-) create mode 100644 src/hooks/useTableScrollTop.ts diff --git a/src/components/draggable-table-tags.tsx b/src/components/draggable-table-tags.tsx index 432cc90..ec88513 100644 --- a/src/components/draggable-table-tags.tsx +++ b/src/components/draggable-table-tags.tsx @@ -1,9 +1,10 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useMemo, useRef } from "react"; import { useDraggableTableContext, Side } from "../hooks/useDraggableTable"; import AddIcon from "../assets/plus-level-1.svg"; import css from "./tables.scss"; +import { useTableTopScrollTopContext } from "../hooks/useTableScrollTop"; const highlightColor = "#FBF719"; @@ -87,173 +88,66 @@ export const DraggagleTableData: React.FC = ({collectionId, attrTitle, children, isParent}) => { const {dragOverId, dragSide} = useDraggableTableContext(); const {style} = getIdAndStyle(collectionId, attrTitle, dragOverId, dragSide); - const thresh = useMemo(() => { - const t: number[] = []; - for (let i = 0; i <= 100; i++) { - t.push(i/100); - } - return t; - },[]); - - const [counter, setCounter] = useState(0); - const [scrollTop, setScrollTop] = useState(0); - - useEffect(() => { - const onScroll = (e: any) => { - setScrollTop(window.scrollY); - setCounter((prevCounter: number) => prevCounter++); - }; - window.addEventListener("scroll", onScroll); - - return () => window.removeEventListener("scroll", onScroll); - }, [scrollTop]); - - const positionDataCellValue = useCallback(()=>{ - const cell = document.querySelector(css.parentData); - function calculateVisibilityForDiv(div$) { - var windowHeight = $(window).height(), - docScroll = $(document).scrollTop(), - divPosition = div$.offset().top, - divHeight = div$.height(), - hiddenBefore = docScroll - divPosition, - hiddenAfter = (divPosition + divHeight) - (docScroll + windowHeight); - - if ((docScroll > divPosition + divHeight) || (divPosition > docScroll + windowHeight)) { - return 0; - } else { - var result = 100; - - if (hiddenBefore > 0) { - result -= (hiddenBefore * 100) / divHeight; - } - - if (hiddenAfter > 0) { - result -= (hiddenAfter * 100) / divHeight; - } - - return result; + const {tableScrollTop, scrollY} = useTableTopScrollTopContext(); + + const cellRef = useRef(null); + + // HACK!!! + let level = 0; + if (cellRef.current) { + let walker: HTMLElement|null = cellRef.current; + while (walker && !walker.classList.contains("tables-portraitTable")) { + if (walker.tagName === "TABLE") { + level++; } + walker = walker.parentElement; + } } - // if (cells) { - // cells.forEach(cell => { - // // cell.textContent==="Wooden" && console.log("in handleIntersection Wooden",entry); - // // console.log(cell.textContent, "intersectionRatio", entry.intersectionRatio); - // const cellRect = cell.getBoundingClientRect(); - // const cellTop = cellRect.top; - // const dataCellHeight = cellRect.height; - // const dataTextValue = cell.querySelector(".data-text-value"); - // const textHeight = dataTextValue?.getBoundingClientRect().height || 16; - // const visiblePortion = Math.min(dataCellHeight, window.innerHeight - cell.getBoundingClientRect().top); - // // console.log(cell.textContent, "target entryRect top", entryRect.top); - // // console.log(cell.textContent, "visibleHeight", visibleHeight); - // // console.log(cell.textContent, "intersectionRect bounds", intersectionRect); - // // console.log(cell.textContent, "visibleTop", visibleTop); - // // console.log(cell.textContent, "visiblePortion", visiblePortion); - // // console.log(cell.textContent, "intersectionRect.top", intersectionRect.top, "cellTop", - // // cellTop); - // // console.log(cell.textContent, "cellTop", cellTop); - // // console.log(cell.textContent, "dataCellHeight", dataCellHeight); - // // console.log(cell.textContent, "window.innerHeight", window.innerHeight); - // let textTopPosition = 0; - - // if (dataTextValue) { - // dataTextValue.style.position = "relative"; - // if (dataCellHeight <= visiblePortion) { // Center value if whole cell is visible - // textTopPosition = 0; - // } - // // else - // // if (entry.isIntersecting && intersectionHeightRatio < 0.95) { - // // if (cellTop < intersectionRect.top/2) { //we're in the bottom part of the visible rect - // // // console.log(cell.textContent, "BOTTOM PART"); - // // textTopPosition = Math.min((dataCellHeight/2 - textHeight), visibleTop - (cellTop) + textHeight); - // // } else { //we're in the top part of the visible rect - // // // console.log(cell.textContent, "TOP PART"); - // // textTopPosition = Math.max((-dataCellHeight/2) + textHeight, - // // (visiblePortion - dataCellHeight) / 2 + textHeight); - // // } - // // } - // // console.log(cell.textContent, "textTopPositon", textTopPosition); - // // console.log(cell.textContent, "*****************************************************"); - // dataTextValue.style.top = `${textTopPosition}px`; - // } - // }); - // } - },[]); - - useEffect(() => { - const handleIntersection = (entries: IntersectionObserverEntry[], o: any) => { - setCounter((prevCounter: number) => prevCounter++); - // entries.forEach((entry) => { - // const target = entry.target; - // const entryRect = target.getBoundingClientRect(); - // const entryHeight = entryRect.height; - // const intersectionRect = entry.intersectionRect; - // const visibleHeight = intersectionRect.height; - // const visibleTop = intersectionRect.top; - // const intersectionHeightRatio = visibleHeight/entryHeight; - // const cells = Array.from(target.querySelectorAll(css.parentData)); - // if (cells) { - // cells.forEach(cell => { - // console.log(cell.textContent, "intersectionRatio", entry.intersectionRatio); - // const cellRect = cell.getBoundingClientRect(); - // const cellTop = cellRect.top; - // const dataCellHeight = cell.clientHeight; - // const dataTextValue = cell.querySelector(".data-text-value"); - // const textHeight = dataTextValue?.getBoundingClientRect().height || 16; - // const visiblePortion = Math.min(dataCellHeight, window.innerHeight - cell.getBoundingClientRect().top); - // console.log(cell.textContent, "target entryRect top", entryRect.top); - // console.log(cell.textContent, "visibleHeight", visibleHeight); - // console.log(cell.textContent, "intersectionRect bounds", intersectionRect); - // console.log(cell.textContent, "visibleTop", visibleTop); - // console.log(cell.textContent, "visiblePortion", visiblePortion); - // console.log(cell.textContent, "intersectionRect.top", intersectionRect.top, "cellTop", - // cellTop); - // console.log(cell.textContent, "cellTop", cellTop); - // console.log(cell.textContent, "dataCellHeight", dataCellHeight); - // console.log(cell.textContent, "window.innerHeight", window.innerHeight); - // let textTopPosition = 0; - - // if (dataTextValue) { - // dataTextValue.style.position = "absolute"; - // // console.log(cell.textContent, "isIntersecting", entry.isIntersecting, - // // "intersectionHeightRatio", intersectionHeightRatio); - // if (dataCellHeight <= visibleHeight) { - // // console.log(cell.textContent, "WHOLE CELL IS VISIBLE"); - // textTopPosition = 0; - // } else - // if (entry.isIntersecting && intersectionHeightRatio < 0.95) { - // if (cellTop < intersectionRect.top/2) { //we're in the bottom part of the visible rect - // // console.log(cell.textContent, "BOTTOM PART"); - // textTopPosition = Math.min((dataCellHeight/2 - textHeight), visibleTop - (cellTop) + textHeight); - // } else { //we're in the top part of the visible rect - // // console.log(cell.textContent, "TOP PART"); - // textTopPosition = Math.max((-dataCellHeight/2) + textHeight, - // (visiblePortion - dataCellHeight) / 2 + textHeight); - // } - // } - // // console.log(cell.textContent, "textTopPositon", textTopPosition); - // // console.log(cell.textContent, "*****************************************************"); - // dataTextValue.style.top = `${textTopPosition}px`; - // } - // }); - // } - // }); - }; - const observer = new IntersectionObserver(handleIntersection, {threshold: thresh}); - document.querySelectorAll(".parent-row").forEach((row) => { - observer.observe(row); - }); - return () => { - document.querySelectorAll(".parent-row").forEach((row) => { - observer.unobserve(row); - }); - }; - }, [thresh]); + level = level / 2; + const cellTextTop = useMemo (() =>{ + if (!cellRef.current) { + return 0; + } + + const {top, bottom, height} = cellRef.current.getBoundingClientRect(); + const stickyHeaders = tableScrollTop === 0; + const stickyHeaderHeight = (3 + level) * 16; + const visibleTop = stickyHeaders ? Math.max(top, stickyHeaderHeight) : tableScrollTop; + const visibleBottom = Math.min(window.innerHeight, visibleTop + height); + const text = cellRef.current.innerText; + const log = text.indexOf("plants") !== -1; + + // whole cell visible? + if (top >= visibleTop && bottom <= visibleBottom) { + if (log) { + console.log(cellRef.current?.innerText, "ALL VISIBLE"); + } + return 0; + } + + const availableHeight = visibleBottom - visibleTop; + const newTop = Math.max(0, ((availableHeight - 16) / 2)) /* + height of text */; + + if (log) { + console.log(cellRef.current?.innerText, JSON.stringify({top, bottom, visibleBottom, + tableScrollTop, visibleTop, availableHeight, newTop, scrollY})); + } + return newTop; + }, [tableScrollTop, scrollY, level]); + + const textStyle: React.CSSProperties = {top: cellTextTop}; + if (cellTextTop === 0) { + textStyle.alignContent = "center"; + textStyle.bottom = 0; + } return ( -
+ {isParent - ?
{children}
+ ? <> + {children} +
{children}
+ : children }
+
@@ -101,8 +104,10 @@ export const PortraitView = (props: ITableProps) => { }; return ( -
- {collections.length && collectionClasses.length && renderTable()} -
+ +
+ {collections.length && collectionClasses.length && renderTable()} +
+
); }; diff --git a/src/components/tables.scss b/src/components/tables.scss index b0cf987..d4b9d0f 100644 --- a/src/components/tables.scss +++ b/src/components/tables.scss @@ -262,7 +262,8 @@ table.draggableTableContainer { width: fit-content; display: flex; flex-wrap: wrap; - align-content: center; + // align-content: center; + top: 0; } } diff --git a/src/hooks/useTableScrollTop.ts b/src/hooks/useTableScrollTop.ts new file mode 100644 index 0000000..00e1485 --- /dev/null +++ b/src/hooks/useTableScrollTop.ts @@ -0,0 +1,34 @@ +import { createContext, useContext, useEffect, useState } from "react"; + +export type TableScrollTopContextType = { + scrollY: number + tableScrollTop: number +}; + +const defaultValue: TableScrollTopContextType = { + scrollY: 0, + tableScrollTop: 0, +}; + +export const TableScrollTopContext = createContext(defaultValue); + +export const useTableScrollTop = (tableRef: React.MutableRefObject) => { + const [tableScrollTop, setTableScrollTop] = useState(defaultValue); + + useEffect(() => { + const onScroll = (e: any) => { + const tableTop = tableRef.current?.getBoundingClientRect().top ?? 0; + setTableScrollTop({ + scrollY: window.scrollY, + tableScrollTop: Math.max(0, tableTop), + }); + }; + window.addEventListener("scroll", onScroll); + + return () => window.removeEventListener("scroll", onScroll); + }, [tableRef]); + + return tableScrollTop; +}; + +export const useTableTopScrollTopContext = () => useContext(TableScrollTopContext); From 8d03ee2875e5cec04a815727728952f756134b3d Mon Sep 17 00:00:00 2001 From: lublagg Date: Mon, 23 Oct 2023 17:46:59 -0400 Subject: [PATCH 15/18] SCROLLING IS WORKING! --- src/components/draggable-table-tags.tsx | 56 +++++++++++++++---------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/src/components/draggable-table-tags.tsx b/src/components/draggable-table-tags.tsx index ec88513..3bb1cab 100644 --- a/src/components/draggable-table-tags.tsx +++ b/src/components/draggable-table-tags.tsx @@ -106,35 +106,45 @@ export const DraggagleTableData: React.FC level = level / 2; const cellTextTop = useMemo (() =>{ - if (!cellRef.current) { + if (!cellRef.current || !isParent) { return 0; - } - - const {top, bottom, height} = cellRef.current.getBoundingClientRect(); - const stickyHeaders = tableScrollTop === 0; - const stickyHeaderHeight = (3 + level) * 16; - const visibleTop = stickyHeaders ? Math.max(top, stickyHeaderHeight) : tableScrollTop; - const visibleBottom = Math.min(window.innerHeight, visibleTop + height); - const text = cellRef.current.innerText; - const log = text.indexOf("plants") !== -1; - - // whole cell visible? - if (top >= visibleTop && bottom <= visibleBottom) { - if (log) { - console.log(cellRef.current?.innerText, "ALL VISIBLE"); + } else { + const {top, bottom, height} = cellRef.current.getBoundingClientRect(); + const stickyHeaders = tableScrollTop === 0; + const stickyHeaderHeight = (3 + level) * 16; + const visibleTop = stickyHeaders ? Math.max(top, stickyHeaderHeight) : top; + // const visibleTop = stickyHeaders ? Math.max(top, (stickyHeaderHeight + scrollY)) : tableScrollTop; + // const visibleBottom = Math.min(window.innerHeight, Math.max(bottom, 0)); + const visibleBottom = Math.min(window.innerHeight, bottom); + const text = cellRef.current.innerText; + const availableHeight = Math.abs(visibleBottom - visibleTop); + + let newTop; + + if (top >= visibleTop && bottom <= visibleBottom) { + // the whole cell is visible + return 0; + } else if (top < visibleTop && bottom < window.innerHeight) { + // we are in the bottom part of the cell + const hiddenHeightOfCell = height - availableHeight; + newTop = Math.max(0, (hiddenHeightOfCell - 16 + (availableHeight / 2))) /* + height of text */; + } else if (top >= visibleTop && bottom > visibleBottom) { + // we are in the top part of the cell + newTop = Math.max(0, ((availableHeight) / 2)) /* + height of text */; + } else { + // we are in the middle of a cell that's taller than the table window + // we need to get the hidden top part of the cell + const hiddenTopPartOfCell = Math.max(0, visibleTop - top); + newTop = Math.max(0, (hiddenTopPartOfCell - 16 + (availableHeight) / 2)) /* + height of text */; } - return 0; - } - const availableHeight = visibleBottom - visibleTop; - const newTop = Math.max(0, ((availableHeight - 16) / 2)) /* + height of text */; - - if (log) { console.log(cellRef.current?.innerText, JSON.stringify({top, bottom, visibleBottom, tableScrollTop, visibleTop, availableHeight, newTop, scrollY})); + return newTop; } - return newTop; - }, [tableScrollTop, scrollY, level]); + + + }, [tableScrollTop, isParent, scrollY, level]); const textStyle: React.CSSProperties = {top: cellTextTop}; if (cellTextTop === 0) { From 633d4c1a5750679abe7b073ea8b7a770e0d7dc06 Mon Sep 17 00:00:00 2001 From: lublagg Date: Mon, 23 Oct 2023 18:10:34 -0400 Subject: [PATCH 16/18] Works with intersection observor --- src/components/draggable-table-tags.tsx | 27 ++++++++---------- src/components/nested-table.tsx | 3 +- src/components/portrait-view.tsx | 38 ++++++++++++++++++++++--- src/types.tsx | 2 +- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src/components/draggable-table-tags.tsx b/src/components/draggable-table-tags.tsx index 3bb1cab..5a2a753 100644 --- a/src/components/draggable-table-tags.tsx +++ b/src/components/draggable-table-tags.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useRef } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { useDraggableTableContext, Side } from "../hooks/useDraggableTable"; import AddIcon from "../assets/plus-level-1.svg"; @@ -82,10 +82,11 @@ interface DraggagleTableDataProps { attrTitle: string; style?: React.CSSProperties; isParent?: boolean; + resizeCounter?: number; } export const DraggagleTableData: React.FC - = ({collectionId, attrTitle, children, isParent}) => { + = ({collectionId, attrTitle, children, isParent, resizeCounter}) => { const {dragOverId, dragSide} = useDraggableTableContext(); const {style} = getIdAndStyle(collectionId, attrTitle, dragOverId, dragSide); const {tableScrollTop, scrollY} = useTableTopScrollTopContext(); @@ -113,8 +114,6 @@ export const DraggagleTableData: React.FC const stickyHeaders = tableScrollTop === 0; const stickyHeaderHeight = (3 + level) * 16; const visibleTop = stickyHeaders ? Math.max(top, stickyHeaderHeight) : top; - // const visibleTop = stickyHeaders ? Math.max(top, (stickyHeaderHeight + scrollY)) : tableScrollTop; - // const visibleBottom = Math.min(window.innerHeight, Math.max(bottom, 0)); const visibleBottom = Math.min(window.innerHeight, bottom); const text = cellRef.current.innerText; const availableHeight = Math.abs(visibleBottom - visibleTop); @@ -125,27 +124,25 @@ export const DraggagleTableData: React.FC // the whole cell is visible return 0; } else if (top < visibleTop && bottom < window.innerHeight) { - // we are in the bottom part of the cell + // we can see the bottom border of the cell but not the top const hiddenHeightOfCell = height - availableHeight; - newTop = Math.max(0, (hiddenHeightOfCell - 16 + (availableHeight / 2))) /* + height of text */; + newTop = Math.max(0, (hiddenHeightOfCell - 16 + (availableHeight / 2))); } else if (top >= visibleTop && bottom > visibleBottom) { - // we are in the top part of the cell - newTop = Math.max(0, ((availableHeight) / 2)) /* + height of text */; + // we can see the top border of the cell but not the bottom + newTop = Math.max(0, ((availableHeight) / 2)); } else { - // we are in the middle of a cell that's taller than the table window - // we need to get the hidden top part of the cell + // we are in the middle of a cell - we can see neither the top nor the bottom border const hiddenTopPartOfCell = Math.max(0, visibleTop - top); - newTop = Math.max(0, (hiddenTopPartOfCell - 16 + (availableHeight) / 2)) /* + height of text */; + newTop = Math.max(0, (hiddenTopPartOfCell - 16 + (availableHeight) / 2)); } - console.log(cellRef.current?.innerText, JSON.stringify({top, bottom, visibleBottom, + console.log(text, JSON.stringify({top, bottom, visibleBottom, tableScrollTop, visibleTop, availableHeight, newTop, scrollY})); return newTop; } + }, [tableScrollTop, isParent, scrollY, level, resizeCounter]); - }, [tableScrollTop, isParent, scrollY, level]); - const textStyle: React.CSSProperties = {top: cellTextTop}; if (cellTextTop === 0) { textStyle.alignContent = "center"; @@ -156,7 +153,7 @@ export const DraggagleTableData: React.FC {isParent ? <> {children} -
{children}
+
{children}
: children } diff --git a/src/components/nested-table.tsx b/src/components/nested-table.tsx index 1cf0d1f..f483cfc 100644 --- a/src/components/nested-table.tsx +++ b/src/components/nested-table.tsx @@ -108,7 +108,7 @@ export const NestedTable = (props: IProps) => { ); }; - const mapCellsFromValues = (collectionId: number, rowKey: string, values: IValues, isParent?: boolean) => { + const mapCellsFromValues = (collectionId: number, rowKey: string, values: IValues, isParent?: boolean, resizeCounter?: number) => { return Object.keys(values).map((key, index) => { const val = values[key]; if (typeof val === "string" || typeof val === "number") { @@ -118,6 +118,7 @@ export const NestedTable = (props: IProps) => { attrTitle={key} key={`${rowKey}-${val}-${index}}`} isParent={isParent} + resizeCounter={resizeCounter} > {val} diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx index 15396c4..8805aee 100644 --- a/src/components/portrait-view.tsx +++ b/src/components/portrait-view.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from "react"; +import React, { useEffect, useMemo, useRef } from "react"; import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; import { DraggableTableContainer, DroppableTableData, DroppableTableHeader } from "./draggable-table-tags"; @@ -6,11 +6,11 @@ import css from "./tables.scss"; import { TableScrollTopContext, useTableScrollTop } from "../hooks/useTableScrollTop"; export type PortraitViewRowProps = - {collectionId: number, caseObj: IProcessedCaseObj, index?: null|number, isParent: boolean} & ITableProps; + {collectionId: number, caseObj: IProcessedCaseObj, index?: null|number, isParent: boolean, resizeCounter: number} & ITableProps; export const PortraitViewRow = (props: PortraitViewRowProps) => { const {paddingStyle, mapCellsFromValues, mapHeadersFromValues, showHeaders, - getClassName, collectionId, caseObj, index, isParent} = props; + getClassName, collectionId, caseObj, index, isParent, resizeCounter} = props; const {children, values} = caseObj; @@ -30,7 +30,7 @@ export const PortraitViewRow = (props: PortraitViewRowProps) => {
} - {mapCellsFromValues(collectionId, `parent-row-${index}`, values, isParent)} + {mapCellsFromValues(collectionId, `parent-row-${index}`, values, isParent, resizeCounter)}
{selectedDataSet.name}
@@ -70,6 +70,35 @@ export const PortraitView = (props: ITableProps) => { const {collectionClasses, selectedDataSet, collections, getValueLength} = props; const tableRef = useRef(null); const tableScrollTop = useTableScrollTop(tableRef); + const [resizeCounter, setResizeCounter] = React.useState(0); + + const thresh = useMemo(() => { + const t: number[] = []; + for (let i = 0; i <= 100; i++) { + t.push(i/100); + } + return t; + }, []); + + + useEffect(() => { + const handleIntersection = (entries: IntersectionObserverEntry[], o: any) => { + setResizeCounter((prevState) => prevState + 1); + }; + + const observer = new IntersectionObserver(handleIntersection, {threshold: thresh}); + + document.querySelectorAll(`.parent-row`).forEach((row) => { + observer.observe(row); + }); + + return () => { + document.querySelectorAll(`.parent-row`).forEach((row) => { + observer.unobserve(row); + }); + }; + + }, [thresh]); const renderTable = () => { const parentColl = collections.filter((coll: ICollection) => !coll.parent)[0]; @@ -95,6 +124,7 @@ export const PortraitView = (props: ITableProps) => { caseObj={caseObj} index={index} isParent={true} + resizeCounter={resizeCounter} /> ))} diff --git a/src/types.tsx b/src/types.tsx index c10ea8e..3f6e97c 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -55,7 +55,7 @@ export interface ITableProps { getClassName: (caseObj: IProcessedCaseObj) => string, selectedDataSet: IDataSet, collections: Array, - mapCellsFromValues: (collectionId: number, rowKey: string, values: IValues, isParent?: boolean) => void, + mapCellsFromValues: (collectionId: number, rowKey: string, values: IValues, isParent?: boolean, resizeCounter?: number) => void, mapHeadersFromValues: (collectionId: number, rowKey: string, values: IValues) => void, getValueLength: (firstRow: Array) => number paddingStyle: Record From c2351ccf239d81f0f5cc28effd9140d33858eec4 Mon Sep 17 00:00:00 2001 From: eireland Date: Mon, 23 Oct 2023 15:23:13 -0700 Subject: [PATCH 17/18] Clean up --- src/components/draggable-table-tags.tsx | 21 ++++++++++----------- src/components/nested-table.tsx | 3 ++- src/components/portrait-view.tsx | 15 ++++++--------- src/types.tsx | 3 ++- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/components/draggable-table-tags.tsx b/src/components/draggable-table-tags.tsx index 5a2a753..bdf3ad9 100644 --- a/src/components/draggable-table-tags.tsx +++ b/src/components/draggable-table-tags.tsx @@ -1,16 +1,18 @@ -import React, { useEffect, useMemo, useRef, useState } from "react"; +import React, { useMemo, useRef } from "react"; import { useDraggableTableContext, Side } from "../hooks/useDraggableTable"; +import { useTableTopScrollTopContext } from "../hooks/useTableScrollTop"; import AddIcon from "../assets/plus-level-1.svg"; import css from "./tables.scss"; -import { useTableTopScrollTopContext } from "../hooks/useTableScrollTop"; const highlightColor = "#FBF719"; const border = `5px solid ${highlightColor}`; const borderLeft = border; const borderRight = border; +const kCellHeight = 16; +const kMinNumHeaders = 3; const getStyle = (id: string, dragOverId?: string, dragSide?: Side) => { return id === dragOverId ? (dragSide === "left" ? {borderLeft} : {borderRight}) : {}; @@ -112,34 +114,31 @@ export const DraggagleTableData: React.FC } else { const {top, bottom, height} = cellRef.current.getBoundingClientRect(); const stickyHeaders = tableScrollTop === 0; - const stickyHeaderHeight = (3 + level) * 16; + const stickyHeaderHeight = (kMinNumHeaders + level) * kCellHeight; const visibleTop = stickyHeaders ? Math.max(top, stickyHeaderHeight) : top; const visibleBottom = Math.min(window.innerHeight, bottom); - const text = cellRef.current.innerText; const availableHeight = Math.abs(visibleBottom - visibleTop); let newTop; - if (top >= visibleTop && bottom <= visibleBottom) { - // the whole cell is visible + if (top >= visibleTop && bottom <= visibleBottom) { // the whole cell is visible return 0; } else if (top < visibleTop && bottom < window.innerHeight) { // we can see the bottom border of the cell but not the top const hiddenHeightOfCell = height - availableHeight; - newTop = Math.max(0, (hiddenHeightOfCell - 16 + (availableHeight / 2))); + newTop = Math.max(0, (hiddenHeightOfCell - kCellHeight + (availableHeight / 2))); } else if (top >= visibleTop && bottom > visibleBottom) { // we can see the top border of the cell but not the bottom newTop = Math.max(0, ((availableHeight) / 2)); } else { // we are in the middle of a cell - we can see neither the top nor the bottom border const hiddenTopPartOfCell = Math.max(0, visibleTop - top); - newTop = Math.max(0, (hiddenTopPartOfCell - 16 + (availableHeight) / 2)); + newTop = Math.max(0, (hiddenTopPartOfCell - kCellHeight + (availableHeight) / 2)); } - - console.log(text, JSON.stringify({top, bottom, visibleBottom, - tableScrollTop, visibleTop, availableHeight, newTop, scrollY})); return newTop; } + // resizeCounter is a hack to force rerender of text positioning when window is resized + // eslint-disable-next-line react-hooks/exhaustive-deps }, [tableScrollTop, isParent, scrollY, level, resizeCounter]); diff --git a/src/components/nested-table.tsx b/src/components/nested-table.tsx index f483cfc..de71fdd 100644 --- a/src/components/nested-table.tsx +++ b/src/components/nested-table.tsx @@ -108,7 +108,8 @@ export const NestedTable = (props: IProps) => { ); }; - const mapCellsFromValues = (collectionId: number, rowKey: string, values: IValues, isParent?: boolean, resizeCounter?: number) => { + const mapCellsFromValues = (collectionId: number, rowKey: string, values: IValues, isParent?: boolean, + resizeCounter?: number) => { return Object.keys(values).map((key, index) => { const val = values[key]; if (typeof val === "string" || typeof val === "number") { diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx index 8805aee..b7122ee 100644 --- a/src/components/portrait-view.tsx +++ b/src/components/portrait-view.tsx @@ -1,16 +1,16 @@ -import React, { useEffect, useMemo, useRef } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { ICollection, IProcessedCaseObj, ITableProps } from "../types"; import { DraggableTableContainer, DroppableTableData, DroppableTableHeader } from "./draggable-table-tags"; +import { TableScrollTopContext, useTableScrollTop } from "../hooks/useTableScrollTop"; import css from "./tables.scss"; -import { TableScrollTopContext, useTableScrollTop } from "../hooks/useTableScrollTop"; -export type PortraitViewRowProps = - {collectionId: number, caseObj: IProcessedCaseObj, index?: null|number, isParent: boolean, resizeCounter: number} & ITableProps; +export type PortraitViewRowProps = {collectionId: number, caseObj: IProcessedCaseObj, index?: null|number, + isParent: boolean, resizeCounter: number} & ITableProps; export const PortraitViewRow = (props: PortraitViewRowProps) => { const {paddingStyle, mapCellsFromValues, mapHeadersFromValues, showHeaders, - getClassName, collectionId, caseObj, index, isParent, resizeCounter} = props; + getClassName, collectionId, caseObj, index, isParent, resizeCounter} = props; const {children, values} = caseObj; @@ -70,7 +70,7 @@ export const PortraitView = (props: ITableProps) => { const {collectionClasses, selectedDataSet, collections, getValueLength} = props; const tableRef = useRef(null); const tableScrollTop = useTableScrollTop(tableRef); - const [resizeCounter, setResizeCounter] = React.useState(0); + const [resizeCounter, setResizeCounter] = useState(0); const thresh = useMemo(() => { const t: number[] = []; @@ -85,13 +85,10 @@ export const PortraitView = (props: ITableProps) => { const handleIntersection = (entries: IntersectionObserverEntry[], o: any) => { setResizeCounter((prevState) => prevState + 1); }; - const observer = new IntersectionObserver(handleIntersection, {threshold: thresh}); - document.querySelectorAll(`.parent-row`).forEach((row) => { observer.observe(row); }); - return () => { document.querySelectorAll(`.parent-row`).forEach((row) => { observer.unobserve(row); diff --git a/src/types.tsx b/src/types.tsx index 3f6e97c..c522b13 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -55,7 +55,8 @@ export interface ITableProps { getClassName: (caseObj: IProcessedCaseObj) => string, selectedDataSet: IDataSet, collections: Array, - mapCellsFromValues: (collectionId: number, rowKey: string, values: IValues, isParent?: boolean, resizeCounter?: number) => void, + mapCellsFromValues: (collectionId: number, rowKey: string, values: IValues, isParent?: boolean, + resizeCounter?: number) => void, mapHeadersFromValues: (collectionId: number, rowKey: string, values: IValues) => void, getValueLength: (firstRow: Array) => number paddingStyle: Record From ceb99f87871846230351c4e874224ccfa32fa39b Mon Sep 17 00:00:00 2001 From: eireland Date: Tue, 24 Oct 2023 10:02:33 -0700 Subject: [PATCH 18/18] replaces parent level hack to a prop passed down from parent. --- src/components/draggable-table-tags.tsx | 20 ++++---------------- src/components/nested-table.tsx | 3 ++- src/components/portrait-view.tsx | 11 +++++++---- src/types.tsx | 2 +- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/components/draggable-table-tags.tsx b/src/components/draggable-table-tags.tsx index bdf3ad9..b0412fd 100644 --- a/src/components/draggable-table-tags.tsx +++ b/src/components/draggable-table-tags.tsx @@ -85,36 +85,24 @@ interface DraggagleTableDataProps { style?: React.CSSProperties; isParent?: boolean; resizeCounter?: number; + parentLevel?: number; } export const DraggagleTableData: React.FC - = ({collectionId, attrTitle, children, isParent, resizeCounter}) => { + = ({collectionId, attrTitle, children, isParent, resizeCounter, parentLevel=0}) => { const {dragOverId, dragSide} = useDraggableTableContext(); const {style} = getIdAndStyle(collectionId, attrTitle, dragOverId, dragSide); const {tableScrollTop, scrollY} = useTableTopScrollTopContext(); const cellRef = useRef(null); - // HACK!!! - let level = 0; - if (cellRef.current) { - let walker: HTMLElement|null = cellRef.current; - while (walker && !walker.classList.contains("tables-portraitTable")) { - if (walker.tagName === "TABLE") { - level++; - } - walker = walker.parentElement; - } - } - level = level / 2; - const cellTextTop = useMemo (() =>{ if (!cellRef.current || !isParent) { return 0; } else { const {top, bottom, height} = cellRef.current.getBoundingClientRect(); const stickyHeaders = tableScrollTop === 0; - const stickyHeaderHeight = (kMinNumHeaders + level) * kCellHeight; + const stickyHeaderHeight = (kMinNumHeaders + parentLevel) * kCellHeight; const visibleTop = stickyHeaders ? Math.max(top, stickyHeaderHeight) : top; const visibleBottom = Math.min(window.innerHeight, bottom); const availableHeight = Math.abs(visibleBottom - visibleTop); @@ -139,7 +127,7 @@ export const DraggagleTableData: React.FC } // resizeCounter is a hack to force rerender of text positioning when window is resized // eslint-disable-next-line react-hooks/exhaustive-deps - }, [tableScrollTop, isParent, scrollY, level, resizeCounter]); + }, [tableScrollTop, isParent, scrollY, parentLevel, resizeCounter]); const textStyle: React.CSSProperties = {top: cellTextTop}; diff --git a/src/components/nested-table.tsx b/src/components/nested-table.tsx index de71fdd..f079f5d 100644 --- a/src/components/nested-table.tsx +++ b/src/components/nested-table.tsx @@ -109,7 +109,7 @@ export const NestedTable = (props: IProps) => { }; const mapCellsFromValues = (collectionId: number, rowKey: string, values: IValues, isParent?: boolean, - resizeCounter?: number) => { + resizeCounter?: number, parentLevel?: number) => { return Object.keys(values).map((key, index) => { const val = values[key]; if (typeof val === "string" || typeof val === "number") { @@ -120,6 +120,7 @@ export const NestedTable = (props: IProps) => { key={`${rowKey}-${val}-${index}}`} isParent={isParent} resizeCounter={resizeCounter} + parentLevel={parentLevel} > {val} diff --git a/src/components/portrait-view.tsx b/src/components/portrait-view.tsx index b7122ee..f115059 100644 --- a/src/components/portrait-view.tsx +++ b/src/components/portrait-view.tsx @@ -6,11 +6,12 @@ import { TableScrollTopContext, useTableScrollTop } from "../hooks/useTableScrol import css from "./tables.scss"; export type PortraitViewRowProps = {collectionId: number, caseObj: IProcessedCaseObj, index?: null|number, - isParent: boolean, resizeCounter: number} & ITableProps; + isParent: boolean, resizeCounter: number, parentLevel?: number} + & ITableProps; export const PortraitViewRow = (props: PortraitViewRowProps) => { const {paddingStyle, mapCellsFromValues, mapHeadersFromValues, showHeaders, - getClassName, collectionId, caseObj, index, isParent, resizeCounter} = props; + getClassName, collectionId, caseObj, index, isParent, resizeCounter, parentLevel} = props; const {children, values} = caseObj; @@ -30,7 +31,7 @@ export const PortraitViewRow = (props: PortraitViewRowProps) => { } - {mapCellsFromValues(collectionId, `parent-row-${index}`, values, isParent, resizeCounter)} + {mapCellsFromValues(collectionId, `parent-row-${index}`, values, isParent, resizeCounter, parentLevel)}
@@ -41,7 +42,8 @@ export const PortraitViewRow = (props: PortraitViewRowProps) => { collectionId: child.collection.id, caseObj: child, index: i, - isParent + isParent, + parentLevel: parentLevel !== undefined && parentLevel !== null ? parentLevel + 1 : undefined, }; if (i === 0 && !child.children.length) { return ( @@ -122,6 +124,7 @@ export const PortraitView = (props: ITableProps) => { index={index} isParent={true} resizeCounter={resizeCounter} + parentLevel={0} /> ))} diff --git a/src/types.tsx b/src/types.tsx index c522b13..9ba0c2c 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -56,7 +56,7 @@ export interface ITableProps { selectedDataSet: IDataSet, collections: Array, mapCellsFromValues: (collectionId: number, rowKey: string, values: IValues, isParent?: boolean, - resizeCounter?: number) => void, + resizeCounter?: number, parentLevel?: number) => void, mapHeadersFromValues: (collectionId: number, rowKey: string, values: IValues) => void, getValueLength: (firstRow: Array) => number paddingStyle: Record