From 84fb6a8915608e2e98186617de4816ef42cf1f72 Mon Sep 17 00:00:00 2001 From: Randy Wilson Date: Tue, 17 Sep 2024 15:11:43 -0600 Subject: [PATCH] Vertical summary. Add parents before spouse to reduce line crossings. Fix vertical highlighting. Also fix up/down buttons on summary. --- graph/view/RelChartBuilder.js | 2 +- split/time-machine.js | 283 +++++++++++++++++++++++++++++----- 2 files changed, 243 insertions(+), 42 deletions(-) diff --git a/graph/view/RelChartBuilder.js b/graph/view/RelChartBuilder.js index b8dc77a..0f9d1df 100644 --- a/graph/view/RelChartBuilder.js +++ b/graph/view/RelChartBuilder.js @@ -181,8 +181,8 @@ RelChartBuilder.prototype.addRelatives = function(personBox, subtree) { do { personBox.subtree = subtree; - this.addSpouses(personBox, subtree, needsRelativesQueue); this.addParents(personBox, subtree, needsRelativesQueue); + this.addSpouses(personBox, subtree, needsRelativesQueue); personBox = needsRelativesQueue.length > 0 ? needsRelativesQueue.splice(0, 1)[0] : null; } while (personBox); diff --git a/split/time-machine.js b/split/time-machine.js index 599a32c..1503ef8 100644 --- a/split/time-machine.js +++ b/split/time-machine.js @@ -1883,6 +1883,7 @@ function handleRowClick(event, rowId) { } } +// Referenced from 'clickInfo()', but IntelliJ doesn't recognize that. function handleColumnClick(event, rowId) { let grouper = grouperMap[rowId]; console.log("Clicked column " + rowId + (event.shiftKey ? " + shift" : "") + (event.ctrlKey ? " + ctrl" : "") + (event.metaKey ? " + meta" : "")); @@ -2592,10 +2593,12 @@ class PersonRow { return false; } - function getButtonsHtml(item) { + function getButtonsHtml(item, isKeep) { let element = split.elements[item.elementIndex]; // Up, =, down buttons - let buttonsHtml = makeButton("↑", DIR_KEEP, element) + makeButton("=", DIR_COPY, element) + makeButton("↓", DIR_MOVE, element) + " "; + let buttonsHtml = (isKeep ? "" : makeButton("↑", DIR_KEEP, element)) + + makeButton("=", DIR_COPY, element) + + (isKeep ? makeButton("↓", DIR_MOVE, element) : "") + " "; if (element.isExtra() && (element.isVisible() || displayOptions.shouldExpandHiddenValues)) { // Add checkbox for information that is not on the current merged person. buttonsHtml += " "; @@ -2623,7 +2626,7 @@ class PersonRow { let namesHtmlList = []; for (let name of person.names) { if (shouldDisplaySummaryElement(name)) { - namesHtmlList.push(getButtonsHtml(name) + getNameFormsHtml(name)); + namesHtmlList.push(getButtonsHtml(name, splitDirection === DIR_KEEP) + getNameFormsHtml(name)); } } namesHtml += namesHtmlList.join("
"); @@ -2639,7 +2642,7 @@ class PersonRow { if (person.facts && person.facts.length > 0) { for (let fact of person.facts) { if (shouldDisplaySummaryElement(fact)) { - factsHtml += getButtonsHtml(fact) + getFactHtml(fact, true, false) + "
"; + factsHtml += getButtonsHtml(fact, splitDirection === DIR_KEEP) + getFactHtml(fact, true, false) + "
"; } } } @@ -2654,7 +2657,7 @@ class PersonRow { (usedColumns.has("father-name") && usedColumns.has("mother-name") ? " colspan='2'" : "") + ">"; for (let element of split.elements) { if (element.type === TYPE_PARENTS && (element.direction === direction || element.direction === DIR_COPY)) { - parentsHtml += getButtonsHtml(element) + getParentsHtml(element.item, encode(" & ")) + "
\n"; + parentsHtml += getButtonsHtml(element, direction === DIR_KEEP) + getParentsHtml(element.item, encode(" & ")) + "
\n"; } } return parentsHtml + "\n"; @@ -2684,7 +2687,7 @@ class PersonRow { isFirstSpouse = startNewRowIfNotFirst(isFirstSpouse, allRowsClass, noteCellHtmlHolder, this.isSummaryRow()); let familyBottomClass = spouseIndex === this.families.length - 1 ? bottomClass : ""; let childrenRowSpan = getRowspanParameter(displayOptions.shouldShowChildren ? spouseFamily.children.length : 1); - let spouseButtonsHtml = this.isSummaryRow() ? getButtonsHtml(spouseFamily.spouse.coupleRelationship): ""; + let spouseButtonsHtml = this.isSummaryRow() ? getButtonsHtml(spouseFamily.spouse.coupleRelationship, splitDirection === DIR_KEEP): ""; addColumn("spouse-name", childrenRowSpan, spouseButtonsHtml + (spouseFamily.spouse ? spouseFamily.spouse.name : ""), familyBottomClass); addColumn("spouse-facts", childrenRowSpan, spouseFamily.spouse ? spouseFamily.spouse.facts : "", familyBottomClass); if (spouseFamily.children.length > 0 && displayOptions.shouldShowChildren) { @@ -2693,7 +2696,7 @@ class PersonRow { let child = spouseFamily.children[childIndex]; isFirstChild = startNewRowIfNotFirst(isFirstChild, allRowsClass, noteCellHtmlHolder, this.isSummaryRow()); let childBottomClass = childIndex === spouseFamily.children.length - 1 ? familyBottomClass : ""; - let childButtonsHtml = this.isSummaryRow() ? getButtonsHtml(child.childAndParentsRelationship): ""; + let childButtonsHtml = this.isSummaryRow() ? getButtonsHtml(child.childAndParentsRelationship, splitDirection === DIR_KEEP): ""; addColumn(COLUMN_CHILD_NAME, "", childButtonsHtml + child.name, childBottomClass); addColumn(COLUMN_CHILD_FACTS, "", child.facts, childBottomClass); } @@ -2770,16 +2773,20 @@ class PersonRow { } getCellClass(tabId) { + let cellClass; if (this.isSummaryRow()) { - return "summary-row"; + cellClass = "summary-row"; } - if (this.isSourceRow()) { - return "source-row"; + else if (this.isSourceRow()) { + cellClass = "source-row"; } - if (this.isOwsRow()) { - return "ord-row"; + else if (this.isOwsRow()) { + cellClass = "ord-row"; } - return "merge-id" + (tabId === MERGE_VIEW && this.isDupNode ? "-dup" : ""); + else { + cellClass ="merge-id" + (tabId === MERGE_VIEW && this.isDupNode ? "-dup" : ""); + } + return cellClass + " " + this.getIdClass(); } getCollectionHtml(rowSpan, clickInfo) { @@ -3561,8 +3568,9 @@ class Element { this.type = type; // Type of item (see TYPE_* above) this.direction = direction; // Direction for this piece of information: DIR_KEEP/COPY/MOVE this.famId = famId; // optional value identifying which family (i.e., spouseId) this relationship element is part of, to group children by spouse. + this.relative = null; // spouse or child GedcomX person. this.canExpand = false; - // Flag for whether following elements of the same type AND with an 'elementSource' should be displayed. + // Flag for whether following elements of the same type AND with an 'elementSource' (i.e., not from the current person) should be displayed. this.isExpanded = false; // Where this came from, if it isn't the main current person. this.elementSource = null; @@ -3603,8 +3611,14 @@ class Split { } initElements(gedcomx, personId) { - function addElement(item, type, famId) { + function addElement(item, type, famId, personIdForFacts) { let element = new Element(elementIndex++, item, type, DIR_KEEP, famId); + if (personIdForFacts) { + let relative = findPersonInGx(gedcomx, personIdForFacts); + if (relative) { + element.relative = relative; + } + } if (item.elementSource) { element.elementSource = item.elementSource; delete item["elementSource"]; @@ -3631,7 +3645,7 @@ class Split { function addRelationshipElements(gedcomx) { function addChildrenElements(spouseId) { for (let childRel of getList(childrenMap, spouseId)) { - addElement(childRel, TYPE_CHILD); + addElement(childRel, TYPE_CHILD, spouseId, getRelativeId(childRel, "child")); } } // child-and-parents relationships in which the person is a child, i.e., containing the person's parents @@ -3645,7 +3659,7 @@ class Split { addElement(parentRel, TYPE_PARENTS); } for (let [spouseId, coupleRel] of coupleMap) { - addElement(coupleRel, TYPE_SPOUSE); + addElement(coupleRel, TYPE_SPOUSE, spouseId, spouseId); addChildrenElements(spouseId); } for (let spouseId in childrenMap) { @@ -3916,6 +3930,17 @@ function moveElement(direction, elementId) { if (element.direction !== DIR_KEEP && element.isExtra() && !element.isSelected) { element.isSelected = true; } + if (element.type === TYPE_SPOUSE) { + // When moving a spouse, move all the children of that spouse as well. They can be moved back individually. + for (let i = elementId + 1; i < split.elements.length; i++) { + let childElement = split.elements[i]; + if (childElement.type === TYPE_CHILD && childElement.famId === element.famId) { + childElement.direction = direction; + } else { + break; + } + } + } updateSummaryRows(); updateSplitViewHtml(); updateFlatViewHtml(comboGrouper); @@ -3984,7 +4009,7 @@ function getSplitViewHtml() { continue; // skip elements that have been deleted. (Perhaps we eventually make these available to "reclaim" when splitting out a person) } if (!prevElement || element.type !== prevElement.type || element.famId !== prevElement.famId) { - html += getHeadingHtml(element); + html += getHeadingHtml(element); // may update isExpanded } if (isExpanded || element.isVisible()) { @@ -4065,13 +4090,17 @@ function getElementHtml(element, personId, shouldDisplay) { return wrapTooltip(element, elementHtml, element.elementSource ? encode(element.elementSource) : null); } +function getExtraValueCheckbox(element) { + return element.isExtra() ? "" : ""; +} + function wrapTooltip(element, mainHtml, tooltipHtml) { let undecidedClass = element.direction === DIR_NULL ? " undecided" : ""; if (!tooltipHtml) { return "" + mainHtml + ""; } return "" - + (element.isExtra() ? "" : "") + + getExtraValueCheckbox(element) + ""; } @@ -4113,31 +4142,120 @@ function deleteEmptyGroup(groupId) { } function getVerticalGrouperHtml(grouper) { + function padGroup(groupIndex) { if (groupIndex < grouper.mergeGroups.length - 1) { html += ""; } } - function getSummaryCellHtml(columnIdForRow, isKeep) { - return "..."; + function shouldInclude(element, type, isKeep) { + return element.type === type + && (displayOptions.shouldExpandHiddenValues || element.isVisible()) + && (element.direction === DIR_COPY || (isKeep && element.direction === DIR_KEEP) || (!isKeep && element.direction === DIR_MOVE)) + } + + function horizontalButtonsHtml(element, isKeep) { + let html = (isKeep ? "" : makeButton("<", DIR_KEEP, element)) + + makeButton("=", DIR_COPY, element) + + (isKeep ? makeButton(">", DIR_MOVE, element) : ""); + let checkbox = getExtraValueCheckbox(element); + return html + (checkbox ? checkbox : " "); + } + + function getSummaryCellHtml(columnIdForRow, isKeep, relativeElement) { + if (columnIdForRow === COLUMN_MOTHER_NAME && grouper.usedColumns.has(COLUMN_FATHER_NAME)) { + return ""; // mother column is grouped in same row as father row, so no cell used. + } + let rowspan = (columnIdForRow === COLUMN_FATHER_NAME && grouper.usedColumns.has(COLUMN_MOTHER_NAME)) ? " rowspan='2'" : ""; + let html = ""; + let personRow = isKeep ? keepPersonRow : splitPersonRow; + switch (columnIdForRow) { + case COLUMN_PERSON_ID: + html += personRow.getPersonIdHtml(false); + break; + case COLUMN_COLLECTION: + if (isKeep) { + html += "Person to keep"; + } + else { + html += "Person to split out"; + } + break; + case COLUMN_PERSON_NAME: + let nameRows = []; + split.elements.forEach(element => { + if (shouldInclude(element, TYPE_NAME, isKeep)) { + nameRows.push(horizontalButtonsHtml(element, isKeep) + getFullText(element.item)); + } + }); + html += nameRows.join("
\n"); + break; + + case COLUMN_PERSON_FACTS: + let factRows = []; + split.elements.forEach(element => { + if (shouldInclude(element, TYPE_FACT, isKeep)) { + factRows.push(horizontalButtonsHtml(element, isKeep) + getFactHtml(element.item, true)); + } + }); + html += factRows.join("
\n"); + break; + case COLUMN_FATHER_NAME: + case COLUMN_MOTHER_NAME: + // Father & mother are grouped into the same row (with rowspan=2 if both are present) + let parentRows = []; + split.elements.forEach(element => { + if (shouldInclude(element, TYPE_PARENTS, isKeep)) { + parentRows.push(horizontalButtonsHtml(element, isKeep) + getParentsHtml(element.item, "
& ")); + } + }); + html += parentRows.join("
\n"); + break; + case COLUMN_SPOUSE_NAME: + case COLUMN_CHILD_NAME: + if (relativeElement) { + html += horizontalButtonsHtml(relativeElement, isKeep) + + getRelativeHtml(relativeElement.relative.id, relativeElement.item.updated); + } + break; + case COLUMN_SPOUSE_FACTS: + case COLUMN_CHILD_FACTS: + if (relativeElement) { + html += getFactsHtml(relativeElement.relative, isKeep); + } + break; + case COLUMN_ATTACHED_TO_IDS: + case COLUMN_CREATED: + case COLUMN_RECORD_DATE: + // Leave blank. + break; + + /* + const TYPE_NAME = "Name"; + const TYPE_GENDER = "Gender"; + const TYPE_FACT = "Facts"; + const TYPE_PARENTS = "Parents"; // child-and-parents relationship from the person to their parents + const TYPE_SPOUSE = "Spouse"; // Couple relationship to a spouse + const TYPE_CHILD = "Children"; // child-and-parents relationship to a child + */ + } + return html + ""; } - function addRow(columnIdForRow, rowLabel, shouldAlwaysInclude, personRowFunction) { + function addRow(columnIdForRow, rowLabel, shouldAlwaysInclude, personRowFunction, keepRelativeElement, splitRelativeElement) { if (shouldAlwaysInclude || grouper.usedColumns.has(columnIdForRow)) { html += "" + headerHtml(grouper, columnIdForRow, rowLabel, shouldAlwaysInclude, true); for (let groupIndex = 0; groupIndex < grouper.mergeGroups.length; groupIndex++) { let group = grouper.mergeGroups[groupIndex]; if (group.groupId === grouper.splitGroupId) { - html += ""; if (displayOptions.shouldShowSummaries) { - html += getSummaryCellHtml(columnIdForRow, true); + html += getSummaryCellHtml(columnIdForRow, true, keepRelativeElement); + html += getSummaryCellHtml(columnIdForRow, false, splitRelativeElement); + padGroup(groupIndex - 1); } - html += ""; - if (displayOptions.shouldShowSummaries) { - padGroup(groupIndex); - html += "" + getSummaryCellHtml(columnIdForRow, false) + ""; - padGroup(groupIndex); + else { + html += ""; } } for (let personRow of group.personRows) { @@ -4159,9 +4277,8 @@ function getVerticalGrouperHtml(grouper) { // [